17

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?

Elmar Peise
  • 14,014
  • 3
  • 21
  • 40
Trident D'Gao
  • 18,973
  • 19
  • 95
  • 159
  • Don't put tags in the question's title. See [this link](http://meta.stackexchange.com/questions/10647/how-do-i-write-a-good-title) on writing good titles ;) – Louis Kottmann Nov 26 '12 at 13:48

2 Answers2

40

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.

Sean Chase
  • 1,139
  • 1
  • 15
  • 23
  • 6
    Great!! This avoid subclassing and allow using commands as eventhandlers from code-behind – isra60 Apr 21 '15 at 10:02
  • Nice and simple solution if you don't want to use external (non MS) libraries at your project. – Shakra Dec 19 '16 at 08:38
  • `public void ShowDialog(object sender, ShowDialogEventArgs e) { }` My solution does not seem to understant the `ShowDialogEventArgs`. How should I implement it? – KMarto Aug 07 '17 at 08:39
25

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

Louis Kottmann
  • 16,268
  • 4
  • 64
  • 88
  • I think it's just very flexible out of the box, which leads to writing various extensions to support things we'd think are basic. – Louis Kottmann Nov 26 '12 at 13:50
  • Just tried your code, well even though I got the idea the code itself doesn't work. (is it copy/paste from a better place?) Basically CommandName is never assigned and thus ResolveCommand returns null and the whole thing doesn't work because of that. There are also some minor problems that things like base.WritePostscript(); are not defined in a base class, but it's not important – Trident D'Gao Nov 26 '12 at 14:28
  • Ok, please modify your code with the floowing snippet that is proven to work: http://pastie.org/5437478, http://pastie.org/5437483 Thank you again and have a great day! – Trident D'Gao Nov 26 '12 at 14:42
  • @bonomo I had only tried it in WPF, glad to see you could port it to silverlight. – Louis Kottmann Nov 26 '12 at 14:46
  • @bonomo I had messed up the code-behind, I fixed it now. Also, I tried your pasties and they work fine as well. Please note that my original ResolveCommand() method was taken from the official Interactivity library. – Louis Kottmann Nov 26 '12 at 14:50
  • @bonomo I updated my answer to reflect your findings, and added some concerns. – Louis Kottmann Nov 26 '12 at 14:54