Click, Double Click and Triple Click

AlHal2
AlHal2 used Ask the Experts™
on
How does C# distinguish between two single click events and a double click event?
Suppose I have a key and I want it to display "1" in the textbox if pressed once; "2" in the textbox if pressed twice and "3" if pressed three times.
If the gap between key presses is more than a second reset counter to zero.
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Commented:
C# doesn't distinguish the difference, normally your operating system does.  In the simplest terms, your operating system has settings for the amount of time between clicks which determines which event is broadcast to any listeners.

You could always use a low level hook and hard code a timer which will wait for a period of time to determine the click succession:

Low-Level Hook Demonstrated on a previous EE_PAQ - https:/Q_28714279.html#a40980172

As well as here - https://blogs.msdn.microsoft.com/toub/2006/05/03/low-level-mouse-hook-in-c/

Low-Level Hook Demonstrating using a Timer to determine click succession - https:/Q_21313562.html#a13411576

-saige-
Russ SuterSenior Software Developer

Commented:
It's determined by the operating system. There's an option in the Mouse Properties control panel that lets you adjust the double click speed.
Mouse Control PanelThe OS will always send a click event message on a single click. If another click is registered before the interval has elapsed (and that click is within a given distance from the first click) it will send an double click message as well. If the second click is registered after the time has elapsed then you just get another single click event.

As it_saige suggested above, you can hook into the windows messaging system and create your own handler to simulate a double-click event using timing other than what the operating system uses.
AndyAinscowFreelance programmer / Consultant

Commented:
In addition it is considered really bad practice to modify system settings without asking the user first - so adjusting the double click interval in the operating system by your app should not be an option.

The op system does not recognise a triple click, that is something you have to do yourself.

A simple alternative to that above is for your app to have the same handler for a double click as for a single click and you track the interval between mouse click events yourself and act appropriately.
Rowby Goren Makes an Impact on Screen and Online

Learn about longtime user Rowby Goren and his great contributions to the site. We explore his method for posing questions that are likely to yield a solution, and take a look at how his career transformed from a Hollywood writer to a website entrepreneur.

Author

Commented:
Hi IT_Saige,

I've no doubt that your hook will work.  The only problem is my difficulty understanding it.
Why do you need to call DLLs?
Perhaps the complexity is due to the requestor asking about co-ordinates.  Do you have a version which just does what I need?
The code below doesn't work properly with an old mobile keypad.  I'm trying to see if it's a simple bug in my code or if I'm missing something more fundamental.

public Button clickedButton1 = null;
        public Button clickedButton2 = null;
        public Button clickedButton3 = null;
        public Button clickedButton4 = null;
        public DateTime clickTime1 = new DateTime(2011,1,1,1,1,1,1);
        public DateTime clickTime2 = new DateTime(2011, 1, 1, 1, 1, 1, 1);
        public DateTime clickTime3 = new DateTime(2011, 1, 1, 1, 1, 1, 1);
        public DateTime clickTime4 = new DateTime(2011, 1, 1, 1, 1, 1, 1);
        TimeSpan timeDiff1;
        TimeSpan timeDiff2;
        TimeSpan timeDiff3;
        TimeSpan timeDiff1Now;
        TimeSpan timeDiff2Now;
        public string LetterToDisplay = "";
        public int cmdAbcPressed = 0;
        public int cmdDefPressed = 0;
