简体   繁体   English

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

[英]WPF Event Binding to ViewModel (for non-Command classes)

I'm working an the second version of an application, and as part of the rewrite I have to move to an MVVM architecture.我正在开发应用程序的第二个版本,作为重写的一部分,我必须迁移到 MVVM 架构。 I'm getting pressure to put absolutely every bit of code in the view model class--having c# in the code behind file is frowned upon.我面临着将所有代码都放在视图 model 类中的压力——在代码隐藏文件中包含 c# 是不受欢迎的。 (I know, I know...I understand that code behind isn't a bad thing, but it isn't my call this time). (我知道,我知道……我知道代码隐藏不是坏事,但这次不是我的决定)。

For objects which implement the command interface, it's easy.对于实现命令接口的对象,这很容易。 I've been able to find a ton of information on how to bind the Command of these objects to an ICommand in the view model. The problem is for objects which don't have this interface, eg我已经找到了大量关于如何将这些对象的命令绑定到视图 model 中的 ICommand 的信息。问题在于没有此接口的对象,例如

<ListBox
   x:Name="myListBox"
   MouseDoubleClick="myCallbackFunction">

<!-- ... -->

</ListBox>

I want to know how to bind the MouseDoubleClick event for the Listbox to myCallbackFunction, which is implemented in the view model. Is this even possible?我想知道如何将列表框的 MouseDoubleClick 事件绑定到视图 model 中实现的 myCallbackFunction。这甚至可能吗?

Thanks!谢谢!

This isn't directly possible.这不是直接可能的。 It could be done via an Attached Property or Behavior, though it would still be a little tricky to find and invoke the appropriate method (this could be done via Reflection fairly easily).它可以通过附加属性或行为来完成,尽管查找和调用适当的方法仍然有点棘手(这可以通过反射相当容易地完成)。

That being said, this is typically handled via ICommand - For example, MVVM Light has a great EventToCommand behavior to map any event to an ICommand on the ViewModel.话虽如此,这通常是通过ICommand处理的——例如,MVVM Light 对 map ViewModel 上的 ICommand 的任何事件都有很好的EventToCommand行为。 The advantage of using ICommand is that you can still use DataBinding, since the ICommand is exposed as a property.使用 ICommand 的优点是您仍然可以使用 DataBinding,因为 ICommand 是作为属性公开的。

WPF supports markup extensions on events as of .NET 4.5.从 .NET 4.5 开始,WPF 支持事件的标记扩展。 Using that capability, I implemented a versatile method binding extension and wrote about it here:使用该功能,我实现了一个通用的方法绑定扩展并在此处写下了它:

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

Can be used to bind to a method using full property path syntax, supports bindings and other markup extensions as arguments, and automatically routes to the method that matches the signature of the arguments provided.可用于绑定到使用完整属性路径语法的方法,支持绑定和其他标记扩展如 arguments,并自动路由到与所提供的 arguments 的签名匹配的方法。 Here are some usage examples:以下是一些使用示例:

<!--  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}}" />

View model method signatures:查看model方法签名:

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

To directly answer your question, please refer to Why to avoid the codebehind in WPF MVVM pattern?要直接回答您的问题,请参阅为什么要避免 WPF MVVM 模式中的代码隐藏? It suggests two possible things you want.它建议你想要的两种可能的东西。

However, why do you want to bind the MouseDoubleClick of the ListBox to your ICommand in the viewmodel?但是,为什么要将 ListBox 的 MouseDoubleClick 绑定到视图模型中的 ICommand?

The alternative way is that you write a method in a codebehind to register the MouseDoubleClick.另一种方法是在代码隐藏中编写一个方法来注册 MouseDoubleClick。 It is not bad due to the facts of the following.由于以下事实,这还不错。

  1. The meaningful databinding is the interaction between a view and a viewmodel.有意义的数据绑定是视图和视图模型之间的交互。 For example, when a user input some text to a TextBox, a viewmodel is also updated.例如,当用户向 TextBox 输入一些文本时,viewmodel 也会更新。 On the contrary, if a viewmodel gets data from a database, it will be shown at a view.相反,如果视图模型从数据库中获取数据,它将显示在视图中。 However, it is not this case that the ICommand in your viewmodel binds to the view.但是,在这种情况下,您的视图模型中的 ICommand 不会绑定到视图。

  2. Of course, the CanExcute of the ICommand would important to your viewmodel, but in many cases, it is not related with the viewmodel or not concerned.当然,ICommand 的 CanExcute 对你的 viewmodel 很重要,但在很多情况下,它与 viewmodel 无关或不关心。 In this case, the difference between the ICommand binding and the writing the codebehind is where the MouseDoubleClick event is binded with a ICommand or registered with a event handler.在这种情况下,ICommand 绑定和编写代码隐藏之间的区别在于 MouseDoubleClick 事件与 ICommand 绑定或向事件处理程序注册。

One way could be to handle the event in the code behind and call appropriate method of view model from code behind一种方法是在代码隐藏中处理事件并从代码隐藏中调用视图 model 的适当方法

You can also go for some ready made commanding library like this tutorial which is using ACB您也可以通过 go 获取一些现成的命令库,例如使用ACB教程

Try EventBinder which will allow you to bind your method directly to any event, including your own events, without a need to wrap the method in ICommand container.尝试 EventBinder,它允许您将方法直接绑定到任何事件,包括您自己的事件,而无需将方法包装在 ICommand 容器中。

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

.NET Framework 3.0 + and .NET Core 3.0 + are supported支持.NET Framework 3.0+和.NET Core 3.0+

Features:特征:

  • Binding to methods without ICommand绑定到没有 ICommand 的方法
  • Binding to methods with return types绑定到具有返回类型的方法
  • Binding to async methods绑定到异步方法
  • Binding to nested objects using .使用 绑定到嵌套对象. delimiter, properties and fields are supported支持分隔符、属性和字段
  • Passing user parameters of int, double, decimal or string type传递 int、double、decimal 或 string 类型的用户参数
  • Passing event parameters using $ sign and position number ( $0 , $1 , etc)使用$符号和 position 数字( $0$1等)传递事件参数
  • Passing default {Binding} as a parameter将默认{Binding}作为参数传递

Usage:用法:

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

Binding:捆绑:

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

or要么

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM