将事件绑定到ViewModel的方法而不使用iccommand
本文关键字:iccommand 方法 事件 绑定 ViewModel | 更新日期: 2023-09-27 18:10:30
问题:我想通过XAML将事件绑定到ViewModel
的公共方法。
臭名昭著的解决方案是在ViewModel
上创建一个公共ICommand
属性,该属性返回RelayCommand
或DelegateCommand
,然后在XAML中使用Windows.Interactivity
中的EventTrigger
和InvokeCommandAction
将事件绑定到命令。非常类似的替代方法是使用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!");
}
我对这种方法有几个问题:
- 我已经测试过了,它对我来说很有魅力,但因为我不是专家,我想问一下这个解决方案是否有一些陷阱或可能意想不到的行为。
- 这符合MVVM模式吗?
-
EventBindingExtension
要求其绑定的公共方法匹配事件的参数。如何扩展它以允许省略object source
参数? - 在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) 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接口