Share via


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>