private void ProcessButton(object sender, EventArgs e)
        {

            clickedButton4 = clickedButton3;
            clickTime4 = clickTime3;
            clickedButton3 = clickedButton2;
            clickTime3 = clickTime2;
            clickedButton2 = clickedButton1;
            clickTime2 = clickTime1;
            clickedButton1 = sender as Button;
            clickTime1 = DateTime.Now;

            timeDiff1 = clickTime1-clickTime2;
            timeDiff2 = clickTime2 - clickTime3;
            timeDiff3 = clickTime3 - clickTime4;

            timeDiff1Now = DateTime.Now-clickTime2;
            timeDiff2Now = DateTime.Now - clickTime3;
            //MessageBox.Show(clickedButton1.Name + " clicked at " + clickTime1);
        }

        private void cmdAbc_Click(object sender, EventArgs e)
        {
            //string LetterToDisplay = "";
            cmdAbcPressed += 1;
            cmdDefPressed = 0;
            //if (LetterToDisplay!="")
            //{
            //    DisplayText(LetterToDisplay);
            //}
            ProcessButton(sender,e);
            if (clickedButton2 == null)
            {
                LetterToDisplay = "a";
            }
            else if(clickedButton2 != clickedButton1)
            {
                LetterToDisplay = "a";
            }
            else if  (timeDiff1.TotalMilliseconds > 500)
                //else if (((clickTime1 - clickTime2).Milliseconds > 500))
            {
                LetterToDisplay = "a";
            }
            else
            {
                if (clickedButton3 == null)
                {
                    LetterToDisplay = "b";
                }
                else if (clickedButton3 != clickedButton2)
                {
                    LetterToDisplay = "b";
                }
                else if (timeDiff2.TotalMilliseconds > 500)
                //else if (((clickTime2 - clickTime3).Milliseconds > 500))

                {
                    LetterToDisplay = "b";
                }
                else
                {
                    LetterToDisplay = "c";
                }
            }
  
            DisplayText(LetterToDisplay);
            txtDebug.Text = txtDebug.Text + (timeDiff1.TotalMilliseconds + " | " + timeDiff2.TotalMilliseconds + " | " + clickTime1 + " | " + clickTime2 + " | " + clickTime3 + " | " + clickTime4 + (char)13 + (char)10);
            if (clickedButton3 != null)
            {
                if ((timeDiff2Now).TotalMilliseconds > 500)
                {
                    clickedButton1 = null;
                    clickedButton2 = null;
                    clickedButton3 = null;
                    clickTime3 = new DateTime(2011, 1, 1, 1, 1, 1, 1);
                    clickTime2 = new DateTime(2011, 1, 1, 1, 1, 1, 1);
                    clickTime1 = new DateTime(2011, 1, 1, 1, 1, 1, 1);
                }
            }
            if (clickedButton2 != null)
            {
                if ((timeDiff1Now).TotalMilliseconds > 500)
                {
                    clickedButton1 = null;
                    clickedButton2 = null;
                    clickTime2 = new DateTime(2011, 1, 1, 1, 1, 1, 1);
                    clickTime1 = new DateTime(2011, 1, 1, 1, 1, 1, 1);
                }
            }
            if (clickedButton1 != null)
            {
                if ((DateTime.Now - clickTime1).TotalMilliseconds > 500)
                {
                    clickedButton1 = null;
                    clickTime1 = new DateTime(2011, 1, 1, 1, 1, 1, 1);
                }
            }

        }

Open in new window

Senior Software Developer
Commented:
For it_saige's suggestion to work, you need to hook into the low-level windows messaging engine. That's why DLLs are needed. The .NET framework provides event handlers for the low-level messages and message timings can get in the way of accurately determining a "triple click". You might be able create a crude model that will do what you're looking for without low level message hooking but you'd have to start playing around with threaded timers and handle both the click and double_click events. Here's a code sample I whipped up quickly that will add a triple click event to a form. Note, the code could be made more complete by also checking to make sure that the mouse cursor hasn't moved beyond the distance specified by SystemInformation.DoubleClickSize.
using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace TripleClick
{
	public partial class ClickyForm : Form
	{
		private System.Threading.Timer _clickTimer;
		private int _clickCount = 0;

		public ClickyForm()
		{
			InitializeComponent();
			this.Click += ClickyForm_Click;
			this.DoubleClick += ClickyForm_DoubleClick;
			this.TripleClick += ClickyForm_TripleClick;
		}

		private void ClickyForm_Click(object sender, EventArgs e)
		{
			++_clickCount;
			if (_clickTimer == null)
			{
				_clickTimer = new System.Threading.Timer(clickTimer_Callback, null, SystemInformation.DoubleClickTime * 2, Timeout.Infinite);
			}
			if (_clickCount == 4)
			{
				// this will be the third click
				_clickCount = 0;
				_clickTimer.Dispose();
				_clickTimer = null;
				TripleClick?.Invoke(sender, e);
				return;
			}
			// Handle regular click logic here
			System.Diagnostics.Debug.Print("click");
		}

		private void ClickyForm_DoubleClick(object sender, EventArgs e)
		{
			System.Diagnostics.Debug.Print("double click");
			if (_clickTimer != null)
			{
				_clickCount += 2;
			}
		}

		private void ClickyForm_TripleClick(object sender, EventArgs e)
		{
			System.Diagnostics.Debug.Print("triple click");
		}

		private void clickTimer_Callback(object state)
		{
			System.Diagnostics.Debug.Print("cancel click timer");
			_clickCount = 0;
			_clickTimer.Dispose();
			_clickTimer = null;
		}

		public event EventHandler TripleClick;
	}
}

