WPF 事件绑定到 ViewModel(对于非命令类)

本文关键字:于非 命令 事件 绑定 ViewModel WPF | 更新日期: 2023-09-27 18:00:00

我正在开发应用程序的第二个版本,作为重写的一部分,我必须迁移到MVVM架构。 我面临着将每一段代码都放在视图模型类中的压力 - 在代码隐藏文件中使用 c# 是不受欢迎的。 (我知道,我知道...我知道隐藏代码不是一件坏事,但这次不是我的电话(。

对于实现命令接口的对象,这很容易。 我已经能够找到大量有关如何将这些对象的命令绑定到视图模型中的 ICommand 的信息。 问题在于没有此接口的对象,例如

<ListBox
   x:Name="myListBox"
   MouseDoubleClick="myCallbackFunction">
<!-- ... -->
</ListBox>

我想知道如何将列表框的 MouseDoubleClick 事件绑定到 myCallbackFunction,该函数在视图模型中实现。 这可能吗?

谢谢!

WPF 事件绑定到 ViewModel(对于非命令类)

这不可能直接实现。 它可以通过附加属性或行为来完成,尽管找到和调用适当的方法仍然有点棘手(这可以通过反射相当容易地完成(。

话虽如此,这通常通过ICommand处理 - 例如,MVVM Light 具有出色的 EventToCommand 行为,可以将任何事件映射到 ViewModel 上的 ICommand。 使用 ICommand 的优点是仍然可以使用 DataBinding,因为 ICommand 是作为属性公开的。

WPF 从 .NET 4.5 开始支持事件的标记扩展。使用该功能,我实现了一个通用的方法绑定扩展,并在此处进行了介绍:

http://www.singulink.com/CodeIndex/post/building-the-ultimate-wpf-event-method-binding-extension

可用于使用完整属性路径语法绑定到方法,支持绑定和其他标记扩展作为参数,并自动路由到与提供的参数的签名匹配的方法。以下是一些使用示例:

<!--  Basic usage  -->
<Button Click="{data:MethodBinding OpenFromFile}" Content="Open" />
<!--  Pass in a binding as a method argument  -->
<Button Click="{data:MethodBinding Save, {Binding CurrentItem}}" Content="Save" />
<!--  Another example of a binding, but this time to a property on another element  -->
<ComboBox x:Name="ExistingItems" ItemsSource="{Binding ExistingItems}" />
<Button Click="{data:MethodBinding Edit, {Binding SelectedItem, ElementName=ExistingItems}}" />
<!--  Pass in a hard-coded method argument, XAML string automatically converted to the proper type  -->
<ToggleButton Checked="{data:MethodBinding SetWebServiceState, True}"
                Content="Web Service"
                Unchecked="{data:MethodBinding SetWebServiceState, False}" />
                
<!--  Pass in sender, and match method signature automatically -->
<Canvas PreviewMouseDown="{data:MethodBinding SetCurrentElement, {data:EventSender}, ThrowOnMethodMissing=False}">
    <controls:DesignerElementTypeA />
    <controls:DesignerElementTypeB />
    <controls:DesignerElementTypeC />
</Canvas>
    <!--  Pass in EventArgs  -->
<Canvas MouseDown="{data:MethodBinding StartDrawing, {data:EventArgs}}"
        MouseMove="{data:MethodBinding AddDrawingPoint, {data:EventArgs}}"
        MouseUp="{data:MethodBinding EndDrawing, {data:EventArgs}}" />
<!-- Support binding to methods further in a property path -->
<Button Content="SaveDocument" Click="{data:MethodBinding CurrentDocument.DocumentService.Save, {Binding CurrentDocument}}" />

查看模型方法签名:

public void OpenFromFile();
public void Save(DocumentModel model);
public void Edit(DocumentModel model);
public void SetWebServiceState(bool state);
public void SetCurrentElement(DesignerElementTypeA element);
public void SetCurrentElement(DesignerElementTypeB element);
public void SetCurrentElement(DesignerElementTypeC element);
public void StartDrawing(MouseEventArgs e);
public void AddDrawingPoint(MouseEventArgs e);
public void EndDrawing(MouseEventArgs e);
public class Document
{
    // Fetches the document service for handling this document
    public DocumentService DocumentService { get; }
}
public class DocumentService
{
    public void Save(Document document);
}

要直接回答您的问题,请参阅为什么要避免 WPF MVVM 模式中的代码隐藏?它建议你想要的两种可能的东西。

但是,为什么要将列表框的鼠标双击绑定到视图模型中的 ICommand?

另一种方法是在代码隐藏中编写一个方法来注册 MouseDoubleClick。由于以下事实,这还不错。

  1. 有意义的数据绑定是视图和视图模型之间的交互。例如,当用户向文本框输入一些文本时,视图模型也会更新。相反,如果视图模型从数据库中获取数据,它将显示在视图中。但是,视图模型中的 ICommand 并非这种情况会绑定到视图。

  2. 当然,ICommand 的 CanExcute 对您的视图模型很重要,但在许多情况下,它与视图模型无关或不相关。在这种情况下,ICommand 绑定和编写代码隐藏之间的区别在于 MouseDoubleClick 事件与 ICommand 绑定或向事件处理程序注册。

一种方法可能是处理代码隐藏中的事件,并从代码隐藏调用适当的视图模型方法

您还可以使用一些现成的命令库,例如本教程,它使用的是ACB

尝试 EventBinder,它将允许您将方法直接绑定到任何事件,包括您自己的事件,而无需将方法包装在 ICommand 容器中。

https://github.com/Serg046/EventBinderhttps://www.nuget.org/packages/EventBinder

支持 .NET Framework 3.0+

、.NET Core 3.0+ 和 Avalonia。

特征:

  • 绑定到没有 IComm 的方法
  • 绑定到具有返回类型的方法
  • 绑定到异步方法
  • 使用.分隔符、属性和字段绑定到嵌套对象受支持
  • 传递整数、双精度、十进制或字符串类型的用户参数
  • 使用符号和位置编号($0$1$等(
  • 将默认{Binding}作为参数传递

用法:

public class ViewModel
{
    public MetadataViewModel Metadata { get; } = new MetadataViewModel();
    public async Task ShowMessage(string msg, decimal centenary, double year)
    {
        await Task.Delay(0);
        MessageBox.Show(msg + centenary + year);
    }
    public class MetadataViewModel
    {
        public void ShowInfo(Window window, double windowWidth, ViewModel viewModel, object sender, MouseButtonEventArgs eventArgs)
        {
            var sb = new StringBuilder("Window width: ")
                .AppendLine(windowWidth.ToString())
                .Append("View model type: ").AppendLine(viewModel.GetType().Name)
                .Append("Sender type: ").AppendLine(sender.GetType().Name)
                .Append("Clicked button: ").AppendLine(eventArgs.ChangedButton.ToString())
                .Append("Mouse X: ").AppendLine(eventArgs.GetPosition(window).X.ToString())
                .Append("Mouse Y: ").AppendLine(eventArgs.GetPosition(window).Y.ToString());
            MessageBox.Show(sb.ToString());
        }
    }
}

捆绑:

<Window xmlns:e="clr-namespace:EventBinder;assembly=EventBinder" Name="Wnd">
    <Rectangle Fill="LightGray" Name="Rct"
        MouseLeftButtonDown="{e:EventBinding ShowMessage, `Happy `, 20m, 20.0 }"
        MouseRightButtonDown="{e:EventBinding Metadata.ShowInfo, {Binding ElementName=Wnd},
            {Binding ElementName=Wnd, Path=ActualWidth}, {Binding}, $0, $1 }" />
</Window>

EventBinding.Bind(Rct, nameof(Rct.MouseLeftButtonDown),
    nameof(ViewModel.ShowMessage),
    "`Happy `", 20m, 20.0);
EventBinding.Bind(Rct, nameof(Rct.MouseRightButtonDown),
    nameof(ViewModel.Metadata) + "." + nameof(ViewModel.Metadata.ShowInfo),
    new Binding { ElementName = nameof(Wnd)},
    new Binding("ActualWidth") { ElementName = nameof(Wnd) },
    new Binding(),
    "$0", "$1");