问题
So I have a simple setup, an autocompletebox with its Populating event that I want to bind to a command. I use
clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity
(is there a better namespace for doing this?)
It's not a big deal to bind it, the big deal is to pass that PopulatingEventArgs argument to the bound command.
So how do I do it according to the best practices of PRISM in particular and MVVM in general?
回答1:
There is no built-in way, so here is how I do it:
The classic Interaction trigger is used like this:
<Button Content="I am a button">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding CommandWithNoArgs}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
We can't access the EventArgs
of the MouseEnter
event through binding, so we're going to have to modify the piece that tosses it away.
As it happens, that piece is the InvokeCommandAction
.
"So we're just going to subclass it and override a convenient method that was waiting for us all along" is what I'd have liked to write. But the class is sealed.
So we're going to have to subclass its parent (abstract) class: TriggerAction<DependencyObject>
The most basic implementation is:
public class InteractiveCommand : TriggerAction<DependencyObject>
{
protected override void Invoke(object parameter)
{
}
}
And that parameter
is your EventArgs
!
But hold on, it's not that simple, we have to reproduce the behavior of a regular InvokeCommandAction
.
Through Reflector, I decompiled it (but you could go look at the official source, I'm just lazy).
We're not going to care about the CommandParameter
dependency property, we're going to assume that if you use this instead of InvokeCommandAction
, you actually want the EventArgs
every time.
Here goes the full class (WPF only, see EDIT for SilverLight):
public class InteractiveCommand : TriggerAction<DependencyObject>
{
protected override void Invoke(object parameter)
{
if (base.AssociatedObject != null)
{
ICommand command = this.ResolveCommand();
if ((command != null) && command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
}
private ICommand ResolveCommand()
{
ICommand command = null;
if (this.Command != null)
{
return this.Command;
}
if (base.AssociatedObject != null)
{
foreach (PropertyInfo info in base.AssociatedObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (typeof(ICommand).IsAssignableFrom(info.PropertyType) && string.Equals(info.Name, this.CommandName, StringComparison.Ordinal))
{
command = (ICommand)info.GetValue(base.AssociatedObject, null);
}
}
}
return command;
}
private string commandName;
public string CommandName
{
get
{
base.ReadPreamble();
return this.commandName;
}
set
{
if (this.CommandName != value)
{
base.WritePreamble();
this.commandName = value;
base.WritePostscript();
}
}
}
#region Command
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
// Using a DependencyProperty as the backing store for Command. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(InteractiveCommand), new UIPropertyMetadata(null));
#endregion
}
As with any code you fetch from the internet, I highly recommand reading through the whole class, and trying to understand what it does. Don't just toss it into your app.
Now we can do:
<Button Content="I am a button">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<local:InteractiveCommand Command="{Binding CommandWithEventArgs}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
And the code behind:
#region CommandWithEventArgs
DelegateCommand<MouseEventArgs> _CommandWithEventArgs;
/// <summary>
/// Exposes <see cref="CommandWithEventArgs(MouseEventArgs)"/>.
/// </summary>
public DelegateCommand<MouseEventArgs> CommandWithEventArgs
{
get { return _CommandWithEventArgs ?? (_CommandWithEventArgs = new DelegateCommand<MouseEventArgs>(CommandWithEventArgs)); }
}
#endregion
public void CommandWithEventArgs(MouseEventArgs param)
{
}
And that's a wrap ;)
EDIT: For SilverLight, use this code instead:
public class InteractiveCommand : TriggerAction<DependencyObject>
{
protected override void Invoke(object parameter)
{
if (base.AssociatedObject != null)
{
ICommand command = Command;
if ((command != null) && command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
}
#region Command
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
// Using a DependencyProperty as the backing store for Command. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(InteractiveCommand), new UIPropertyMetadata(null));
#endregion
}
But please note that it is less safe than using the full fledged WPF version (doesn't check types, can crash with frozen elements).
回答2:
I tried the InteractiveCommand and it caused problems for me. Instead I set a reference to Microsoft.Expression.Interactions and included
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
<i:Interaction.Triggers>
<i:EventTrigger EventName="AppointmentEditing">
<ei:CallMethodAction MethodName="AppointmentEditing" TargetObject="{Binding}" />
</i:EventTrigger>
<i:EventTrigger EventName="ShowDialog">
<ei:CallMethodAction MethodName="ShowDialog" TargetObject="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
...in my UserControl.
Then put the event handlers in my ViewModel and set the scope to public:
public void ShowDialog(object sender, ShowDialogEventArgs e) {
}
public void AppointmentEditing(object sender, AppointmentEditingEventArgs e) {
}
Working well so far.
来源:https://stackoverflow.com/questions/13565484/how-can-i-pass-the-event-argument-to-a-command-using-triggers