Categories:
.Net
,
C#
,
WPF
,
XAML
by Joshua Arzt
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…
Like this:
Like
Loading...
Related
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