How to use Interaction Triggers to handle user-initiated events in WPF / MVVM

Example scenario: User clicks the mouse in a WPF application – how do we ‘listen’ to that event in order to trigger and handle an event in the main code? A possible solution is to use in WPF MVVM.

See this post to learn how to do this using MvvmLight:

https://www.technical-recipes.com/2017/handling-mouse-events-in-wpf-mvvm-using-mvvmlight-event-triggers/

Here is an example WPF implementation created in Visual Studio 2015.

Step 1: Create a WPF application

interactivity1

Step 2: Add the System.Windows.Interactivity reference

UPDATE

For more recent versions of .NET see this StackOverflow post on how to upgrade from older versions of Interactivity to the latest NuGet package:

https://stackoverflow.com/questions/8360209/how-to-add-system-windows-interactivity-to-project/56240223#56240223

If you need to migrate from an older version, then do these steps:

1. Remove reference to “Microsoft.Expression.Interactions” and “System.Windows.Interactivity”
2. Install the Microsoft.Xaml.Behaviors.Wpf NuGet package.
3. XAML files – replace the xmlns namespaces http://schemas.microsoft.com/expression/2010/interactivity and http://schemas.microsoft.com/expression/2010/interactions with http://schemas.microsoft.com/xaml/behaviors
4. C# files – replace the usings in c# files “Microsoft.Xaml.Interactivity” and “Microsoft.Xaml.Interactions” with “Microsoft.Xaml.Behaviors”

[Older version]

Right click the project folder and select ‘Add Reference’. Select System.Windows.Interactivity reference:

interactivity2

And add this reference to the MainWindow.xaml:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

So that the MainWindow.xaml looks something like this:

<Window x:Class="Interactivity.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:local="clr-namespace:Interactivity"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        
    </Grid>
</Window>

As mentioned in the update, replace usages of

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

with

xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

Step 3: Add the event handling infrastructure

Add the following classes to you project: EventArgs.cs, EventRaiser.cs, RelayCommand.cs

EventArgs.cs

using System;

namespace Interactivity
{
   public class EventArgs<T> : EventArgs
   {
      public EventArgs(T value)
      {
         Value = value;
      }

      public T Value { get; private set; }
   }
}

EventRaiser.cs

using System;

namespace Interactivity
{
   public static class EventRaiser
   {
      public static void Raise(this EventHandler handler, object sender)
      {
         if (handler != null)
         {
            handler(sender, EventArgs.Empty);
         }
      }

      public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, T value)
      {
         if (handler != null)
         {
            handler(sender, new EventArgs<T>(value));
         }
      }

      public static void Raise<T>(this EventHandler<T> handler, object sender, T value) where T : EventArgs
      {
         if (handler != null)
         {
            handler(sender, value);
         }
      }

      public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, EventArgs<T> value)
      {
         if (handler != null)
         {
            handler(sender, value);
         }
      }
   }
}

RelayCommand.cs

using System;
using System.Windows.Input;

namespace Interactivity
{
   public class RelayCommand<T> : ICommand
   {
      private readonly Predicate<T> _canExecute;
      private readonly Action<T> _execute;

      public RelayCommand(Action<T> execute)
         : this(execute, null)
      {
         _execute = execute;
      }

      public RelayCommand(Action<T> execute, Predicate<T> canExecute)
      {
         if (execute == null)
         {
            throw new ArgumentNullException("execute");
         }
         _execute = execute;
         _canExecute = canExecute;
      }

      public bool CanExecute(object parameter)
      {
         return _canExecute == null || _canExecute((T)parameter);
      }

      public void Execute(object parameter)
      {
         _execute((T)parameter);
      }

      public event EventHandler CanExecuteChanged
      {
         add { CommandManager.RequerySuggested += value; }
         remove { CommandManager.RequerySuggested -= value; }
      }
   }

   public class RelayCommand : ICommand
   {
      private readonly Predicate<object> _canExecute;
      private readonly Action<object> _execute;

      public RelayCommand(Action<object> execute)
         : this(execute, null)
      {
         _execute = execute;
      }

      public RelayCommand(Action<object> execute, Predicate<object> canExecute)
      {
         if (execute == null)
         {
            throw new ArgumentNullException("execute");
         }
         _execute = execute;
         _canExecute = canExecute;
      }

      public bool CanExecute(object parameter)
      {
         return _canExecute == null || _canExecute(parameter);
      }

      public void Execute(object parameter)
      {
         _execute(parameter);
      }

      // Ensures WPF commanding infrastructure asks all RelayCommand objects whether their
      // associated views should be enabled whenever a command is invoked 
      public event EventHandler CanExecuteChanged
      {
         add
         {
            CommandManager.RequerySuggested += value;
            CanExecuteChangedInternal += value;
         }
         remove
         {
            CommandManager.RequerySuggested -= value;
            CanExecuteChangedInternal -= value;
         }
      }

      private event EventHandler CanExecuteChangedInternal;

      public void RaiseCanExecuteChanged()
      {
         CanExecuteChangedInternal.Raise(this);
      }
   }
}

Step 4: Set the MainWindow.xaml DataContext and Event Triggers

MainWindow.xaml

<Window x:Class="Interactivity.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:local="clr-namespace:Interactivity"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseLeftButtonDown" >
            <i:InvokeCommandAction Command="{Binding LeftMouseButtonDown}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    
    <Grid>
       
    </Grid>
</Window>

Step 5: Create a ViewModel class for the Main Window

Add the class MainWindowViewModel.cs to your project.

Our MainWindowViewModel class will be used to bind the ‘left mouse button down’ EventName command defined in the MainWindow.xaml.

MainWindowViewModel.cs

using System;
using System.Windows;
using System.Windows.Input;

namespace Interactivity
{
   public class MainWindowViewModel
   {
      private ICommand _leftButtonDownCommand;

      public ICommand LeftMouseButtonDown
      {
         get
         {
            return _leftButtonDownCommand ?? (_leftButtonDownCommand = new RelayCommand(
               x =>
               {
                  DoStuff();
               }));
         }
      }

      private static void DoStuff()
      {
         MessageBox.Show("Responding to left mouse button click event...");
      }
   }
}

So that when we run the empty WPF application and left click anywhere on the window, the event gets handled, as evidenced by the message box that gets invoked:

interactivity3