使用MVVM实现对AvalonEdit文档文本的双向绑定

32

我希望在我的MVVM应用程序中包含一个AvalonEdit TextEditor控件。首先需要做的是能够绑定到TextEditor.Text属性,以便显示文本。为了实现这一点,我遵循了使AvalonEdit MVVM兼容中给出的示例。现在,我已经使用接受的答案作为模板实现了以下类:

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));
    }
}

XAML是什么?

<Controls:MvvmTextEditor HorizontalAlignment="Stretch"
                         VerticalAlignment="Stretch"
                         FontFamily="Consolas"
                         FontSize="9pt" 
                         Margin="2,2" 
                         Text="{Binding Text, NotifyOnSourceUpdated=True, Mode=TwoWay}"/>

首先,这种方法行不通。在Snoop中根本没有显示绑定(实际上我甚至看不到Text依赖属性)。
我看到了与我的问题完全相同的这个问题Two-way binding in AvalonEdit doesn't work,但是被接受的答案并不起作用(至少对我来说是这样的)。所以我的问题是:
如何使用上述方法执行双向绑定,以及我的类的正确实现是什么?
感谢您的时间。
注:我在ViewModel中有我的

这是我要定位的 Text 属性。我肯定已经正确地监视了控件。感谢您的帮助... - MoonKnight
这行代码让我有些怀疑,"RaisePropertyChanged("Text");",你不应该在控件层面这样做,只能在ViewModel中这样做。你应该尝试获取TextProperty的绑定,然后获取绑定并执行UpdateSource(); - 123 456 789 0
哦,还有一件事,在你的依赖属性中将“PropertyMetadata”更改为“FrameworkPropertyMetadata”。 - 123 456 789 0
为什么要将其更改为“FrameworkPropertyMetadata”?此外,您能否提供答案 - 听起来您可能可以提供解决方案? - MoonKnight
"RaisePropertyChanged("Text");" 正在改变 DP 的值,我不明白为什么会有可疑之处? - MoonKnight
显示剩余5条评论
5个回答

64

创建一个Behavior类,该类将附加到TextChanged事件,并将连接到绑定到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 必须被定义为

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; }
    }

这应该为您提供文本并将其推回到ViewModel。


2
当你从源代码更新时,它不起作用。也就是说,如果我设置Test = "XYZ";,视图将不会更新...如果我输入一些内容,更新Text是可以的。 - MoonKnight
1
看起来不错,但是会出现错误,提示需要一个对象引用来访问 AssiatedObject(它不能在静态上下文中访问)。 - Vaccano
5
这个只需要微小的修改就能够运行。在最后一段代码中...editor.CaretOffset = editor.Document.TextLength < caretOffset? editor.Document.TextLength : caretOffset;这会确保光标不会超出边界。 - Marc Vitalis
4
AvalonEdit不是开源的吗?为什么没有人提交一个pull request来使基础控件可绑定? - MgSam
1
私有静态无返回值 PropertyChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs eventArgs) { var behavior = obj as AvalonEditBehaviour; if (behavior.AssociatedObject != null) { var editor = behavior.AssociatedObject as TextEditor; if (editor.Document != null && editor.Document.Text != (string)eventArgs.NewValue) { var caretOffset = editor.CaretOffset; editor.Document.Text = eventArgs.NewValue != null ? eventArgs.NewValue.ToString() : string.Empty; editor.CaretOffset = editor.Document.TextLength < caretOffset ? editor.Document.TextLength : caretOffset; } } } - Tommy
显示剩余19条评论

12

创建一个BindableAvalonEditor类,其Text属性具有双向绑定。

通过将Jonathan Perry的答案123 456 789 0的答案结合起来,我成功地在最新版本的AvalonEdit中建立了双向绑定。这样可以直接实现双向绑定,无需使用行为。

以下是源代码...

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;
}

这个好像不起作用 - 文本绑定没有更新源,我不知道为什么。 - Tom Charles Zhang

5
我不喜欢这些解决方案。作者没有在Text上创建依赖属性是出于性能原因。通过创建附加属性来解决问题意味着每次按键都必须重新创建文本字符串。在一个100MB的文件上,这可能会成为严重的性能问题。内部只使用文档缓冲区,在请求时才会创建完整的字符串。
它公开了另一个属性Document,它是一个依赖属性,并将Text属性公开以仅在需要时构建字符串。虽然可以将其绑定,但这意味着设计您的ViewModel围绕UI元素,这违背了具有ViewModel UI不可知性的目的。我也不喜欢那个选项。
老实说,最干净(ish)的解决方案是在ViewModel中创建两个事件,一个用于显示文本,另一个用于更新文本。然后在代码后台编写一个一行的事件处理程序,这很好,因为它纯粹与UI相关。这样,只有真正需要时才构造和分配完整的文档字符串。此外,您甚至不需要存储(也不需要更新)ViewModel中的文本。只需在需要时引发DisplayScript和UpdateScript即可。
这不是理想的解决方案,但比我见过的任何其他方法都少一些缺点。
TextBox也面临类似的问题,它通过内部使用DeferredReference对象来解决问题,只有在真正需要时才构建字符串。该类是内部的,不可供公众使用,并且Binding代码被硬编码以特殊方式处理DeferredReference。不幸的是,似乎没有任何方法可以像TextBox那样解决该问题--除非TextEditor继承自TextBox。

你不想在ViewModel中存储文本。因此,文本存储的唯一位置是View中的TextEditor?View如何请求文本?通过命令ModelView调用包含文本作为参数的DisplayScript事件?假设我已更改了TextEditor中的文本。现在我希望ViewModel对其进行某些操作,例如将其保存到文件中。我会调用一个方法来触发ViewModel调用UpdateScript事件吗?该事件是否具有引用参数,在其中View将设置TextReader的文本? - dutop
1
视图不会请求文本。当加载文件时,ViewModel设置文本。如果ViewModel想要保存到文件,则会从UI请求文本。对于事件,我有一个类作为参数类型,其中包含2个字段:与TextEditor相关联的ViewModel(或者如果有多个编辑器,则是任何引用对象),以及用于读取或设置文本的Text属性。在ViewModel上调用触发UI事件的方法没有意义。这实际上非常简单。ViewModel知道何时需要加载或保存。 - Etienne Charland

4

对于那些想了解使用AvalonEdit实现MVVM的人,这里是其中一种方法,首先我们有这个类:

/// <summary>
/// Class that inherits from the AvalonEdit TextEditor control to 
/// enable MVVM interaction. 
/// </summary>
public class CodeEditor : TextEditor, INotifyPropertyChanged
{
    // Vars.
    private static bool canScroll = true;

    /// <summary>
    /// Default constructor to set up event handlers.
    /// </summary>
    public CodeEditor()
    {
        // Default options.
        FontSize = 12;
        FontFamily = new FontFamily("Consolas");
        Options = new TextEditorOptions
        {
            IndentationSize = 3,
            ConvertTabsToSpaces = true
        };
    }

    #region Text.
    /// <summary>
    /// Dependancy property for the editor text property binding.
    /// </summary>
    public static readonly DependencyProperty TextProperty =
         DependencyProperty.Register("Text", typeof(string), typeof(CodeEditor),
         new PropertyMetadata((obj, args) =>
         {
             CodeEditor target = (CodeEditor)obj;
             target.Text = (string)args.NewValue;
         }));

    /// <summary>
    /// Provide access to the Text.
    /// </summary>
    public new string Text
    {
        get { return base.Text; }
        set { base.Text = value; }
    }

    /// <summary>
    /// Return the current text length.
    /// </summary>
    public int Length
    {
        get { return base.Text.Length; }
    }

    /// <summary>
    /// Override of OnTextChanged event.
    /// </summary>
    protected override void OnTextChanged(EventArgs e)
    {
        RaisePropertyChanged("Length");
        base.OnTextChanged(e);
    }

    /// <summary>
    /// Event handler to update properties based upon the selection changed event.
    /// </summary>
    void TextArea_SelectionChanged(object sender, EventArgs e)
    {
        this.SelectionStart = SelectionStart;
        this.SelectionLength = SelectionLength;
    }

