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
View.xaml
where
i
is defined as "xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity""ViewModel.cs
That should give you the Text and push it back to the ViewModel.
Another nice OOP approach is to download the source code of AvalonEdit (it's open sourced), and creating a new class that inherits from
TextEditor
class (the main editor of AvalonEdit).What you want to do is basically override the
Text
property and implement anINotifyPropertyChanged
version of it, using dependency property for theText
property and raising theOnPropertyChanged
event when text is changed (this can be done by overriding theOnTextChanged()
method.Here's a quick code (fully working) example that works for me:
For those wondering about an MVVM implementation using AvalonEdit, here is one of the ways it can be done, first we have the class
Then in your view where you want to have AvalonEdit, you can do
Where this can be placed in a UserControl or Window or what ever, then in the ViewModel for this view we have (where I am using Caliburn Micro for the MVVM framework stuff)
And that's it! Done.
I hope this helps.
Edit. for all those looking for an example of working with AvalonEdit using MVVM, you can download a very basic editor application from http://1drv.ms/1E5nhCJ.
Notes. This application actually creates a MVVM friendly editor control by inheriting from the AvalonEdit standard control and adds additional Dependency Properties to it as appropriate - *this is different to what I have shown in the answer given above*. However, in the solution I have also shown how this can be done (as I describe in the answer above) using Attached Properties and there is code in the solution under the
Behaviors
namespace. What is actually implemented however, is the first of the above approaches.Please also be aware that there is some code in the solution that is unused. This *sample* was a stripped back version of a larger application and I have left some code in as it could be useful to the user who downloads this example editor. In addition to the above, in the example code I access the Text by binding to document, there are some purest that may argue that this is not pure-MVVM, and I say "okay, but it works". Some times fighting this pattern is not the way to go.
I hope this of use to some of you.
I like none of these solutions. The reason the author didn't create a dependency property on Text is for performance reason. Working around it by creating an attached property means the text string must be recreated on every key stroke. On a 100mb file, this can be a serious performance issue. Internally, it only uses a document buffer and will never create the full string unless requested.
It exposes another property, Document, which is a dependency property, and it exposes the Text property to construct the string only when needed. Although you can bind to it, it would mean designing your ViewModel around a UI element which defeats the purpose of having a ViewModel UI-agnostic. I don't like that option either.
Honestly, the cleanest(ish) solution is to create 2 events in your ViewModel, one to display the text and one to update the text. Then you write a one-line event handler in your code-behind, which is fine since it's purely UI-related. That way, you construct and assign the full document string only when it's truly needed. Additionally, you don't even need to store (nor update) the text in the ViewModel. Just raise DisplayScript and UpdateScript when it is needed.
It's not an ideal solution, but there are less drawbacks than any other method I've seen.
TextBox also faces a similar issue, and it solves it by internally using a DeferredReference object that constructs the string only when it is really needed. That class is internal and not available to the public, and the Binding code is hard-coded to handle DeferredReference in a special way. Unfortunately there doesn't seen to be any way of solving the problem in the same way as TextBox -- perhaps unless TextEditor would inherit from TextBox.
Here is my very first StackOverflow post!
I was able to establish a two-way binding with the latest version of AvalonEdit using an object oriented approach which combines 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...