Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent single click on WPF control when double click occured

I have Image control with PreviewMouseLeftButtonDown event handled. The logic is to change image content when single click occured and to activate other visual style when double click occured.

I know about ClickCount property like some answers said (e.g. this) and successfully distinguish between single/double clicks, but problem is that single click occures always, wherether double click follow or not follow the next moment (which is fair enought, anyway). So, with double click both actions processed - for single click and the next moment for double click.

The question: is there any method to prevent single click before occuring right after that double click, other than handling this situation with some kind of timers magic?

Edit:
I found question with good comment, which makes an analogy with windows explorer - how it wait after single click on selected file, and start renaming just ensured that no other click occured after first click.
Delay will definitely exist in purpose to solve this problem, but does it mean that windows explorer using exactly timer, or maybe it have some other option (some property or event that can be awaited) to hold single click in case double click occured?

like image 446
Sam Avatar asked Aug 18 '15 15:08

Sam


2 Answers

Finally there were no suggestions received with timer-unrelated solution (and I didn't find any either), so here is simple example how to prevent single click when double click occurred.

Xaml:

<Window x:Class="StackOverflow.DoubleClickExample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="100" Width="150"
    MouseDown="RootElement_OnMouseDown">
</Window>

Code-behind:

namespace StackOverflow.DoubleClickExample
{
    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Input;

    public partial class MainWindow : Window
    {
        [DllImport("user32.dll")]
        public static extern uint GetDoubleClickTime();

        public MainWindow()
        {
            this.InitializeComponent();
        }

        private Guid lastGuid = Guid.Empty;

        private void RootElement_OnMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount == 1)
            {
                // Create new unique id and save it into field.
                var guid = Guid.NewGuid();
                this.lastGuid = guid;

                // Run task asynchronously for ensuring that there is no another click
                // happened in time interval when double-click can occure.
                Task.Run(async () =>
                {
                    // Wait system double-click time interval.
                    await Task.Delay((int)GetDoubleClickTime());

                    // If no double-click occured in awaited time interval, then
                    // last saved id (saved when first click occured) will be unchanged.
                    if (guid == this.lastGuid)
                    {
                        // Here is any logic for single-click handling.
                        Trace.WriteLine("Single-click occured");
                    }
                });

                return;
            }

            // Can be here only when e.ClickCount > 1, so must change last saved unique id.
            // After that, asynchronously running task (for single-click) will detect
            // that id was changed and so will NOT run single-click logic.
            this.lastGuid = Guid.NewGuid();

            // Here is any logic for double-click handling.
            Trace.WriteLine("Double-click occured");
        }
    }
}

For testing, make clicks in window area and track messages writing into output window in visual studio (menu View -> Output).

Another way is using CancellationTokenSource and trigger its Cancel method when double-click occured. Just replace lastGuid field and RootElement_OnMouseDown method:

private CancellationTokenSource cancellationTokenSource;

private void RootElement_OnMouseDown(object sender, MouseButtonEventArgs e)
    {
        if (e.ClickCount == 1)
        {
            try
            {
                this.cancellationTokenSource = new CancellationTokenSource();
                var token = this.cancellationTokenSource.Token;

                // Run task asynchronously for ensuring that there is no another click
                // happened in time interval when double-click can occure.
                Task.Run(async () =>
                {
                    // Wait system double-click time interval.
                    await Task.Delay((int)GetDoubleClickTime(), token);

                    // Here is any logic for single-click handling.
                    Trace.WriteLine("Single-click occured");
                }, token);
            }
            catch (OperationCanceledException)
            {
                // This exception always occure when task is cancelled.
                // It happening by design, just ignore it.
            }

            return;
        }

        // Cancel single-click task.
        if (this.cancellationTokenSource != null)
        {
            this.cancellationTokenSource.Cancel();
        }

        // Here is any logic for double-click handling.
        Trace.WriteLine("Double-click occured");
    }
like image 127
Sam Avatar answered Sep 19 '22 10:09

Sam


I guess you need to use a timer. For getting the max time that is valid for a double click still to occur you could use following function (tested; output is 500 ms):

[DllImport("user32.dll")]
static extern uint GetDoubleClickTime();

(source: how to get the double-click-time in WPF)

Usually when you have several values you want to bind to one WPF control you use something like ItemsSource and bind it to a List in view model. But I guess that doesn't work for Image control. So you should go with a timer and use the value of the function above for your timer.

like image 37
Marco Tröster Avatar answered Sep 18 '22 10:09

Marco Tröster