将事件绑定到ViewModel的方法而不使用iccommand

本文关键字:iccommand 方法 事件 绑定 ViewModel | 更新日期: 2023-09-27 18:10:30

问题:我想通过XAML将事件绑定到ViewModel的公共方法。

臭名昭著的解决方案是在ViewModel上创建一个公共ICommand属性,该属性返回RelayCommandDelegateCommand,然后在XAML中使用Windows.Interactivity中的EventTriggerInvokeCommandAction将事件绑定到命令。非常类似的替代方法是使用MVVMLight的EventToCommand,它甚至提供了将EventArgs作为命令参数传递的可能性。

这个解决方案的缺点是过于冗长,因此使代码难以重构和维护。

我想使用MarkupExtension将事件直接绑定到ViewModel 的公共方法。这篇博文中的EventBindingExtension提供了这种可能性。

在XAML中的示例用法:

<Button Content="Click me" Click="{my:EventBinding OnClick}" />

其中ViewModel有以下方法:

public void OnClick(object sender, EventArgs e)
{
    MessageBox.Show("Hello world!");
}

我对这种方法有几个问题:

  1. 我已经测试过了,它对我来说很有魅力,但因为我不是专家,我想问一下这个解决方案是否有一些陷阱或可能意想不到的行为。
  2. 这符合MVVM模式吗?
  3. EventBindingExtension要求其绑定的公共方法匹配事件的参数。如何扩展它以允许省略object source参数?
  4. 在WPF或NuGet包的框架中还有其他类似的MarkupExtensions吗?

将事件绑定到ViewModel的方法而不使用iccommand

这是我的WPF事件方法绑定的完整功能实现:

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

1) iccommand的好处之一是,您可以通过简单地修改相应的绑定,更容易地在应用程序周围路由命令。通过直接绑定到处理程序,您将失去此功能,并且必须自己实现它。这在您的特定情况下可能不是问题,但无论如何,这是一个不必要的层。

2)这可能是一个有点主观的话题,但我个人认为,尽管它不是MVVM的技术违反,但它不符合整体哲学。WPF,尤其是MVVM,被设计成数据驱动的;绑定到一个方法有点像回到过去的事件驱动的做事方式(至少对我来说是这样)。在任何情况下,绑定到一个方法可能仍然符合MVVM,至少从技术上讲,传递一个UI对象作为发送者绝对不会!

3)你需要修改GetHandler函数来构造、编译并返回一个LINQ表达式或一个接受预期参数的IL委托,删除第一个并将其余的传递给绑定目标的方法。这应该足以让你开始:

static Delegate GetHandler(object dataContext, EventInfo eventInfo, string eventHandlerName)
{
    // get the vm handler we're binding to
    var eventParams = GetParameterTypes(eventInfo.EventHandlerType);
    var method = dataContext.GetType().GetMethod(eventHandlerName, eventParams.Skip(1).ToArray());
    if (method == null)
        return null;
    // construct an expression that calls it
    var instance = Expression.Constant(dataContext);
    var paramExpressions = eventParams.Select(p => Expression.Parameter(p)).ToArray();
    var call = Expression.Call(instance, method, paramExpressions.Skip(1));
    // wrap it in a lambda and compile it
    return Expression.Lambda(eventInfo.EventHandlerType, call, paramExpressions).Compile();
}
4)有点一般性的问题,我唯一经常使用的是翻译本地化。

ViewModel不应该有任何对视图控件的引用。

事件处理程序通过object sender提供此访问权限。

此外,命令允许您管理它是否可以执行,但简单的方法-不。要解决这个问题,你必须定义一个功能来管理控件的启用/禁用。

当你实现这个的时候,你会考虑如何封装共享的功能——你会有另一个Command接口