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

时间:2015-08-30 20:02:07

标签: c# .net wpf xaml mvvm

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

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

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

我想使用MarkupExtension将事件绑定到EventArgs 的公共方法。这种可能性由this blog postViewModel提供。

XAML中的示例用法:

EventBindingExtension

ViewModel具有以下方法:

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

关于这种方法,我有几个问题:

  1. 我测试了它,它对我来说就像一个魅力,但由于我不是专家,我想问这个解决方案是否有一些陷阱或可能出现意外行为。
  2. 这是否符合MVVM模式?
  3. public void OnClick(object sender, EventArgs e) { MessageBox.Show("Hello world!"); } 需要绑定的公共方法来匹配事件的参数。如何扩展以允许省略EventBindingExtension参数?
  4. WPF或NuGet包的框架中还有哪些与此类似的MarkupExtensions?

3 个答案:

答案 0 :(得分:3)

以下是我对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 :(得分:1)

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

2)这可能是一个主观话题,但我个人认为虽然它不是对MVVM的技术违规,但它并不符合整体哲学。 WPF,尤其是MVVM,旨在实现数据驱动;绑定到一个方法有点回到旧的事件驱动的做事方式(至少对我来说)。在任何情况下,虽然绑定到方法可能仍然有资格作为MVVM,至少在技术上,传递UI对象作为发送者肯定不会!

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

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

答案 2 :(得分:0)

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

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

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

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