4

I have created a custom TextEditor control that inherits from AvalonEdit. I have done this to facilitate the use of MVVM and Caliburn Micro using this editor control. The [cut down for display purposes] MvvTextEditor class is

public class MvvmTextEditor : TextEditor, INotifyPropertyChanged
{
    public MvvmTextEditor()
    {
        TextArea.SelectionChanged += TextArea_SelectionChanged;
    }

    void TextArea_SelectionChanged(object sender, EventArgs e)
    {
        this.SelectionStart = SelectionStart;
        this.SelectionLength = SelectionLength;
    }

    public static readonly DependencyProperty SelectionLengthProperty =
         DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
         new PropertyMetadata((obj, args) =>
             {
                 MvvmTextEditor target = (MvvmTextEditor)obj;
                 target.SelectionLength = (int)args.NewValue;
             }));

    public new int SelectionLength
    {
        get { return base.SelectionLength; }
        set { SetValue(SelectionLengthProperty, value); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void RaisePropertyChanged([CallerMemberName] string caller = null)
    {
        var handler = PropertyChanged;
        if (handler != null)
            PropertyChanged(this, new PropertyChangedEventArgs(caller));
    }
}

Now, in the view that holds this control, I have the following XAML:

    <Controls:MvvmTextEditor 
        Caliburn:Message.Attach="[Event TextChanged] = [Action DocumentChanged()]"
        TextLocation="{Binding TextLocation, Mode=TwoWay}"
        SyntaxHighlighting="{Binding HighlightingDefinition}" 
        SelectionLength="{Binding SelectionLength, 
                                  Mode=TwoWay, 
                                  NotifyOnSourceUpdated=True, 
                                  NotifyOnTargetUpdated=True}" 
        Document="{Binding Document, Mode=TwoWay}"/>

My issue is SelectionLength (and SelectionStart but let us just consider the length for now as the problem is the same). If I selected something with the mouse, the binding from the View to my View Model works great. Now, I have written a find and replace utility and I want to set the SelectionLength (which has get and set available in the TextEditor control) from the code behind. In my View Model I am simply setting SelectionLength = 50, I implement this in the View Model like

private int selectionLength;
public int SelectionLength
{
    get { return selectionLength; }
    set
    {
        if (selectionLength == value)
            return;
        selectionLength = value;
        Console.WriteLine(String.Format("Selection Length = {0}", selectionLength));
        NotifyOfPropertyChange(() => SelectionLength);
    }
}

when I set SelectionLength = 50, the DependencyProperty SelectionLengthProperty does not get updated in the MvvmTextEditor class, it is like the TwoWay binding to my control is failing but using Snoop there is no sign of this. I thought this would just work via the binding, but this does not seem to be the case.

Is there something simple I am missing, or will I have to set up and event handler in the MvvmTextEditor class which listens for changes in my View Model and updated the DP itself [which presents it's own problems]?

Thanks for your time.

MoonKnight
  • 23,214
  • 40
  • 145
  • 277

3 Answers3

1

This is because the Getter and Setter from a DependencyProperty is only a .NET Wrapper. The Framework will use the GetValue and SetValue itself.

What you can try is to access the PropertyChangedCallback from your DependencyProperty and there set the correct Value.

 public int SelectionLength
        {
            get { return (int)GetValue(SelectionLengthProperty); }
            set { SetValue(SelectionLengthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SelectionLength.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SelectionLengthProperty =
            DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor), new PropertyMetadata(0,SelectionLengthPropertyChanged));


        private static void SelectionLengthPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var textEditor = obj as MvvmTextEditor;

            textEditor.SelectionLength = e.NewValue;
        }
Johannes Wanzek
  • 2,825
  • 2
  • 29
  • 47
  • Are you registering `SelectionLengthPropertyChanged` in the constructor? – MoonKnight Jun 02 '14 at 08:24
  • No I'm sorry. You are registering it in the `Dependencyproperty` `Metadata`. Forgot this in my example. Also: try setting the value DIRECTLY to the control. This will be the best way I guess. – Johannes Wanzek Jun 02 '14 at 08:27
  • One thing is that I NEED to call `get { retrun base.SelectionLength; }` because I need to return this from the underlying control (overridden control). – MoonKnight Jun 02 '14 at 08:37
  • Thanks for your help here, but this has not worked. It is behaving in exactly the same way as before... – MoonKnight Jun 02 '14 at 08:54
1

Here is another answer if you are still open. Since SelectionLength is already defined as a dependency property on the base class, rather than create a derived class (or add an already existing property to the derived class), I would use an attached property to achieve the same functionality.

The key is to use System.ComponentModel.DependencyPropertyDescriptor to subscribe to the change event of the already existing SelectionLength dependency property and then take your desired action in the event handler.

Sample code below:

public class SomeBehavior
{
    public static readonly DependencyProperty IsEnabledProperty
        = DependencyProperty.RegisterAttached("IsEnabled",
        typeof(bool), typeof(SomeBehavior), new PropertyMetadata(OnIsEnabledChanged));

    public static void SetIsEnabled(DependencyObject dpo, bool value)
    {
        dpo.SetValue(IsEnabledProperty, value);
    }

    public static bool GetIsEnabled(DependencyObject dpo)
    {
        return (bool)dpo.GetValue(IsEnabledProperty);
    }

    private static void OnIsEnabledChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
    {
        var editor = dpo as TextEditor;
        if (editor == null)
            return;

        var dpDescriptor = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(TextEditor.SelectionLengthProperty,editor.GetType());
        dpDescriptor.AddValueChanged(editor, OnSelectionLengthChanged);
    }

    private static void OnSelectionLengthChanged(object sender, EventArgs e)
    {
        var editor = (TextEditor)sender;
        editor.Select(editor.SelectionStart, editor.SelectionLength);
    }
}

Xaml below:

  <Controls:TextEditor Behaviors:SomeBehavior.IsEnabled="True">
    </Controls:TextEditor>
Kess
  • 488
  • 3
  • 11
0

This is how I did this...

public static readonly DependencyProperty SelectionLengthProperty =
     DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
     new PropertyMetadata((obj, args) =>
         {
             MvvmTextEditor target = (MvvmTextEditor)obj;
             if (target.SelectionLength != (int)args.NewValue)
             {
                 target.SelectionLength = (int)args.NewValue;
                 target.Select(target.SelectionStart, (int)args.NewValue);
             }
         }));

public new int SelectionLength
{
    get { return base.SelectionLength; }
    //get { return (int)GetValue(SelectionLengthProperty); }
    set { SetValue(SelectionLengthProperty, value); }
}

Sorry for any time wasted. I hope this helps someone else...

MoonKnight
  • 23,214
  • 40
  • 145
  • 277