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.
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.
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; }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With