MVVM:如何对控件进行函数调用

本文关键字:函数调用 控件 MVVM | 更新日期: 2023-09-27 18:37:12

在 XAML 中,我有一个带有 x:MyTextBox名称的文本框。

<TextBox x:Name="MyTextBox">Some text</TextBox>

出于速度原因,我想调用该方法.AppendText,例如在C#代码中,我会调用MyTextBox.AppendText("...")

但是,这不太像 MVVM。如果我想使用绑定到 ViewModel 来调用控件上的函数,实现此目的的优雅方法是什么?

我正在使用 MVVM Light。

更新

如果我想要一个简单,快速的解决方案,我会使用@XAML情人的答案。此答案使用混合行为,该行为较少使用C#编码。

如果我想编写一个可重用的依赖属性,我将使用@Chris Eelmaa的答案,我可以在将来应用于任何文本框。此示例基于依赖项属性,该属性虽然稍微复杂一些,但一旦编写,它就非常强大且可重用。当它插入本机类型时,使用它的 XAML 也略少。

MVVM:如何对控件进行函数调用

基本上,当您从控件调用方法时,很明显您正在执行一些与 UI 相关的逻辑。这不应该坐在ViewModel中。但在一些特殊情况下,我建议创建一种行为。创建一个行为并定义一个类型为操作<字符串>的依赖属性>因为 AppendText 应该将字符串作为参数。

public class AppendTextBehavior : Behavior<TextBlock>
{
    public Action<string> AppendTextAction
    {
        get { return (Action<string>)GetValue(AppendTextActionProperty); }
        set { SetValue(AppendTextActionProperty, value); }
    }
    // Using a DependencyProperty as the backing store for AppendTextAction.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty AppendTextActionProperty =
        DependencyProperty.Register("AppendTextAction", typeof(Action<string>), typeof(AppendTextBehavior), new PropertyMetadata(null));
    protected override void OnAttached()
    {
        SetCurrentValue(AppendTextActionProperty, (Action<string>)AssociatedObject.AppendText);
        base.OnAttached();
    }
}

在 OnAttached 方法中,我已将我在 TextBlock 上创建的扩展方法分配给行为 DP。现在,我们可以将此行为附加到视图中的文本块。

    <TextBlock Text="Original String"
               VerticalAlignment="Top">
        <i:Interaction.Behaviors>
            <wpfApplication1:AppendTextBehavior AppendTextAction="{Binding AppendTextAction, Mode=OneWayToSource}" />
        </i:Interaction.Behaviors>
    </TextBlock>

假设我们在 ViewModel 中有一个具有相同签名的属性。该属性是此绑定的源。然后我们可以随时调用该操作,它将自动调用我们在 TextBlock 上的扩展方法。在这里,我在单击按钮时调用该方法。请记住,在这种情况下,我们的行为就像视图和视图模型之间的适配器。

public class ViewModel
{
    public Action<string> AppendTextAction { get; set; }
    public ICommand ClickCommand { get; set; }
    public ViewModel()
    {
        ClickCommand = new DelegateCommand(OnClick);
    }
    private void OnClick()
    {
        AppendTextAction.Invoke(" test");
    }
}
对我来说

似乎是一个合理的要求。 AppendText绝对非常快,因为它处理指针。MVVM 世界中几乎每个答案都是子类化或附加属性。

您可以创建新接口,将其称为ITextBuffer

public interface ITextBuffer
{
    void Delete();
    void Delete(int offset, int length);
    void Append(string content);
    void Append(string content, int offset);
    string GetCurrentValue();
    event EventHandler<string> BufferAppendedHandler;
}
internal class MyTextBuffer : ITextBuffer
{
    #region Implementation of ITextBuffer
    private readonly StringBuilder _buffer = new StringBuilder();
    public void Delete()
    {
        _buffer.Clear();
    }
    public void Delete(int offset, int length)
    {
        _buffer.Remove(offset, length);
    }
    public void Append(string content)
    {
        _buffer.Append(content);
        var @event = BufferAppendedHandler;
        if (@event != null)
            @event(this, content);
    }
    public void Append(string content, int offset)
    {
        if (offset == _buffer.Length)
        {
            _buffer.Append(content);
        }
        else
        {
            _buffer.Insert(offset, content);
        }
    }
    public string GetCurrentValue()
    {
        return _buffer.ToString();
    }
    public event EventHandler<string> BufferAppendedHandler;
    #endregion
}

这将在整个视图模型中使用。您现在要做的就是在执行绑定时编写一个附加属性,该属性会提前执行此类接口。

像这样:

public sealed class MvvmTextBox
{
    public static readonly DependencyProperty BufferProperty =
        DependencyProperty.RegisterAttached(
            "Buffer",
            typeof (ITextBuffer),
            typeof (MvvmTextBox),
            new UIPropertyMetadata(null, PropertyChangedCallback)
        );
    private static void PropertyChangedCallback(
        DependencyObject dependencyObject,
        DependencyPropertyChangedEventArgs depPropChangedEvArgs)
    {
        // todo: unrelease old buffer.
        var textBox = (TextBox) dependencyObject;
        var textBuffer = (ITextBuffer) depPropChangedEvArgs.NewValue;
        var detectChanges = true;
        textBox.Text = textBuffer.GetCurrentValue();
        textBuffer.BufferAppendedHandler += (sender, appendedText) =>
        {
            detectChanges = false;
            textBox.AppendText(appendedText);
            detectChanges = true;
        };
        // todo unrelease event handlers.
        textBox.TextChanged += (sender, args) =>
        {
            if (!detectChanges)
                return;
            foreach (var change in args.Changes)
            {
                if (change.AddedLength > 0)
                {
                    var addedContent = textBox.Text.Substring(
                        change.Offset, change.AddedLength);
                    textBuffer.Append(addedContent, change.Offset);
                }
                else
                {
                    textBuffer.Delete(change.Offset, change.RemovedLength);
                }
            }
            Debug.WriteLine(textBuffer.GetCurrentValue());
        };
    }
    public static void SetBuffer(UIElement element, Boolean value)
    {
        element.SetValue(BufferProperty, value);
    }
    public static ITextBuffer GetBuffer(UIElement element)
    {
        return (ITextBuffer)element.GetValue(BufferProperty);
    }
}

这里的想法是将StringBuilder包装到接口中(因为它默认情况下不会引发任何事件:)然后可以通过附加属性和TextBox实际实现来利用。

在您的视图模型中,您可能需要这样的东西:

public class MyViewModel
{
    public ITextBuffer Description { get; set; }
    public MyViewModel()
    {
        Description= new MyTextBuffer();
        Description.Append("Just testing out.");
    }
}

并在以下观点中:

<TextBox wpfApplication2:MvvmTextBox.Buffer="{Binding Description}" />