[英]Binding Event to a Method of the ViewModel without ICommand
问题:我想通过XAML将事件绑定到ViewModel
的公共方法。
臭名昭着的解决方案是在ViewModel
上创建一个公共ICommand
属性,该属性返回RelayCommand
或DelegateCommand
,然后在XAML中使用来自Windows.Interactivity
EventTrigger
和InvokeCommandAction
将事件绑定到命令。 非常相似的替代方法是使用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!");
}
关于这种方法,我有几个问题:
EventBindingExtension
需要绑定的公共方法来匹配事件的参数。 如何扩展以允许省略object source
参数? 这是我对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.