Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Two Way Binding to AvalonEdit Document Text using MVVM

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.

like image 655
MoonKnight Avatar asked Sep 23 '13 16:09

MoonKnight


2 Answers

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.

like image 129
123 456 789 0 Avatar answered Oct 02 '22 21:10

123 456 789 0


Create a BindableAvalonEditor class with a two-way binding on the Text property.

I was able to establish a two-way binding with the latest version of AvalonEdit by combining Jonathan Perry's answer and 123 456 789 0's answer. This allows a direct two-way binding without the need for behaviors.

Here is the source code...

public class BindableAvalonEditor : ICSharpCode.AvalonEdit.TextEditor, INotifyPropertyChanged {     /// <summary>     /// A bindable Text property     /// </summary>     public new string Text     {         get         {             return (string)GetValue(TextProperty);         }         set         {             SetValue(TextProperty, value);             RaisePropertyChanged("Text");         }     }      /// <summary>     /// The bindable text property dependency property     /// </summary>     public static readonly DependencyProperty TextProperty =         DependencyProperty.Register(             "Text",             typeof(string),             typeof(BindableAvalonEditor),             new FrameworkPropertyMetadata             {                 DefaultValue = default(string),                 BindsTwoWayByDefault = true,                 PropertyChangedCallback = OnDependencyPropertyChanged             }         );      protected static void OnDependencyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)     {         var target = (BindableAvalonEditor)obj;          if (target.Document != null)         {             var caretOffset = target.CaretOffset;             var newValue = args.NewValue;              if (newValue == null)             {                 newValue = "";             }              target.Document.Text = (string)newValue;             target.CaretOffset = Math.Min(caretOffset, newValue.ToString().Length);         }     }      protected override void OnTextChanged(EventArgs e)     {         if (this.Document != null)         {             Text = this.Document.Text;         }          base.OnTextChanged(e);     }      /// <summary>     /// Raises a property changed event     /// </summary>     /// <param name="property">The name of the property that updates</param>     public void RaisePropertyChanged(string property)     {         if (PropertyChanged != null)         {             PropertyChanged(this, new PropertyChangedEventArgs(property));         }     }      public event PropertyChangedEventHandler PropertyChanged; } 
like image 40
SlugBob Avatar answered Oct 02 '22 22:10

SlugBob