wpf MVVM 将 EventArgs 作为命令参数传递
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6205472/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
MVVM Passing EventArgs As Command Parameter
提问by Ahmed Ghoneim
I'm using Microsoft Expression Blend 4
I have a Browser ..,
我正在使用 Microsoft Expression Blend 4
我有一个浏览器 ..,
[ XAML ]ConnectionView " Empty Code Behind "
[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 class
[ 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 Class
[ 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 classHere
[ C# ] ViewModelBase 类在这里
Finally :
Binding with commands works well and MessageBoxes shown
最后:
使用命令绑定效果很好,并显示了 MessageBoxes
My Question :
How to pass NavigationEventArgsas Command Parameters when Navigated Event occurs ?
我的问题:
如何在发生导航事件时将NavigationEventArgs作为命令参数传递?
回答by E.Z. Hart
It's not easily supported. Here's an articlewith instructions on how to pass EventArgs as command parameters.
它不容易被支持。这是一篇文章,其中包含有关如何将 EventArgs 作为命令参数传递的说明。
You might want to look into using MVVMLight- it supports EventArgs in command directly; your situation would look something like this:
您可能想考虑使用MVVMLight- 它直接在命令中支持 EventArgs;你的情况看起来像这样:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Navigated">
<cmd:EventToCommand Command="{Binding NavigatedEvent}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
回答by Mike Fuchs
I try to keep my dependencies to a minimum, so I implemented this myself instead of going with EventToCommand of MVVMLight. Works for me so far, but feedback is welcome.
我尽量将我的依赖项保持在最低限度,所以我自己实现了这一点,而不是使用 MVVMLight 的 EventToCommand。到目前为止对我有用,但欢迎反馈。
Xaml:
Xml:
<i:Interaction.Behaviors>
<beh:EventToCommandBehavior Command="{Binding DropCommand}" Event="Drop" PassArguments="True" />
</i:Interaction.Behaviors>
ViewModel:
视图模型:
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);
}
}
}
回答by AzzamAziz
I've always come back here for the answer so I wanted to make a short simple one to go to.
我总是回到这里寻找答案,所以我想做一个简短的简单答案。
There are multiple ways of doing this:
有多种方法可以做到这一点:
1. Using WPF Tools. Easiest.
1. 使用 WPF 工具。最简单。
Add Namespaces:
添加命名空间:
System.Windows.Interactivitiy
Microsoft.Expression.Interactions
System.Windows.Interactivitiy
Microsoft.Expression.Interactions
XAML:
XAML:
Use the EventName
to call the event you want then specify your Method
name in the MethodName
.
使用EventName
来调用您想要的事件,然后Method
在MethodName
.
<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>
Code:
代码:
public void ShowCustomer()
{
// Do something.
}
2. Using MVVMLight. Most difficult.
2. 使用 MVVMLight。最难的。
Install GalaSoft NuGet package.
安装 GalaSoft NuGet 包。
Get the namespaces:
获取命名空间:
System.Windows.Interactivity
GalaSoft.MvvmLight.Platform
System.Windows.Interactivity
GalaSoft.MvvmLight.Platform
XAML:
XAML:
Use the EventName
to call the event you want then specify your Command
name in your binding. If you want to pass the arguments of the method, mark PassEventArgsToCommand
to true.
使用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>
Code Implementing Delegates: Source
代码实现委托:源代码
You must get the Prism MVVM NuGet package for this.
为此,您必须获得 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.
}
Code Without DelegateCommand
: Source
无代码DelegateCommand
:源代码
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. Using Telerik EventToCommandBehavior. It's an option.
3. 使用Telerik EventToCommandBehavior。这是一个选择。
You'll have to download it's NuGet Package.
您必须下载它的NuGet Package。
XAML
:
XAML
:
<i:Interaction.Behaviors>
<telerek:EventToCommandBehavior
Command="{Binding DropCommand}"
Event="Drop"
PassArguments="True" />
</i:Interaction.Behaviors>
Code:
代码:
public ActionCommand<DragEventArgs> DropCommand { get; private set; }
this.DropCommand = new ActionCommand<DragEventArgs>(OnDrop);
private void OnDrop(DragEventArgs e)
{
// Do Something
}
回答by joshb
For people just finding this post, you should know that in newer versions (not sure on the exact version since official docs are slim on this topic) the default behavior of the InvokeCommandAction, if no CommandParameter is specified, is to pass the args of the event it's attached to as the CommandParameter. So the originals poster's XAML could be simply written as:
对于刚刚找到这篇文章的人,您应该知道在较新的版本中(不确定确切版本,因为官方文档对此主题很薄)InvokeCommandAction 的默认行为(如果未指定 CommandParameter)是传递参数的参数它作为命令参数附加到的事件。所以原始海报的 XAML 可以简单地写成:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Navigated">
<i:InvokeCommandAction Command="{Binding NavigatedEvent}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Then in your command, you can accept a parameter of type NavigationEventArgs
(or whatever event args type is appropriate) and it will automatically be provided.
然后在您的命令中,您可以接受类型参数NavigationEventArgs
(或任何合适的事件参数类型),它将自动提供。
回答by pjs
I know this is a fairly old question, but I ran into the same problem today and wasn't too interested in referencing all of MVVMLight just so I can use event triggers with event args. I have used MVVMLight in the past and it's a great framework, but I just don't want to use it for my projects any more.
我知道这是一个相当古老的问题,但我今天遇到了同样的问题,并且对引用所有 MVVMLight 不太感兴趣,只是这样我就可以将事件触发器与事件参数一起使用。我过去曾使用过 MVVMLight,它是一个很棒的框架,但我只是不想再将它用于我的项目。
What I did to resolve this problem was create an ULTRAminimal, EXTREMELYadaptable custom trigger action that would allow me to bind to the command and provide an event args converter to pass on the args to the command's CanExecute and Execute functions. You don't want to pass the event args verbatim, as that would result in view layer types being sent to the view model layer (which should never happen in MVVM).
我做了什么来解决这个问题是创建一个ULTRA最小,EXTREMELY适应自定义触发动作,让我绑定到命令,并提供一个事件参数转换器在ARGS传递给此命令的CanExecute和执行功能。您不想逐字传递事件参数,因为这会导致视图层类型被发送到视图模型层(这在 MVVM 中永远不会发生)。
Here is the EventCommandExecuterclass I came up with:
这是我想出的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);
}
}
}
}
This class has two dependency properties, one to allow binding to your view model's command, the other allows you to bind the source of the event if you need it during event args conversion. You can also provide culture settings if you need to (they default to the current UI culture).
这个类有两个依赖属性,一个允许绑定到你的视图模型的命令,另一个允许你在事件参数转换期间绑定事件源。如果需要,您还可以提供文化设置(它们默认为当前的 UI 文化)。
This class allows you to adapt the event args so that they may be consumed by your view model's command logic. However, if you want to just pass the event args on verbatim, simply don't specify an event args converter.
此类允许您调整事件参数,以便它们可以被您的视图模型的命令逻辑使用。但是,如果您只想逐字传递事件参数,只需不要指定事件参数转换器。
The simplest usage of this trigger action in XAML is as follows:
此触发器操作在 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>
If you needed access to the source of the event, you would bind to the owner of the event
如果您需要访问事件的来源,您将绑定到事件的所有者
<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>
(this assumes that the XAML node you're attaching the triggers to has been assigned x:Name="SomeEventSource"
(这假设您将触发器附加到的 XAML 节点已分配 x:Name="SomeEventSource"
This XAML relies on importing some required namespaces
此 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"
and creating an IValueConverter
(called NameChangedArgsToStringConverter
in this case) to handle the actual conversion logic. For basic converters I usually create a default static readonly
converter instance, which I can then reference directly in XAML as I have done above.
并创建一个IValueConverter
(NameChangedArgsToStringConverter
在这种情况下称为)来处理实际的转换逻辑。对于基本转换器,我通常会创建一个默认static readonly
转换器实例,然后我可以像上面所做的那样直接在 XAML 中引用它。
The benefit of this solution is that you really only need to add a single class to any project to use the interaction framework much the same way that you would use it with InvokeCommandAction
. Adding a single class (of about 75 lines) should be much more preferable to an entire library to accomplish identical results.
这个解决方案的好处是你真的只需要向任何项目添加一个类就可以使用交互框架,就像使用InvokeCommandAction
. 添加单个类(大约 75 行)应该比整个库更可取以实现相同的结果。
NOTE
笔记
this is somewhat similar to the answer from @adabyron but it uses event triggers instead of behaviours. This solution also provides an event args conversion ability, not that @adabyron's solution could not do this as well. I really don't have any good reason why I prefer triggers to behaviours, just a personal choice. IMO either strategy is a reasonable choice.
这有点类似于@adabyron 的答案,但它使用事件触发器而不是行为。该解决方案还提供了事件参数转换能力,而不是@adabyron 的解决方案也无法做到这一点。我真的没有任何充分的理由为什么我更喜欢行为触发,这只是个人选择。IMO 任一策略都是合理的选择。
回答by EbbnFlow
To add to what joshb has stated already - this works just fine for me. Make sure to add references to Microsoft.Expression.Interactions.dll and System.Windows.Interactivity.dll and in your xaml do:
添加到 joshb 已经声明的内容 - 这对我来说很好用。确保添加对 Microsoft.Expression.Interactions.dll 和 System.Windows.Interactivity.dll 的引用,并在您的 xaml 中执行以下操作:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
I ended up using something like this for my needs. This shows that you can also pass a custom parameter:
我最终使用了这样的东西来满足我的需求。这表明您还可以传递自定义参数:
<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>
回答by Tim
I don't think you can do that easily with the InvokeCommandAction
- I would take a look at EventToCommand
from MVVMLight or similar.
我不认为你可以很容易地做到这一点InvokeCommandAction
- 我会EventToCommand
从 MVVMLight 或类似的地方看一看。
回答by datchung
Prism's InvokeCommandAction
will pass the event args by default if CommandParameter
is not set.
InvokeCommandAction
如果CommandParameter
未设置,Prism将默认传递事件参数。
Here is an example. Note the use of prism:InvokeCommandAction
instead of i:InvokeCommandAction
.
这是一个例子。注意使用prism:InvokeCommandAction
代替i:InvokeCommandAction
。
<i:Interaction.Triggers>
<i:EventTrigger EventName="Sorting">
<prism:InvokeCommandAction Command="{Binding SortingCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
The ViewModel
视图模型
private DelegateCommand<EventArgs> _sortingCommand;
public DelegateCommand<EventArgs> SortingCommand => _sortingCommand ?? (_sortingCommand = new DelegateCommand<EventArgs>(OnSortingCommand));
private void OnSortingCommand(EventArgs obj)
{
//do stuff
}
There is a new version of the Prismlibrary documentation.
Prismlibrary 文档有一个新版本。
回答by OzFrog
With Behaviors and Actions in Blend for Visual Studio 2013 you can use the InvokeCommandAction. I tried this with the Drop event and although no CommandParameter was specified in the XAML, to my surprise, the Execute Action parameter contained the DragEventArgs. I presume this would happen for other events but have not tested them.
通过 Blend for Visual Studio 2013 中的行为和操作,您可以使用 InvokeCommandAction。我在 Drop 事件中尝试了这个,虽然 XAML 中没有指定 CommandParameter,但令我惊讶的是,Execute Action 参数包含 DragEventArgs。我认为这会发生在其他事件中,但尚未对其进行测试。
回答by DRL
What I do is to use InvokeCommandAction to bind the control loaded event to a command in the view model, give the control a x:Name in Xaml and pass as CommandParameter, then in said loaded command hook view model handlers up to the events where I need to get the event args.
我所做的是使用 InvokeCommandAction 将控件加载事件绑定到视图模型中的命令,在 Xaml 中给控件 ax:Name 并作为 CommandParameter 传递,然后在所述加载的命令挂钩视图模型处理程序中直到我需要的事件获取事件参数。