Categories: .Net , C# , WPF , XAML

As you may know, there is no double-click event for an Image in WPF. However, there are definitely cases where we might want one. But the question is, how do we pull this off while upholding a pure MVVM implementation?

Some considerations:

  • Consideration 1: We should use Commands instead of Events.
  • Consideration 2: In order to get 2 clicks, we will have to look at the MouseDown event, and look at it’s ClickCount since that is something an Image can handle.
  • Consideration 3: Since this is an Image , there is no inherent Command property. In this case, we will need to use a library that provides one.
  • To start, let’s create a separate class that implements ICommand . In this example all we are doing is firing a MessageBox on successful completion of the event. In a pure implementation a MessageBox would not be considered kosher, but we are just going to use it for our example so we know if it worked:

    using System; using System.Windows; using System.Windows.Input; namespace DoubleClickImageTest /// <summary> /// A test command. /// </summary> public class TestCommand : ICommand public virtual bool CanExecute(object parameter) return true; public virtual void Execute(object parameter) // We shouldn't put message boxes in commands normally, but we will for this test. MessageBox.Show("Hey, we just double-clicked our Image!", "Double-Click Test", MessageBoxButton.OK); public event EventHandler CanExecuteChanged add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; }

    Next, because this is MVVM we should create a View Model that will hold our command as a property:

    using System; namespace DoubleClickImageTest public class TestViewModel #region Properties /// <summary> /// Gets or sets my command. /// </summary> /// <value> /// My command. /// </value> public TestCommand MyCommand { get; set; } #endregion #region Constructors /// <summary> /// Initializes a new instance of the <see cref="TestViewModel"/> class. /// </summary> public TestViewModel() MyCommand = new TestCommand(); #endregion

    To finish setting up the View Model, we will add it to the code-behind of our View and set the Data Context of the View to the View Model instance.

    using System; using System.Windows; namespace DoubleClickImageTest /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window #region Members private TestViewModel _vm; #endregion #region Constructors /// <summary> /// Initializes a new instance of the <see cref="MainWindow"/> class. /// </summary> public MainWindow() // Get and set VM _vm = new TestViewModel(); this.DataContext = _vm; // Initialize UI InitializeComponent(); #endregion

    We have now satisfied the conditions of Consideration 1 .

    In order to satisfy Consideration 2 , we will need to do the following:

  • Write a class called CustomImage that extends System.Windows.Control.Image.
  • In CustomImage create a RoutedEvent and RoutedEventHandler to facilitate the double-click event.
  • In CustomImage override OnMouseLeftButtonDown to evaluate the click count.
  • First, we create the RoutedEvent :

    /// <summary> /// The mouse left button double click event /// </summary> public static readonly RoutedEvent MouseLeftButtonDoubleClick = EventManager.RegisterRoutedEvent( "MouseLeftButtonDoubleClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(CustomImage));

    Next we will create the RoutedEventHandler to execute MouseLeftButtonDoubleClick :

    /// <summary> /// Occurs when [mouse left button double click event handler]. /// </summary> public event RoutedEventHandler MouseLeftButtonDoubleClickEvent AddHandler(MouseLeftButtonDoubleClick, value); remove RemoveHandler(MouseLeftButtonDoubleClick, value);

    Lastly, we need to override the OnMouseLeftButtonDown event.

    So, why are we doing this? We know images have OnMouseLeftButtonDown , so we can use that to observe the click-count and call our event. In this case, we observe the ClickCount of our MouseButtonEventArgs . If we observe 2 clicks, we raise the MouseLeftButtonDoubleClick event:

    /// <summary> /// Invoked when an unhandled <see cref="E:System.Windows.UIElement.MouseLeftButtonDown" /> /// routed event is raised on this element. Implement this method to add class handling for /// this event. /// </summary> /// <param name="e">The <see cref="T:System.Windows.Input.MouseButtonEventArgs" /> that /// contains the event data. The event data reports that the left mouse button was pressed. /// </param> protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) // Double-click if (e.ClickCount == 2) RaiseEvent(new MouseLeftButtonDoubleClickEventArgs( MouseLeftButtonDoubleClick, this)); base.OnMouseLeftButtonDown(e); /// <summary> /// MouseLeftButtonDoubleClick EventArgs. /// </summary> public class MouseLeftButtonDoubleClickEventArgs : RoutedEventArgs /// <summary> /// Initializes a new instance of the <see cref="MouseLeftButtonDoubleClickEventArgs"/> /// class. /// </summary> /// <param name="routedEvent">The routed event identifier for this instance of the /// <see cref="T:System.Windows.RoutedEventArgs" /> class.</param> /// <param name="source">An alternate source that will be reported when the event is /// handled. This pre-populates the /// <see cref="P:System.Windows.RoutedEventArgs.Source" /> property.</param> public MouseLeftButtonDoubleClickEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source)

    So, in it’s entirety, the CustomImage class looks like this:

    using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace DoubleClickImageTest /// <summary> /// Custom image. /// </summary> public class CustomImage : Image /// <summary> /// The mouse left button double click event /// </summary> public static readonly RoutedEvent MouseLeftButtonDoubleClick = EventManager.RegisterRoutedEvent( "MouseLeftButtonDoubleClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(CustomImage)); /// <summary> /// Occurs when [mouse left button double click event handler]. /// </summary> public event RoutedEventHandler MouseLeftButtonDoubleClickEvent AddHandler(MouseLeftButtonDoubleClick, value); remove RemoveHandler(MouseLeftButtonDoubleClick, value); /// <summary> /// Invoked when an unhandled <see cref="E:System.Windows.UIElement.MouseLeftButtonDown" /> /// routed event is raised on this element. Implement this method to add class handling for /// this event. /// </summary> /// <param name="e">The <see cref="T:System.Windows.Input.MouseButtonEventArgs" /> that /// contains the event data. The event data reports that the left mouse button was pressed. /// </param> protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) // Double-click if (e.ClickCount == 2) RaiseEvent(new MouseLeftButtonDoubleClickEventArgs( MouseLeftButtonDoubleClick, this)); base.OnMouseLeftButtonDown(e); /// <summary> /// MouseLeftButtonDoubleClick EventArgs. /// </summary> public class MouseLeftButtonDoubleClickEventArgs : RoutedEventArgs /// <summary> /// Initializes a new instance of the <see cref="MouseLeftButtonDoubleClickEventArgs"/> /// class. /// </summary> /// <param name="routedEvent">The routed event identifier for this instance of the /// <see cref="T:System.Windows.RoutedEventArgs" /> class.</param> /// <param name="source">An alternate source that will be reported when the event is /// handled. This pre-populates the /// <see cref="P:System.Windows.RoutedEventArgs.Source" /> property.</param> public MouseLeftButtonDoubleClickEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source)

    For Consideration 3 we need to extend our image to allow Commanding, and to do that we will use the Interactivity Library from Expression Blend .

    Now, we will build our XAML. Let’s start by adding our CustomImage to a window:

    <Window x:Class="DoubleClickImageTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:DoubleClickImageTest" Title="MainWindow" Height="350" Width="525"> <local:CustomImage Width="200" Height="200" Source="/DoubleClickImageTest;component/cat_popcorn.jpg" HorizontalAlignment="Center" VerticalAlignment="Center"> </local:CustomImage> </Grid> </Window>

    It looks like this at runtime:

    So far, there is nothing overly significant about this implementation. Our CustomImage is behaving just like a normal image and it’s center-mass in our window.

    Now, let’s add our new MouseLeftButtonDoubleClick event.

    <Window x:Class="DoubleClickImageTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:DoubleClickImageTest" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" Title="MainWindow" Height="350" Width="525"> <local:CustomImage Width="200" Height="200" Source="/DoubleClickImageTest;component/cat_popcorn.jpg" HorizontalAlignment="Center" VerticalAlignment="Center"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonDoubleClickEvent"> <i:InvokeCommandAction Command="{Binding Path=MyCommand}" /> </i:EventTrigger> </i:Interaction.Triggers> </local:CustomImage> </Grid> </Window>

    You will notice we included the Interactivity Library in the header:

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

    Also, this new block inside CustomImage :

    <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonDoubleClickEvent"> <i:InvokeCommandAction Command="{Binding Path=MyCommand}" /> </i:EventTrigger> </i:Interaction.Triggers>

    This says: “whenever MouseLeftButtonDoubleClick is fired, invoke MyCommand “.

    Note: you will notice this works exactly the same as setting a command on a Button.

    So, now when we double-click our image, we see this:

    And just like that, we created our double-clickable image.

    Until next time…

    Comments
    Accessing Monitor Information with C#, Part 2: Getting a Monitor associated with a Window Handle Accessing Monitor Information with C#, Part 1: Getting Monitor Handles Trap when Alt+Tab is pressed in a WPF Application