    /// <summary>
    /// Event that handles when the caret changes.
    /// </summary>
    void TextArea_CaretPositionChanged(object sender, EventArgs e)
    {
        try
        {
            canScroll = false;
            this.TextLocation = TextLocation;
        }
        finally
        {
            canScroll = true;
        }
    }
    #endregion // Text.

    #region Caret Offset.
    /// <summary>
    /// DependencyProperty for the TextEditorCaretOffset binding. 
    /// </summary>
    public static DependencyProperty CaretOffsetProperty =
        DependencyProperty.Register("CaretOffset", typeof(int), typeof(CodeEditor),
        new PropertyMetadata((obj, args) =>
        {
            CodeEditor target = (CodeEditor)obj;
            if (target.CaretOffset != (int)args.NewValue)
                target.CaretOffset = (int)args.NewValue;
        }));

    /// <summary>
    /// Access to the SelectionStart property.
    /// </summary>
    public new int CaretOffset
    {
        get { return base.CaretOffset; }
        set { SetValue(CaretOffsetProperty, value); }
    }
    #endregion // Caret Offset.

    #region Selection.
    /// <summary>
    /// DependencyProperty for the TextLocation. Setting this value 
    /// will scroll the TextEditor to the desired TextLocation.
    /// </summary>
    public static readonly DependencyProperty TextLocationProperty =
         DependencyProperty.Register("TextLocation", typeof(TextLocation), typeof(CodeEditor),
         new PropertyMetadata((obj, args) =>
         {
             CodeEditor target = (CodeEditor)obj;
             TextLocation loc = (TextLocation)args.NewValue;
             if (canScroll)
                 target.ScrollTo(loc.Line, loc.Column);
         }));

    /// <summary>
    /// Get or set the TextLocation. Setting will scroll to that location.
    /// </summary>
    public TextLocation TextLocation
    {
        get { return base.Document.GetLocation(SelectionStart); }
        set { SetValue(TextLocationProperty, value); }
    }

    /// <summary>
    /// DependencyProperty for the TextEditor SelectionLength property. 
    /// </summary>
    public static readonly DependencyProperty SelectionLengthProperty =
         DependencyProperty.Register("SelectionLength", typeof(int), typeof(CodeEditor),
         new PropertyMetadata((obj, args) =>
         {
             CodeEditor target = (CodeEditor)obj;
             if (target.SelectionLength != (int)args.NewValue)
             {
                 target.SelectionLength = (int)args.NewValue;
                 target.Select(target.SelectionStart, (int)args.NewValue);
             }
         }));

    /// <summary>
    /// Access to the SelectionLength property.
    /// </summary>
    public new int SelectionLength
    {
        get { return base.SelectionLength; }
        set { SetValue(SelectionLengthProperty, value); }
    }

    /// <summary>
    /// DependencyProperty for the TextEditor SelectionStart property. 
    /// </summary>
    public static readonly DependencyProperty SelectionStartProperty =
         DependencyProperty.Register("SelectionStart", typeof(int), typeof(CodeEditor),
         new PropertyMetadata((obj, args) =>
         {
             CodeEditor target = (CodeEditor)obj;
             if (target.SelectionStart != (int)args.NewValue)
             {
                 target.SelectionStart = (int)args.NewValue;
                 target.Select((int)args.NewValue, target.SelectionLength);
             }
         }));

    /// <summary>
    /// Access to the SelectionStart property.
    /// </summary>
    public new int SelectionStart
    {
        get { return base.SelectionStart; }
        set { SetValue(SelectionStartProperty, value); }
    }
    #endregion // Selection.

    #region Properties.
    /// <summary>
    /// The currently loaded file name. This is bound to the ViewModel 
    /// consuming the editor control.
    /// </summary>
    public string FilePath
    {
        get { return (string)GetValue(FilePathProperty); }
        set { SetValue(FilePathProperty, value); }
    }

    // Using a DependencyProperty as the backing store for FilePath. 
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty FilePathProperty =
         DependencyProperty.Register("FilePath", typeof(string), typeof(CodeEditor),
         new PropertyMetadata(String.Empty, OnFilePathChanged));
    #endregion // Properties.

