WPF: Handling Both Click and DoubleClick Events
Introduction
If you try to distinguish between single and double clicks of a control in WPF by hooking up event handlers for both the PreviewMouseLeftButtonDown and MouseDoubleClick events like this, you will notice that the MouseDoubleClick event handler won't be invoked when you double-click on the control:
<Label PreviewMouseLeftButtonDown="Label_PreviewMouseLeftButtonDown"
MouseDoubleClick="Label_MouseDoubleClick" Content="Click Here" />
private void Label_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) =>
MessageBox.Show("Single Click!");
private void Label_MouseDoubleClick(object sender, MouseButtonEventArgs e) =>
MessageBox.Show("Double Click!");
This makes sense given that the second click of a double-click is by definition always preceded by a single click, and in this case the call to MessageBox.Show in the PreviewMouseLeftButtonDown event handler is blocking the dispatcher thread once the first click has been recorded.
DispatcherTimer
So how can you solve this if you want to be able to detect double-clicks but still handling single clicks as well? One solution would be to use a timer and wait for a certain amount of time to see if there is another click following the first one.
You could handle the PreviewMouseLeftButtonDown event and check the value of the ClickCount property of the MouseButtonEventArgs. If it equals to 2, you have detected a double-click and stop (reset) the timer. If the ClickCount is less than 2, you simply start the timer and wait for the Tick event to be raised. If the event is being raised before another PreviewMouseLeftButtonDown event, you have detected a single-click. Here is the sample code:
public partial class MainWindow : Window
{
private readonly System.Windows.Threading.DispatcherTimer _timer
= new System.Windows.Threading.DispatcherTimer();
public MainWindow()
{
InitializeComponent();
_timer.Interval = TimeSpan.FromSeconds(0.2); //wait for the other click for 200ms
_timer.Tick += Timer_Tick;
}
private void Timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
MessageBox.Show("Single Click!"); //handle the single click event here...
}
private void Label_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
_timer.Stop();
MessageBox.Show("Double Click!"); //handle the double click event here...
}
else
{
_timer.Start();
}
}
}
MVVM
If you apply the MVVM design pattern, you could wrap this functionality in a behavior. The following sample implementation derives from the System.Windows.Interactivity.Behavior<T> class which is part of the Microsoft Expression Blend SDK and can be downloaded from NuGet.
public class ClickBehavior : Behavior<Control>
{
private readonly DispatcherTimer _timer = new DispatcherTimer();
public ClickBehavior()
{
_timer.Interval = TimeSpan.FromSeconds(0.2);
_timer.Tick += Timer_Tick;
}
public static readonly DependencyProperty ClickCommandPropery =
DependencyProperty.Register(nameof(ClickCommand), typeof(ICommand), typeof(ClickBehavior));
public ICommand ClickCommand
{
get => (ICommand)GetValue(ClickCommandPropery);
set => SetValue(ClickCommandPropery, value);
}
public static readonly DependencyProperty DoubleClickCommandPropery =
DependencyProperty.Register(nameof(DoubleClickCommand), typeof(ICommand), typeof(ClickBehavior));
public ICommand DoubleClickCommand
{
get => (ICommand)GetValue(DoubleClickCommandPropery);
set => SetValue(DoubleClickCommandPropery, value);
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObject_Loaded;
AssociatedObject.Unloaded += AssociatedObject_Unloaded;
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
AssociatedObject.Loaded -= AssociatedObject_Loaded;
AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObject_PreviewMouseLeftButtonDown;
}
private void AssociatedObject_Unloaded(object sender, RoutedEventArgs e)
{
AssociatedObject.Unloaded -= AssociatedObject_Unloaded;
AssociatedObject.PreviewMouseLeftButtonDown -= AssociatedObject_PreviewMouseLeftButtonDown;
}
private void Timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
ClickCommand?.Execute(null);
}
private void AssociatedObject_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
_timer.Stop();
DoubleClickCommand?.Execute(null);
}
else
{
_timer.Start();
}
}
}
It hooks up the same event handler to the control once it has been loaded, and invokes a ClickCommand and a DoubleClickCommand when a single and double click is detected respectively. You apply it to a control in XAML by binding the ClickCommand and DoubleClickCommand dependency properties to two ICommand properties of your view model:
<Label xmlns:local="clr-namespace:WpfApp1"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Content="Click Here">
<i:Interaction.Behaviors>
<local:ClickBehavior ClickCommand="{Binding ClickCommand}"
DoubleClickCommand="{Binding DoubleClickCommand}" />
</i:Interaction.Behaviors>
</Label>