繁体   English   中英

将事件绑定到没有ICommand的ViewModel的方法

[英]Binding Event to a Method of the ViewModel without ICommand

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

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

这种解决方案存在过于冗长的缺陷,因此使代码难以重构和维护。

我想使用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?

这是我对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)ICommand的一个好处是,通过简单地相应地修改绑定,您可以更轻松地在应用程序周围路由命令。 通过直接绑定到处理程序,您将失去此功能,并且必须自己实现它。 这可能不是您特定情况下的问题,但无论如何都是不必要的层。

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)一般问题的一点,我经常使用的唯一一个是Translate for localization

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

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

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

实现这一点时,您将考虑如何封装共享功能 - 您将拥有另一个Command接口。

暂无
暂无

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

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