    #region Raise Property Changed.
    /// <summary>
    /// Implement the INotifyPropertyChanged event handler.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
    public void RaisePropertyChanged([CallerMemberName] string caller = null)
    {
        var handler = PropertyChanged;
        if (handler != null)
            PropertyChanged(this, new PropertyChangedEventArgs(caller));
    }
    #endregion // Raise Property Changed.
}

然后在您想要使用AvalonEdit的视图中,您可以执行以下操作:

...
<Grid>
    <Local:CodeEditor 
        x:Name="CodeEditor" 
        FilePath="{Binding FilePath, 
            Mode=TwoWay, 
            NotifyOnSourceUpdated=True, 
            NotifyOnTargetUpdated=True}"
        WordWrap="{Binding WordWrap, 
            Mode=TwoWay, 
            NotifyOnSourceUpdated=True, 
            NotifyOnTargetUpdated=True}"
        ShowLineNumbers="{Binding ShowLineNumbers, 
            Mode=TwoWay, 
            NotifyOnSourceUpdated=True, 
            NotifyOnTargetUpdated=True}"
        SelectionLength="{Binding SelectionLength, 
            Mode=TwoWay, 
            NotifyOnSourceUpdated=True, 
            NotifyOnTargetUpdated=True}" 
        SelectionStart="{Binding SelectionStart, 
            Mode=TwoWay, 
            NotifyOnSourceUpdated=True, 
            NotifyOnTargetUpdated=True}"
        TextLocation="{Binding TextLocation, 
            Mode=TwoWay,
            NotifyOnSourceUpdated=True, 
            NotifyOnTargetUpdated=True}"/>
</Grid>

这可以放置在UserControl、Window或其他地方,然后在此视图的ViewModel中,我们有以下代码(我正在使用Caliburn Micro作为MVVM框架):

    public string FilePath
    {
        get { return filePath; }
        set
        {
            if (filePath == value)
                return;
            filePath = value;
            NotifyOfPropertyChange(() => FilePath);
        }
    }

    /// <summary>
    /// Should wrap?
    /// </summary>
    public bool WordWrap
    {
        get { return wordWrap; }
        set
        {
            if (wordWrap == value)
                return;
            wordWrap = value;
            NotifyOfPropertyChange(() => WordWrap);
        }
    }

    /// <summary>
    /// Display line numbers?
    /// </summary>
    public bool ShowLineNumbers
    {
        get { return showLineNumbers; }
        set
        {
            if (showLineNumbers == value)
                return;
            showLineNumbers = value;
            NotifyOfPropertyChange(() => ShowLineNumbers);
        }
    }

    /// <summary>
    /// Hold the start of the currently selected text.
    /// </summary>
    private int selectionStart = 0;
    public int SelectionStart
    {
        get { return selectionStart; }
        set
        {
            selectionStart = value;
            NotifyOfPropertyChange(() => SelectionStart);
        }
    }

    /// <summary>
    /// Hold the selection length of the currently selected text.
    /// </summary>
    private int selectionLength = 0;
    public int SelectionLength
    {
        get { return selectionLength; }
        set
        {
            selectionLength = value;
            UpdateStatusBar();
            NotifyOfPropertyChange(() => SelectionLength);
        }
    }

    /// <summary>
    /// Gets or sets the TextLocation of the current editor control. If the 
    /// user is setting this value it will scroll the TextLocation into view.
    /// </summary>
    private TextLocation textLocation = new TextLocation(0, 0);
    public TextLocation TextLocation
    {
        get { return textLocation; }
        set
        {
            textLocation = value;
            UpdateStatusBar();
            NotifyOfPropertyChange(() => TextLocation);
        }
    }

就是这样!完成。

希望这可以帮到你。


编辑。对于所有寻找使用MVVM处理AvalonEdit示例的人,你可以从http://1drv.ms/1E5nhCJ下载一个非常基本的编辑器应用程序。

