【问题标题】:MVVM Passing EventArgs As Command ParameterMVVM 将 EventArgs 作为命令参数传递
【发布时间】:2011-09-06 12:31:55
【问题描述】:

我正在使用 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 类 Here

最后:
使用命令绑定效果很好,并显示了 MessageBox


我的问题:
导航事件发生时如何将NavigationEventArgs作为命令参数传递?

【问题讨论】:

    标签: wpf browser expression-blend eventargs


    【解决方案1】:

    我知道这有点晚了,但是微软已经将他们的 Xaml.Behaviors 开源了,现在只需一个命名空间就可以更容易地使用交互性。

    1. 首先将 Microsoft.Xaml.Behaviors.Wpf Nuget 包添加到您的项目中。
      https://www.nuget.org/packages/Microsoft.Xaml.Behaviors.Wpf/
    2. 将 xmlns:behaviours="http://schemas.microsoft.com/xaml/behaviors" 命名空间添加到您的 xml.

    那就这样用吧,

    <Button Width="150" Style="{DynamicResource MaterialDesignRaisedDarkButton}">
       <behaviours:Interaction.Triggers>
           <behaviours:EventTrigger EventName="Click">
               <behaviours:InvokeCommandAction Command="{Binding OpenCommand}" PassEventArgsToCommand="True"/>
           </behaviours:EventTrigger>
        </behaviours:Interaction.Triggers>
        Open
    </Button>
    

    PassEventArgsToCommand="True" 应设置为 True,并且您实现的 RelayCommand 可以将 RoutedEventArgs 或对象作为模板。如果您使用 object 作为参数类型,只需将其转换为适当的事件类型。

    命令看起来像这样,

    OpenCommand = new RelayCommand<object>(OnOpenClicked, (o) => { return true; });
    

    命令方法看起来像这样,

    private void OnOpenClicked(object parameter)
    {
        Logger.Info(parameter?.GetType().Name);
    }
    

    “参数”将是路由事件对象。

    还有日志以防你好奇,

    2020-12-15 11:40:36.3600|INFO|MyApplication.ViewModels.MainWindowViewModel|RoutedEventArgs

    如您所见,记录的 TypeName 是 RoutedEventArgs

    RelayCommand 的实现可以在这里找到。

    Why RelayCommand

    PS:您可以绑定到任何控件的任何事件。像Window的Closing事件一样,你会得到相应的事件。

    【讨论】:

      【解决方案2】:

      如果未设置CommandParameter,Prism 的InvokeCommandAction 将默认传递事件参数。

      https://docs.microsoft.com/en-us/previous-versions/msp-n-p/gg405494(v=pandp.40)#passing-eventargs-parameters-to-the-command

      这是一个例子。注意使用prism:InvokeCommandAction 而不是i:InvokeCommandAction

      <i:Interaction.Triggers>
          <i:EventTrigger EventName="Sorting">
              <prism:InvokeCommandAction Command="{Binding SortingCommand}"/>
          </i:EventTrigger>
      </i:Interaction.Triggers>
      

      视图模型

          private DelegateCommand<EventArgs> _sortingCommand;
      
          public DelegateCommand<EventArgs> SortingCommand => _sortingCommand ?? (_sortingCommand = new DelegateCommand<EventArgs>(OnSortingCommand));
      
          private void OnSortingCommand(EventArgs obj)
          {
              //do stuff
          }
      

      Prismlibrary documentation 有一个新版本。

      【讨论】:

      • 这似乎是最简单的解决方案。可能并不总是这样。
      【解决方案3】:

      不容易支持。这是an article,其中包含有关如何将 EventArgs 作为命令参数传递的说明。

      您可能想考虑使用MVVMLight - 它直接在命令中支持 EventArgs;你的情况应该是这样的:

       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Navigated">
              <cmd:EventToCommand Command="{Binding NavigatedEvent}"
                  PassEventArgsToCommand="True" />
          </i:EventTrigger>
       </i:Interaction.Triggers>
      

      【讨论】:

      • 那有没有直接的方法?因为我讨厌使用总是有错误的模板..等等,所以我喜欢从头开始编码
      • @Ahmed Adel:这是一个相当有趣的说法。
      • 真的,只需使用 MVVM Light。它要简单得多,您实际上只需要使用 RelayCommand 和 EventToCommand 类。
      • Silverlight/WPF 通常不是一件容易的事,是吗?
      • 什么是“cmd”命名空间?
      【解决方案4】:

      作为对@Mike Fuchs 答案的改编,这里有一个更小的解决方案。我正在使用Fody.AutoDependencyPropertyMarker 来减少一些样板。

      班级

      public class EventCommand : TriggerAction<DependencyObject>
      {
          [AutoDependencyProperty]
          public ICommand Command { get; set; }
      
          protected override void Invoke(object parameter)
          {
              if (Command != null)
              {
                  if (Command.CanExecute(parameter))
                  {
                      Command.Execute(parameter);
                  }
              }
          }
      }
      

      EventArgs

      public class VisibleBoundsArgs : EventArgs
      {
          public Rect VisibleVounds { get; }
      
          public VisibleBoundsArgs(Rect visibleBounds)
          {
              VisibleVounds = visibleBounds;
          }
      }
      

      XAML

      <local:ZoomableImage>
         <i:Interaction.Triggers>
            <i:EventTrigger EventName="VisibleBoundsChanged" >
               <local:EventCommand Command="{Binding VisibleBoundsChanged}" />
            </i:EventTrigger>
         </i:Interaction.Triggers>
      </local:ZoomableImage>
      

      视图模型

      public ICommand VisibleBoundsChanged => _visibleBoundsChanged ??
                                              (_visibleBoundsChanged = new RelayCommand(obj => SetVisibleBounds(((VisibleBoundsArgs)obj).VisibleVounds)));
      

      【讨论】:

        【解决方案5】:

        我总是回到这里来寻找答案,所以我想做一个简短的简单的回答。

        有多种方法可以做到这一点:

        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>
        

        代码实现委托:Source

        您必须为此获取 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 Package

        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
        }
        

        【讨论】:

        • 不,一点也不。我一直想使用 MVVM Light,但我没有使用它。这本身就可以正常工作。
        • @DavidNichols 第二个依赖于 MVVM Light。
        • 在这里使用选项 1 可以极大地简化生活。我不同意“使用 MVVMLight [是最困难但最好的做法”。如果它增加了额外的复杂性,并且 MS 已经包含了保持关注点分离的 MVVM 功能,那么如果您不必再添加 2 个包,为什么还要添加?
        • 选项1不向方法传递参数,该选项不完整还是不可能?
        【解决方案6】:

        这是@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 抽象泄漏到他们的视图模型程序集中。为了防止这种情况,我创建了一个代表我们关心的价值观的界面。接口可以存在于视图模型程序集中,私有实现在 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 的答案。

        【讨论】:

          【解决方案7】:

          补充一下 joshb 已经说过的内容 - 这对我来说很好用。确保在您的 xaml 中添加对 Microsoft.Expression.Interactions.dll 和 System.Windows.Interactivity.dll 的引用:

              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>
          

          【讨论】:

          • 当您想要的参数可以通过绑定访问(OP 想要 EventArgs)时,这非常有效,并且只需要 Interactivity 命名空间。明确指定CommandParameter 和元素的SelectedItem 之间的绑定对我来说很关键,因为我尝试只输入字符串“SelectedItem”,这当然不起作用。干杯!
          【解决方案8】:

          对于刚刚找到这篇文章的人来说,你应该知道在较新的版本中(不确定确切的版本,因为官方文档在这个主题上很薄弱)如果没有指定 CommandParameter,InvokeCommandAction 的默认行为是传递它作为 CommandParameter 附加到的事件的参数。所以原始海报的 XAML 可以简单地写成:

          <i:Interaction.Triggers>
            <i:EventTrigger EventName="Navigated">
              <i:InvokeCommandAction Command="{Binding NavigatedEvent}"/>
            </i:EventTrigger>
          </i:Interaction.Triggers>
          

          然后在您的命令中,您可以接受NavigationEventArgs 类型的参数(或任何合适的事件参数类型),它将自动提供。

          【讨论】:

          • 嘿,这样好像不行。嗯,那太容易了。 :)
          • 我将这种技术用于 Windows 10 UWP 应用程序,但不确定它在哪里工作。
          • 它适用于 Prism InvokeCommandAction msdn.microsoft.com/en-us/library/…
          • 这种行为你肯定需要 Prism。
          • 这可行,但在 InvokeCommandAction 中缺少 PassEventArgsToCommand="True"。添加使其工作
          【解决方案9】:

          我知道这是一个相当老的问题,但我今天遇到了同样的问题,并且对引用所有 MVVMLight 并不太感兴趣,只是为了可以将事件触发器与事件参数一起使用。我过去使用过 MVVMLight,它是一个很棒的框架,但我只是不想再将它用于我的项目。

          我为解决这个问题所做的是创建一个 ULTRA 最小、EXTREMELY 可适应的自定义触发操作,它允许我绑定到命令并提供事件参数转换器将参数传递给命令的 CanExecute 和 Execute 函数。您不想逐字传递事件参数,因为这会导致视图层类型被发送到视图模型层(这在 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"
          

          并创建一个IValueConverter(在本例中称为NameChangedArgsToStringConverter)来处理实际的转换逻辑。对于基本转换器,我通常会创建一个默认的 static readonly 转换器实例,然后我可以像上面所做的那样直接在 XAML 中引用它。

          此解决方案的好处是您实际上只需要向任何项目添加一个类即可使用交互框架,就像使用 InvokeCommandAction 一样。添加单个类(大约 75 行)应该比整个库更可取以实现相同的结果。

          注意

          这有点类似于@adabyron 的答案,但它使用事件触发器而不是行为。该解决方案还提供了事件参数转换能力,并不是@adabyron 的解决方案也无法做到这一点。我真的没有任何充分的理由为什么我更喜欢触发器而不是行为,这只是个人选择。 IMO 任何一种策略都是合理的选择。

          【讨论】:

          • 对我来说完美的解决方案。太棒了。
          【解决方案10】:

          借助 Blend for Visual Studio 2013 中的行为和操作,您可以使用 InvokeCommandAction。我尝试使用 Drop 事件进行此操作,虽然 XAML 中没有指定 CommandParameter,但令我惊讶的是,Execute Action 参数包含 DragEventArgs。我认为其他事件也会发生这种情况,但尚未对其进行测试。

          【讨论】:

          • 您能提供一个代码(XAML 和 VM)示例吗?如前所述,它对我不起作用(WPF,.NET 4.5)
          【解决方案11】:

          我尝试将我的依赖项保持在最低限度,所以我自己实现了这个,而不是使用 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);
                  }
              }
          }
          

          动作命令:

          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);
                  }
              }
          }
          

          【讨论】:

          • 一个可接受的样板代码级别,无需采用其他框架。对我也很有效,干杯!
          • 有趣的解决方案。我遇到的唯一问题是它将 UI 相关代码放在 ViewModel 中。 DragEventArgs 来自 System.Windows.Forms,ActionCommand 也可以说是与 UI 相关的。我倾向于让我的 ViewModel 在它们自己的程序集中完全分离,没有任何与 UI 相关的引用。它可以防止我不小心越过“线”。这是个人喜好,取决于每个开发人员希望遵守 MVVM 模式的严格程度。
          • Matthew,命令在 MVVM 模式中完全有效,并且属于 ViewModel。可以说 EventArgs 不属于那里,但如果你不喜欢它,你可能想对这个问题发表评论,而不是对它的解决方案。顺便说一句,DragEventArgs 位于 WPF 的 System.Windows 命名空间中。
          • @Matthew 我认为我们可以创建一个单独的项目并在那里添加 EventToCommandBehavior 和 ActionCOMmand 类。这样您就可以在需要的地方使用 SYStem.Windows,并避免引用承载行为的 System.Windows.Interactivity 命名空间。
          • @adabyron 你有没有做过多个事件?我可以在 xaml 中放置此行为的多个实例吗?
          【解决方案12】:

          我所做的是使用 InvokeCommandAction 将控件加载事件绑定到视图模型中的命令,在 Xaml 中给控件 ax:Name 并作为 CommandParameter 传递,然后在所述加载的命令钩子视图模型处理程序中直到事件我需要在哪里获取事件参数。

          【讨论】:

            【解决方案13】:

            我认为您不能使用 InvokeCommandAction 轻松做到这一点 - 我会看看来自 MVVMLight 或类似的 EventToCommand

            【讨论】: