MVVM将EventArgs作为命令parameter passing
我正在使用Microsoft Expression Blend 4
我有一个浏览器..,
[XAML] ConnectionView“空白代码”
<WebBrowser local:AttachedProperties.BrowserSource="{Binding Source}"> <i:Interaction.Triggers> <i:EventTrigger> <i:InvokeCommandAction Command="{Binding LoadedEvent}"/> </i:EventTrigger> <i:EventTrigger EventName="Navigated"> <i:InvokeCommandAction Command="{Binding NavigatedEvent}" CommandParameter="??????"/> </i:EventTrigger> </i:Interaction.Triggers> </WebBrowser>
[C#] AttachedProperties类
public static class AttachedProperties { public static readonly DependencyProperty BrowserSourceProperty = DependencyProperty . RegisterAttached ( "BrowserSource" , typeof ( string ) , typeof ( AttachedProperties ) , new UIPropertyMetadata ( null , BrowserSourcePropertyChanged ) ); public static string GetBrowserSource ( DependencyObject _DependencyObject ) { return ( string ) _DependencyObject . GetValue ( BrowserSourceProperty ); } public static void SetBrowserSource ( DependencyObject _DependencyObject , string Value ) { _DependencyObject . SetValue ( BrowserSourceProperty , Value ); } public static void BrowserSourcePropertyChanged ( DependencyObject _DependencyObject , DependencyPropertyChangedEventArgs _DependencyPropertyChangedEventArgs ) { WebBrowser _WebBrowser = _DependencyObject as WebBrowser; if ( _WebBrowser != null ) { string URL = _DependencyPropertyChangedEventArgs . NewValue as string; _WebBrowser . Source = URL != null ? new Uri ( URL ) : null; } } }
[C#] ConnectionViewModel类
public class ConnectionViewModel : ViewModelBase { public string Source { get { return Get<string> ( "Source" ); } set { Set ( "Source" , value ); } } public void Execute_ExitCommand ( ) { Application . Current . Shutdown ( ); } public void Execute_LoadedEvent ( ) { MessageBox . Show ( "___Execute_LoadedEvent___" ); Source = ...... ; } public void Execute_NavigatedEvent ( ) { MessageBox . Show ( "___Execute_NavigatedEvent___" ); } }
[C#] ViewModelBase类 在这里
最后:
与命令绑定效果很好,并显示MessageBoxes
我的问题 :
导航事件发生时如何将NavigationEventArgs作为命令parameter passing?
这不容易支持。 这里有一篇关于如何通过EventArgs作为命令参数的说明文章: http : //weblogs.asp.net/alexeyzakharov/archive/2010/03/24/silverlight-commands-hacks-passing-eventargs-as-commandparameter-to- delegatecommand触发按eventtrigger.aspx
您可能想要使用MVVMLight进行研究 – 它直接在命令中支持EventArgs; 你的情况看起来像这样:
<i:Interaction.Triggers> <i:EventTrigger EventName="Navigated"> <cmd:EventToCommand Command="{Binding NavigatedEvent}" PassEventArgsToCommand="True" /> </i:EventTrigger> </i:Interaction.Triggers>
我尽量保持我的依赖到最低限度,所以我自己实现,而不是使用MVVMLight的EventToCommand。 为我工作到目前为止,但欢迎反馈。
XAML:
<i:Interaction.Behaviors> <beh:EventToCommandBehavior Command="{Binding DropCommand}" Event="Drop" PassArguments="True" /> </i:Interaction.Behaviors>
视图模型:
public ActionCommand<DragEventArgs> DropCommand { get; private set; } this.DropCommand = new ActionCommand<DragEventArgs>(OnDrop); private void OnDrop(DragEventArgs e) { // ... }
EventToCommandBehavior:
/// <summary> /// Behavior that will connect an UI event to a viewmodel Command, /// allowing the event arguments to be passed as the CommandParameter. /// </summary> public class EventToCommandBehavior : Behavior<FrameworkElement> { private Delegate _handler; private EventInfo _oldEvent; // Event public string Event { get { return (string)GetValue(EventProperty); } set { SetValue(EventProperty, value); } } public static readonly DependencyProperty EventProperty = DependencyProperty.Register("Event", typeof(string), typeof(EventToCommandBehavior), new PropertyMetadata(null, OnEventChanged)); // Command public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } } public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommandBehavior), new PropertyMetadata(null)); // PassArguments (default: false) public bool PassArguments { get { return (bool)GetValue(PassArgumentsProperty); } set { SetValue(PassArgumentsProperty, value); } } public static readonly DependencyProperty PassArgumentsProperty = DependencyProperty.Register("PassArguments", typeof(bool), typeof(EventToCommandBehavior), new PropertyMetadata(false)); private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var beh = (EventToCommandBehavior)d; if (beh.AssociatedObject != null) // is not yet attached at initial load beh.AttachHandler((string)e.NewValue); } protected override void OnAttached() { AttachHandler(this.Event); // initial set } /// <summary> /// Attaches the handler to the event /// </summary> private void AttachHandler(string eventName) { // detach old event if (_oldEvent != null) _oldEvent.RemoveEventHandler(this.AssociatedObject, _handler); // attach new event if (!string.IsNullOrEmpty(eventName)) { EventInfo ei = this.AssociatedObject.GetType().GetEvent(eventName); if (ei != null) { MethodInfo mi = this.GetType().GetMethod("ExecuteCommand", BindingFlags.Instance | BindingFlags.NonPublic); _handler = Delegate.CreateDelegate(ei.EventHandlerType, this, mi); ei.AddEventHandler(this.AssociatedObject, _handler); _oldEvent = ei; // store to detach in case the Event property changes } else throw new ArgumentException(string.Format("The event '{0}' was not found on type '{1}'", eventName, this.AssociatedObject.GetType().Name)); } } /// <summary> /// Executes the Command /// </summary> private void ExecuteCommand(object sender, EventArgs e) { object parameter = this.PassArguments ? e : null; if (this.Command != null) { if (this.Command.CanExecute(parameter)) this.Command.Execute(parameter); } } }
ActionCommand:
public class ActionCommand<T> : ICommand { public event EventHandler CanExecuteChanged; private Action<T> _action; public ActionCommand(Action<T> action) { _action = action; } public bool CanExecute(object parameter) { return true; } public void Execute(object parameter) { if (_action != null) { var castParameter = (T)Convert.ChangeType(parameter, typeof(T)); _action(castParameter); } } }
我一直回到这里来寻求答案,所以我想做一个简单的答案。
有多种方法可以做到这一点:
1.使用WPF工具。 最容易的。
添加命名空间:
-
System.Windows.Interactivitiy
-
Microsoft.Expression.Interactions
XAML:
使用EventName
调用您想要的事件,然后在MethodName
指定您的Method
名称。
<Window> xmlns:wi="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"> <wi:Interaction.Triggers> <wi:EventTrigger EventName="SelectionChanged"> <ei:CallMethodAction TargetObject="{Binding}" MethodName="ShowCustomer"/> </wi:EventTrigger> </wi:Interaction.Triggers> </Window>
码:
public void ShowCustomer() { // Do something. }
2.使用MVVMLight。 最难的。
安装GalaSoft NuGet软件包。
获取命名空间:
-
System.Windows.Interactivity
-
GalaSoft.MvvmLight.Platform
XAML:
使用EventName
调用你想要的事件,然后在你的绑定中指定你的Command
名称。 如果要传递方法的参数,请将PassEventArgsToCommand
标记为true。
<Window> xmlns:wi="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:cmd="http://www.galasoft.ch/mvvmlight"> <wi:Interaction.Triggers> <wi:EventTrigger EventName="Navigated"> <cmd:EventToCommand Command="{Binding CommandNameHere}" PassEventArgsToCommand="True" /> </wi:EventTrigger> </wi:Interaction.Triggers> </Window>
代码实现代表: 源代码
您必须为此获得Prism MVVM NuGet包。
using Microsoft.Practices.Prism.Commands; // With params. public DelegateCommand<string> CommandOne { get; set; } // Without params. public DelegateCommand CommandTwo { get; set; } public MainWindow() { InitializeComponent(); // Must initialize the DelegateCommands here. CommandOne = new DelegateCommand<string>(executeCommandOne); CommandTwo = new DelegateCommand(executeCommandTwo); } private void executeCommandOne(string param) { // Do something here. } private void executeCommandTwo() { // Do something here. }
没有DelegateCommand
代码: Source
using GalaSoft.MvvmLight.CommandWpf public MainWindow() { InitializeComponent(); CommandOne = new RelayCommand<string>(executeCommandOne); CommandTwo = new RelayCommand(executeCommandTwo); } public RelayCommand<string> CommandOne { get; set; } public RelayCommand CommandTwo { get; set; } private void executeCommandOne(string param) { // Do something here. } private void executeCommandTwo() { // Do something here. }
3.使用Telerik EventToCommandBehavior 。 这是一个选项。
你将不得不下载它的NuGet包 。
XAML
:
<i:Interaction.Behaviors> <telerek:EventToCommandBehavior Command="{Binding DropCommand}" Event="Drop" PassArguments="True" /> </i:Interaction.Behaviors>
码:
public ActionCommand<DragEventArgs> DropCommand { get; private set; } this.DropCommand = new ActionCommand<DragEventArgs>(OnDrop); private void OnDrop(DragEventArgs e) { // Do Something }
我知道这是一个相当古老的问题,但我今天遇到了同样的问题,并没有太在意引用所有的MVVMLight,所以我可以使用事件参数的事件触发器。 我过去使用MVVMLight,这是一个很好的框架,但我不想再用它来做我的项目了。
我所做的解决这个问题的方法是创build一个ULTRA minimal, EXTREMELY自适应自定义触发器动作,它允许我绑定到该命令并提供一个事件参数转换器来将parameter passing给命令的CanExecute和Execute函数。 您不想逐字传递事件参数,因为这会导致视图图层types被发送到视图模型图层(MVVM中绝不会发生这种情况)。
这是我想出的EventCommandExecuter类:
public class EventCommandExecuter : TriggerAction<DependencyObject> { #region Constructors public EventCommandExecuter() : this(CultureInfo.CurrentCulture) { } public EventCommandExecuter(CultureInfo culture) { Culture = culture; } #endregion #region Properties #region Command public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } } public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventCommandExecuter), new PropertyMetadata(null)); #endregion #region EventArgsConverterParameter public object EventArgsConverterParameter { get { return (object)GetValue(EventArgsConverterParameterProperty); } set { SetValue(EventArgsConverterParameterProperty, value); } } public static readonly DependencyProperty EventArgsConverterParameterProperty = DependencyProperty.Register("EventArgsConverterParameter", typeof(object), typeof(EventCommandExecuter), new PropertyMetadata(null)); #endregion public IValueConverter EventArgsConverter { get; set; } public CultureInfo Culture { get; set; } #endregion protected override void Invoke(object parameter) { var cmd = Command; if (cmd != null) { var param = parameter; if (EventArgsConverter != null) { param = EventArgsConverter.Convert(parameter, typeof(object), EventArgsConverterParameter, CultureInfo.InvariantCulture); } if (cmd.CanExecute(param)) { cmd.Execute(param); } } } }
这个类有两个依赖属性,一个允许绑定到你的视图模型的命令,另一个允许你绑定事件的来源,如果你需要它在事件参数转换。 如果需要,也可以提供文化设置(默认为当前的UI文化)。
这个类允许你调整事件参数,以便它们可以被视图模型的命令逻辑所使用。 但是,如果您只想逐字传递事件参数,则不要指定事件参数转换器。
XAML中此触发器操作的最简单用法如下所示:
<i:Interaction.Triggers> <i:EventTrigger EventName="NameChanged"> <cmd:EventCommandExecuter Command="{Binding Path=Update, Mode=OneTime}" EventArgsConverter="{x:Static c:NameChangedArgsToStringConverter.Default}"/> </i:EventTrigger> </i:Interaction.Triggers>
如果您需要访问事件的来源,您将绑定到事件的所有者
<i:Interaction.Triggers> <i:EventTrigger EventName="NameChanged"> <cmd:EventCommandExecuter Command="{Binding Path=Update, Mode=OneTime}" EventArgsConverter="{x:Static c:NameChangedArgsToStringConverter.Default}" EventArgsConverterParameter="{Binding ElementName=SomeEventSource, Mode=OneTime}"/> </i:EventTrigger> </i:Interaction.Triggers>
(这假设你附加触发器的XAML节点已经被分配了x:Name="SomeEventSource"
这个XAML依赖于导入一些必需的命名空间
xmlns:cmd="clr-namespace:MyProject.WPF.Commands" xmlns:c="clr-namespace:MyProject.WPF.Converters" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
并创build一个IValueConverter
(在这种情况下称为NameChangedArgsToStringConverter
)来处理实际的转换逻辑。 对于基本的转换器,我通常创build一个默认的static readonly
转换器实例,然后我可以直接在XAML中引用,如上所述。
这个解决scheme的好处是,你只需要添加一个类到任何项目来使用交互框架,就像你用InvokeCommandAction
。 为了达到相同的结果,添加一个类(大约75行)应该比整个库更可取。
注意
这有点类似于@adabyron的答案,但它使用事件触发器而不是行为。 这个解决scheme还提供了一个事件参数转换的能力,而不是@ adabyron的解决scheme不能做到这一点。 为什么我更喜欢触发行为,只是个人select,我真的没有什么好的理由。 海事组织的任何一项战略都是合理的选
对于刚发现这篇文章的人来说,你应该知道,在新版本(不知道正式版本,因为官方文档对这个主题很苗条),InvokeCommandAction的默认行为,如果没有指定CommandParameter,就是传递参数事件作为CommandParameter附加。 所以原稿海报的XAML可以简单地写成:
<i:Interaction.Triggers> <i:EventTrigger EventName="Navigated"> <i:InvokeCommandAction Command="{Binding NavigatedEvent}"/> </i:EventTrigger> </i:Interaction.Triggers>
然后在你的命令中,你可以接受一个types为NavigationEventArgs
的参数(或者任何types的argstypes都是合适的)并自动提供。
我不认为你可以用InvokeCommandAction
轻松做到这InvokeCommandAction
– 我会看MVVMLight或类似的EventToCommand。
添加到什么已经表明 – 这对我来说工作得很好。 确保添加对Microsoft.Expression.Interactions.dll和System.Windows.Interactivity.dll的引用,并在您的xaml中执行:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
我最终使用这样的东西来满足我的需求。 这表明你也可以传递一个自定义参数:
<i:Interaction.Triggers> <i:EventTrigger EventName="SelectionChanged"> <i:InvokeCommandAction Command="{Binding Path=DataContext.RowSelectedItem, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}" /> </i:EventTrigger> </i:Interaction.Triggers>
通过Blend for Visual Studio 2013中的行为和操作,您可以使用InvokeCommandAction。 我尝试了Drop事件,虽然没有在XAML中指定CommandParameter,但令我惊讶的是,Execute Action参数包含了DragEventArgs。 我想这会发生在其他事件,但没有testing它们。
我所做的是使用InvokeCommandAction将控件加载的事件绑定到视图模型中的命令,给Xaml中的控制ax:Name并作为CommandParameter传递,然后在所述加载的命令钩子视图模型处理程序中,直到我需要的事件得到事件参数。
这是@ adabyron的答案,防止泄漏的EventArgs
抽象的版本。
首先,修改的EventToCommandBehavior
类(现在是一个通用的抽象类,并用ReSharper代码清理格式化)。 请注意新的GetCommandParameter
虚拟方法及其默认实现:
public abstract class EventToCommandBehavior<TEventArgs> : Behavior<FrameworkElement> where TEventArgs : EventArgs { public static readonly DependencyProperty EventProperty = DependencyProperty.Register("Event", typeof(string), typeof(EventToCommandBehavior<TEventArgs>), new PropertyMetadata(null, OnEventChanged)); public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommandBehavior<TEventArgs>), new PropertyMetadata(null)); public static readonly DependencyProperty PassArgumentsProperty = DependencyProperty.Register("PassArguments", typeof(bool), typeof(EventToCommandBehavior<TEventArgs>), new PropertyMetadata(false)); private Delegate _handler; private EventInfo _oldEvent; public string Event { get { return (string)GetValue(EventProperty); } set { SetValue(EventProperty, value); } } public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } } public bool PassArguments { get { return (bool)GetValue(PassArgumentsProperty); } set { SetValue(PassArgumentsProperty, value); } } protected override void OnAttached() { AttachHandler(Event); } protected virtual object GetCommandParameter(TEventArgs e) { return e; } private void AttachHandler(string eventName) { _oldEvent?.RemoveEventHandler(AssociatedObject, _handler); if (string.IsNullOrEmpty(eventName)) { return; } EventInfo eventInfo = AssociatedObject.GetType().GetEvent(eventName); if (eventInfo != null) { MethodInfo methodInfo = typeof(EventToCommandBehavior<TEventArgs>).GetMethod("ExecuteCommand", BindingFlags.Instance | BindingFlags.NonPublic); _handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, this, methodInfo); eventInfo.AddEventHandler(AssociatedObject, _handler); _oldEvent = eventInfo; } else { throw new ArgumentException($"The event '{eventName}' was not found on type '{AssociatedObject.GetType().FullName}'."); } } private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var behavior = (EventToCommandBehavior<TEventArgs>)d; if (behavior.AssociatedObject != null) { behavior.AttachHandler((string)e.NewValue); } } // ReSharper disable once UnusedMember.Local // ReSharper disable once UnusedParameter.Local private void ExecuteCommand(object sender, TEventArgs e) { object parameter = PassArguments ? GetCommandParameter(e) : null; if (Command?.CanExecute(parameter) == true) { Command.Execute(parameter); } } }
接下来,隐藏DragCompletedEventArgs
的示例派生类。 有些人表示担心把EventArgs
抽象泄漏到他们的视图模型组件中。 为了防止这种情况,我创build了一个表示我们关心的值的接口。 界面可以在UI组件中使用私有实现存在于视图模型组件中:
// UI assembly public class DragCompletedBehavior : EventToCommandBehavior<DragCompletedEventArgs> { protected override object GetCommandParameter(DragCompletedEventArgs e) { return new DragCompletedArgs(e); } private class DragCompletedArgs : IDragCompletedArgs { public DragCompletedArgs(DragCompletedEventArgs e) { Canceled = e.Canceled; HorizontalChange = e.HorizontalChange; VerticalChange = e.VerticalChange; } public bool Canceled { get; } public double HorizontalChange { get; } public double VerticalChange { get; } } } // View model assembly public interface IDragCompletedArgs { bool Canceled { get; } double HorizontalChange { get; } double VerticalChange { get; } }
将命令参数转换为IDragCompletedArgs
,类似于@adabyron的答案。