Open in new window

Having run a few quick tests I have determined that this does produce a triple click event as expected under normal circumstances. However, I personally would not do it this way. Hooking into the windows messaging system would definitely be the preferred method.

Author

Commented:
I've put Saige's solution into program.cs in a windows form application.
I want to put two buttons onto Form1 and check their click, double click and triple click events.  Also, I need to see if the user has pressed button 2 after a single or double click of button 1.
What do I put in form1.cs?
Russ SuterSenior Software Developer

Commented:
AlHal2,

Where are you on this issue? Do you still need help?

Author

Commented:
I realised I could use key presses instead of mouse clicks and came up with this code.  It seems to work, but let me know what you think.


using System;

using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.IO;
namespace 
{
    public partial class Form1 : Form
    {
        public DateTime FirstClickTime = new DateTime(2011, 1, 1, 1, 1, 1, 1);
        public DateTime SecondClickTime = new DateTime(2011, 1, 1, 1, 1, 1, 1);
        public DateTime ThirdClickTime = new DateTime(2011, 1, 1, 1, 1, 1, 1);
        public DateTime FourthClickTime = new DateTime(2011, 1, 1, 1, 1, 1, 1);
        TimeSpan timeDiff1;
        TimeSpan timeDiff2;
        TimeSpan timeDiff3;
        TimeSpan timeDiff1Now;
        public string FirstKey = "";
        public string SecondKey = "";
        public string ThirdKey = "";
        public string FourthKey = "";
        public DateTime StartTime;
        public DateTime EndTime;
        public int Keystrokes = 0;
        public int TimeTaken = 0;
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            StartTime = DateTime.Now;
            using (StreamWriter sw = new StreamWriter(@"D:\OneDrive\Documents\Tests\OldMobileKeyboard\MobKeyboard4\Results.txt", false))
            {
                sw.WriteLine("Started at " + StartTime);
            }
        }
        private void textBox1_KeyUp(object sender, KeyEventArgs e)
        {
            textBox1.Text = Regex.Replace(textBox1.Text, @"[\d-]", "");
            textBox1.Text = textBox1.Text.Replace("?", "");
        }
        private string ReturnKeys(string KeyPressed)
        {
            
            string OutputKey = "";
            switch (KeyPressed)
            {
                case "2":
                    OutputKey = "abc";
                    break;

                case "3":
                    OutputKey = "def";
                    break;
                case "4":
                    OutputKey = "ghi";
                    break;
                case "5":
                    OutputKey = "jkl";
                    break;
                case "6":
                    OutputKey = "mno";
                    break;
                case "7":
                    OutputKey = "pqrs";
                    break;
                case "8":
                    OutputKey = "tuv";
                    break;
                case "9":
                    OutputKey = "wxyz";
                    break;
                case "0":
                    OutputKey = " ";
                    break;
                case "#":
                    OutputKey = "#";
                    break;
                case "*":
                    OutputKey = "*";
                    break;

                default:
                    OutputKey = "???";
                    MessageBox.Show("Please enter one of the following keys: 234567890*#");
                    break;
            }
                    return OutputKey;
            
        }