注释。这个应用程序实际上通过继承AvalonEdit标准控件并根据需要添加附加依赖属性来创建一个MVVM友好的编辑器控件——*这与我在上面给出的答案不同*。然而,在解决方案中,我还展示了如何使用附加属性来完成此操作(如我在上面的答案中所述),并且在Behaviors命名空间下有代码。但实际上实现的是以上的第一种方法。

还请注意,解决方案中有一些未使用的代码。这个*样例*是一个较大应用程序的简化版本,我留下了一些代码,因为它可能对下载此示例编辑器的用户有用。除了上述之外,我在示例代码中访问Text通过绑定到document,有些人可能会认为这不是纯粹的MVVM,我说“好吧,但它有效”。有时候,与这种模式斗争并不是正确的方法。

希望这对你们中的一些人有用。


如何获取与状态栏标签相关联的插入符位置? - savi
我有两个问题,一个是光标插入符问题,当您运行程序并打开.seg文件时,它会抛出异常。第二个问题是当我打开多个文件并且想要打印当前活动的选项卡/文件时,如何实现PrintCommand?这是您可以下载代码的链接https://bitbucket.org/sahanatambi/avaloneditor/overview。感谢您的耐心等待。 - savi
你好,我已经查看了你的代码,说实话,如果让我从头开始提供一个如何完成这个任务的示例,会更快一些,所以我已经做到了。你不需要使用MVVM框架,但这是我所做的,我还包括了MahApps Metro,因为它看起来很酷。现在,请不要担心“Bootstrap”等,你只需要关注“MvvmTextEditor”以及它如何被整合到我的漂亮的VS2012风格的选项卡控件中。我基本上是从活动编辑器控件链接到主窗口(“Shell”),以便我们获得漂亮和一致的UI更新... - MoonKnight
我还没有看过你的打印问题,因为这是一个完全不同的问题,你最好提出一个新问题来询问。如果你提出问题,你可以在这里链接它,我可能会帮你看一下。首先,我想恭喜你迄今为止在MVVM方面所做的努力,从零开始做这个(没有MVVM框架)并不容易,但长期来看会让你变得更好。当我刚开始时,我也是从零开始创建应用程序而没有使用框架。如果我现在能提供任何建议,那就是不要过度复杂化你的继承层次结构... - MoonKnight
如果您需要让一个编辑器控件继承另一个控件,那也可以,但我很少看到这种需求。在大多数情况下,基于MvvmTextEditor(您将会明白我的意思)创建一个具有特定行为的新控件更加清晰。项目链接为http://1drv.ms/1E5nhCJ,希望对您有用。请下载后告诉我,我会将其删除...希望能帮到您。祝好运。 - MoonKnight
显示剩余2条评论

4
另一种不错的面向对象编程方法是下载 AvalonEdit 源代码(它是开源的),并创建一个继承 TextEditor 类(AvalonEdit 的主编辑器)的新类。
你需要做的基本上是重写 Text 属性,并使用依赖属性实现一个 INotifyPropertyChanged 版本的 Text 属性,在文本更改时引发 OnPropertyChanged 事件(可以通过重写 OnTextChanged() 方法来完成这个操作)。
以下是一个快速的代码示例(完全可用),适用于我:
public class BindableTextEditor : TextEditor, INotifyPropertyChanged
{
    /// <summary>
    /// A bindable Text property
    /// </summary>
    public new string Text
    {
        get { return base.Text; }
        set { base.Text = value; }
    }

    /// <summary>
    /// The bindable text property dependency property
    /// </summary>
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(BindableTextEditor), new PropertyMetadata((obj, args) =>
    {
        var target = (BindableTextEditor)obj;
        target.Text = (string)args.NewValue;
    }));

    protected override void OnTextChanged(EventArgs e)
    {
        RaisePropertyChanged("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;
}

4
这对于回传到绑定属性的编辑无效。(我使用了以下绑定:Text="{Binding CurrentText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" - Vaccano
1
然而,它在能够将文本设置为值并使其进入AvalonEdit方面表现出色。 - Vaccano
3
@Vaccano:您是如何获得AvalonEditor文本的双向绑定的?我能够查看文本,但是当我更新ViewModel时,它并没有得到更新。你有什么想法吗? - savi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接