Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I pass the event argument to a command using triggers?

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?

like image 676
Trident D'Gao Avatar asked Nov 26 '12 13:11

Trident D'Gao


2 Answers

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.

like image 108
Sean Chase Avatar answered Nov 11 '22 16:11

Sean Chase


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).

like image 23
Louis Kottmann Avatar answered Nov 11 '22 15:11

Louis Kottmann