Two Way Binding to AvalonEdit Document Text using MVVM

avalonedit, binding, c#, mvvm, wpf

Solution

Create a Behavior class that will attach the TextChanged event and will hook up the dependency property that is bound to the ViewModel.

AvalonTextBehavior.cs

public sealed class AvalonEditBehaviour : Behavior<TextEditor> 
{
    public static readonly DependencyProperty GiveMeTheTextProperty =
        DependencyProperty.Register("GiveMeTheText", typeof(string), typeof(AvalonEditBehaviour), 
        new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, PropertyChangedCallback));

    public string GiveMeTheText
    {
        get { return (string)GetValue(GiveMeTheTextProperty); }
        set { SetValue(GiveMeTheTextProperty, value); }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        if (AssociatedObject != null)
            AssociatedObject.TextChanged += AssociatedObjectOnTextChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        if (AssociatedObject != null)
            AssociatedObject.TextChanged -= AssociatedObjectOnTextChanged;
    }

    private void AssociatedObjectOnTextChanged(object sender, EventArgs eventArgs)
    {
        var textEditor = sender as TextEditor;
        if (textEditor != null)
        {
            if (textEditor.Document != null)
                GiveMeTheText = textEditor.Document.Text;
        }
    }

    private static void PropertyChangedCallback(
        DependencyObject dependencyObject,
        DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        var behavior = dependencyObject as AvalonEditBehaviour;
        if (behavior.AssociatedObject!= null)
        {
            var editor = behavior.AssociatedObject as TextEditor;
            if (editor.Document != null)
            {
                var caretOffset = editor.CaretOffset;
                editor.Document.Text = dependencyPropertyChangedEventArgs.NewValue.ToString();
                editor.CaretOffset = caretOffset;
            }
        }
    }
}

View.xaml

 <avalonedit:TextEditor
        WordWrap="True"
        ShowLineNumbers="True"
        LineNumbersForeground="Magenta"
        x:Name="textEditor"
        FontFamily="Consolas"
        SyntaxHighlighting="XML"
        FontSize="10pt">
        <i:Interaction.Behaviors>
            <controls:AvalonEditBehaviour GiveMeTheText="{Binding Test, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        </i:Interaction.Behaviors>
    </avalonedit:TextEditor>

`i` must be defined as

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

ViewModel.cs

    private string _test;
    public string Test
    {
        get { return _test; }
        set { _test = value; }
    }

That should give you the Text and push it back to the ViewModel.

Problem

I want to include an AvalonEdit `TextEditor` control into my MVVM application. The first thing I require is to be able to bind to the `TextEditor.Text` property so that I can display text. To do this I have followed and example that was given in Making AvalonEdit MVVM compatible. Now, I have implemented the following class using the accepted answer as a template ``` public sealed class MvvmTextEditor : TextEditor, INotifyPropertyChanged { public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(MvvmTextEditor), new PropertyMetadata((obj, args) => { MvvmTextEditor target = (MvvmTextEditor)obj; target.Text = (string)args.NewValue; }) ); public new string Text { get { return base.Text; } set { base.Text = value; } } protected override void OnTextChanged(EventArgs e) { RaisePropertyChanged("Text"); base.OnTextChanged(e); } public event PropertyChangedEventHandler PropertyChanged; public void RaisePropertyChanged(string info) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(info)); } } ``` Where the XAML is ``` <Controls:MvvmTextEditor HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FontFamily="Consolas" FontSize="9pt" Margin="2,2" Text="{Binding Text, NotifyOnSourceUpdated=True, Mode=TwoWay}"/> ``` Firstly, this does not work. The Binding is not shown in Snoop at all (not red, not anything, in fact I cannot even see the `Text` dependency property). I have seen this question which is exactly the same as mine Two-way binding in AvalonEdit doesn't work but the accepted answer does not work (at least for me). So my question is: How can I perform two way binding using the above method and what is the correct implementation of my `MvvmTextEditor` class? Thanks for your time. Note: I have my `Text` property in my ViewModel and it implements the required `INotifyPropertyChanged` interface.

Original source

Related problems