        private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
        {
            
            try
            {
                using (StreamWriter sw = new StreamWriter(@"D:\OneDrive\Documents\Tests\OldMobileKeyboard\MobKeyboard4\Results.txt", true))
                {
                Keystrokes += 1;
                textBox1.Text = Regex.Replace(textBox1.Text, @"[\d-]", "");
                textBox1.Text = textBox1.Text.Replace("?", "");
                FourthKey = ThirdKey;
                FourthClickTime = ThirdClickTime;
                ThirdKey = SecondKey;
                ThirdClickTime = SecondClickTime;
                SecondKey = FirstKey;
                SecondClickTime = FirstClickTime;
                FirstKey = e.KeyChar.ToString();
                FirstClickTime = DateTime.Now;
                string LetterToDisplay = "";
                timeDiff1Now = DateTime.Now - FirstClickTime;
                timeDiff1 = FirstClickTime - SecondClickTime;
                timeDiff2 = SecondClickTime - ThirdClickTime;
                timeDiff3 = ThirdClickTime - FourthClickTime;
                int NumberOfClicks = 0;
                
                    switch (FirstKey)
                    {
                        case ("2"):
                        case ("3"):
                        case ("4"):
                        case ("5"):
                        case ("6"):
                        case ("7"):
                        case ("8"):
                        case ("9"):
                        case ("0"):
                        case ("*"):
                        case ("#"):
                            break;
                        default:
                            e.Handled = true;
                            break;
                    }
                if (FirstKey != SecondKey)
                {
 
                        LetterToDisplay = ReturnKeys(FirstKey).Substring(0, 1);
                        NumberOfClicks = 1;
                        TimeTaken += 500;

                }
                else if (FirstKey == SecondKey && timeDiff1.TotalMilliseconds >= 500)
                {
                    LetterToDisplay = ReturnKeys(FirstKey).Substring(0, 1);
                    NumberOfClicks = 1;
                    TimeTaken += 500;
                    }
                else if (SecondKey == ThirdKey && timeDiff2.TotalMilliseconds < 500 && ThirdKey == FourthKey && timeDiff3.TotalMilliseconds >= 500)
                {
                    LetterToDisplay = ReturnKeys(SecondKey).Substring(2, 1);
                    NumberOfClicks = 3;
                    TimeTaken += 500;
                    }
                else if (SecondKey == ThirdKey && timeDiff2.TotalMilliseconds < 500 && ThirdKey == FourthKey && timeDiff3.TotalMilliseconds < 500)
                {
                    LetterToDisplay = ReturnKeys(SecondKey).Substring(3, 1);
                    NumberOfClicks = 4;
                    TimeTaken += 100;
                    }
                else if (FirstKey == SecondKey && timeDiff1.TotalMilliseconds < 500 && SecondKey != ThirdKey)
                {
                    LetterToDisplay = ReturnKeys(FirstKey).Substring(1, 1);
                    NumberOfClicks = 2;
                    TimeTaken += 100;
                }
                else if (FirstKey == SecondKey && timeDiff1.TotalMilliseconds < 500 && SecondKey == ThirdKey && timeDiff2.TotalMilliseconds >= 500)
                {
                    LetterToDisplay = ReturnKeys(FirstKey).Substring(1, 1);
                    NumberOfClicks =2;
                    TimeTaken += 100;
                }
                else if (FirstKey == SecondKey && timeDiff1.TotalMilliseconds < 500 && SecondKey == ThirdKey && timeDiff2.TotalMilliseconds < 500)
                {
                    LetterToDisplay = ReturnKeys(FirstKey).Substring(2, 1);
                    NumberOfClicks = 3;
                    TimeTaken += 100;
                }
                else
                {
                    LetterToDisplay = ReturnKeys(FirstKey).Substring(0, 1);
                    NumberOfClicks = 1;
                    TimeTaken += 500;
                }

                if (NumberOfClicks >1)
                {
                    textBox1.Text = textBox1.Text.Remove(textBox1.Text.Length -  1,  1);
                }
                
                textBox1.Text = textBox1.Text + LetterToDisplay;
                sw.WriteLine(e.KeyChar);
                textBox1.Text= Regex.Replace(textBox1.Text, @"[\d-]", "");
                textBox1.Text = textBox1.Text.Replace("?", "");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            EndTime = DateTime.Now;
            using (StreamWriter sw = new StreamWriter(@"D:\OneDrive\Documents\Tests\OldMobileKeyboard\MobKeyboard4\Results.txt", true))
            {
                sw.WriteLine("Ended at   " + EndTime);
                sw.WriteLine("Duration" + (EndTime - StartTime));
                sw.WriteLine("Minimum Duration " + TimeTaken +" milliseconds");
                sw.WriteLine("keystrokes " + (Keystrokes));
                sw.WriteLine("To make this more configurable I would put each key in a cross reference file with the chracters assigned to it.");
            }
            Environment.Exit(0);
        }
    }
}

Open in new window

Russ SuterSenior Software Developer

Commented:
I'm not sure what you're trying to accomplish here but it's a completely different problem than your initial question. May I suggest that if you require assistance with the code immediately above that you open a new question. As for the original question about single, double, and triple clicking, both it_saige and myself offered viable solutions.

Author

Commented:
Thanks all.

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial