问题:我想通过XAML将事件绑定到ViewModel
的公共方法。
臭名昭着的解决方案是在ICommand
上创建公开ViewModel
属性,该属性返回RelayCommand
或DelegateCommand
,然后使用EventTrigger
和{来自XAML中InvokeCommandAction
的{1}}将事件绑定到命令。非常相似的替代方法是使用MVVMLight的Windows.Interactivity
,它甚至可以将EventToCommand
作为Command的参数传递。
这种解决方案存在过于冗长的缺陷,因此使代码难以重构和维护。
我想使用MarkupExtension将事件绑定到EventArgs
的公共方法。这种可能性由this blog post的ViewModel
提供。
XAML中的示例用法:
EventBindingExtension
ViewModel具有以下方法:
<Button Content="Click me" Click="{my:EventBinding OnClick}" />
关于这种方法,我有几个问题:
public void OnClick(object sender, EventArgs e)
{
MessageBox.Show("Hello world!");
}
需要绑定的公共方法来匹配事件的参数。如何扩展以允许省略EventBindingExtension
参数?答案 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接口。