How to determine single click vs. double click?

G

Gregg Walker

I have a windows forms application that needs to do one thing for a single click and another thing for a double click event.
However, the forms click event gets fired whenever a double click event occurs and I don't want to perform the single click event
plus the double click whenever the user double clicks on the form. What is the best (standard) way to handle this?

IMHO it seems like there should be three Click events for a form:

Click - Gets fired on first mouse down mouse up
MouseDown
Click
MouseUp

SingleClick - Gets fired after click if no double click
MouseDown
Click
MouseUp
SingleClick

DoubleClick - Gets fired after second mouse down mouse up
MouseDown
Click
MouseUp
MouseDown
DoubleClick
MouseUp

Am I missing something?

Thanks.
Gregg Walker
 
E

Ed Kaim [MSFT]

The MouseEventArgs parameter of the MouseDown has a property called Clicks
that tracks how many times the button was pressed. This may help, but I
haven't tried using it in a real app.

Gregg Walker said:
I have a windows forms application that needs to do one thing for a single
click and another thing for a double click event.
However, the forms click event gets fired whenever a double click event
occurs and I don't want to perform the single click event
plus the double click whenever the user double clicks on the form. What
is the best (standard) way to handle this?
 
D

Doug Forster

Hi Gregg,

One way is to start a timer to do the click action after the first click
(with a short interval say 200 - 300 ms) and cancel the timer (or set a flag
or whatever) if a double click fires within that interval.

Cheers

Doug Forster

Gregg Walker said:
I have a windows forms application that needs to do one thing for a single
click and another thing for a double click event.
However, the forms click event gets fired whenever a double click event
occurs and I don't want to perform the single click event
plus the double click whenever the user double clicks on the form. What
is the best (standard) way to handle this?
 
G

Gregg Walker

Thanks for the reply Ed.

MouseEventArgs.Clicks doesn't help. MouseDown gets fired twice for DoubleClick. First time clicks = 1 second time click = 2.

Thanks,
Gregg Walker
 
G

Gregg Walker

Hi Doug,

So far what you suggest is the only way I can see to determine if a click is a "SingleClick" or a "DoubleClick". I was hoping for
another solution not involving a timer. Not that coding it is particularly difficult. It's just that there's already an "internal"
timer being used already to determine that a double click has occurred. Seems like when that timer expires and another MouseDown
has not occurred something like a SingleClick event should be raised. That way an application can know whether a click is single or
double.

If you have any other thoughts let me know.

Thanks much.
Gregg Walker
 
S

Stoitcho Goutsev \(100\) [C# MVP]

Hi Gregg,
That Windows OS behaviour is by design. On the first click windows sends
WM_LBUTTONDOWN and if:
1) The second click fallows the first one within some defined interval of
time (GetDoubleClickTime() API)
and
2) If the distance between the point where the first click occured and the
point of the second click is within defined rectangle (system metrics
SM_CXDOUBLECLK and SM_CYDOUBLECLK )
and
3) Windows class has the flag CS_DBLCLKS set

Then windows sends the message WM_LBUTTONDBLCLK.

So, you can see whether windows will notify for double click or not is not
only a matter of time measuring.

If you want an application to take actions on click and double click you
should design your application in a fashion where double click action is a
complement of the single click one.
For example single-click - selectes and double-click executes (or take some
action over the selected objects)

Otherwise it is really hard to guess whether single click will be followed
by a double click.


--
B\rgds
100
Gregg Walker said:
I have a windows forms application that needs to do one thing for a single
click and another thing for a double click event.
However, the forms click event gets fired whenever a double click event
occurs and I don't want to perform the single click event
plus the double click whenever the user double clicks on the form. What
is the best (standard) way to handle this?
 
G

Gregg Walker

Hello Stoitcho,

Thanks for the information. I have written an inheritable panel in which I've implemented the Click, SingleClick and DoubleClick
logic. Since I presume the the DoubleClick in the framework is already taking the metrics you described into account I am
concluding that if a double click doesn't occur within SystemInformation.DoubleClickTime interval then I have a SingleClick. This
makes it fairly trivial to determine if a click is Single or Double.
If you want an application to take actions on click and double click you
should design your application in a fashion where double click action is a
complement of the single click one.
For example single-click - selectes and double-click executes (or take some
action over the selected objects)

After using my class and understanding that the timing involved in a double-click is sytem dependant I wholeheartedly agree with
your statement above. Wherever both single and double clicks are allowed the click event needs to be used where it can be repeated
over and over again. The way I've implemented the SingleClick event has the side-effect of the DoubleClick actually being handled
quicker than a SingleClick. This is because the SingleClick event can't be fired until the timeout for the DoubleClick has elapsed.
Depending on how a user configured their mouse in the control panel that could be a long or short time and when on the long side may
not give the desired result as far as UI is concerned.

Thanks much for the information and comments. I really appreciate your input.

Sincerely,
Gregg Walker

p.s. Here's the code for my base class panel. Let me know if you have any comments.

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

using Rmca.Common;

namespace Rmca.Forms
{
/// <summary>
/// Summary description for EntryPanel.
/// </summary>
public class EntryPanel : System.Windows.Forms.Panel
{
public event System.EventHandler SingleClick;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

private System.Timers.Timer _clickTimer = null;
private bool _clickFired = false;
private bool _clickTimerFired = false;

public EntryPanel()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();

// TODO: Add any initialization after the InitComponent call
_clickTimer = new System.Timers.Timer(SystemInformation.DoubleClickTime);
_clickTimer.Enabled = false;
_clickTimer.Elapsed += new System.Timers.ElapsedEventHandler(_clickTimer_Elapsed);
_clickTimer.SynchronizingObject = this;
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if( components != null )
components.Dispose();
}

if(_clickTimer != null)
{
_clickTimer.Stop();
_clickTimer.Dispose();
_clickTimer = null;
}

base.Dispose( disposing );
}

#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion

protected override void OnPaint(PaintEventArgs pe)
{
// TODO: Add custom paint code here

// Calling the base class OnPaint
base.OnPaint(pe);
}

protected override void OnClick(EventArgs e)
{
_clickFired = true;

base.OnClick(e);
}

protected virtual void OnSingleClick(System.EventArgs e)
{
if(_clickTimer != null)
{
if(_clickTimer.Enabled)
{
_clickTimer.Stop();
_clickTimerFired = false;
}
}

if(this.SingleClick != null)
{
this.SingleClick(this, e);
}
}

protected override void OnDoubleClick(EventArgs e)
{
if(_clickTimer != null)
{
if(_clickTimer.Enabled)
{
_clickTimer.Stop();
_clickTimerFired = false;
}
}

base.OnDoubleClick(e);
}

protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);

if(_clickTimer != null)
{
if(this.SingleClick != null)
{
_clickFired = false;
_clickTimerFired = false;

if(e.Clicks < 2)
{
_clickTimer.Start();
}
else
{
_clickTimer.Stop();
_clickTimerFired = false;
}
}
}
}

protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);

if(_clickTimer != null)
{
if(_clickTimerFired)
{
PerformSingleClick();
}
}
}

private void _clickTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if(_clickTimer != null)
{
_clickTimer.Stop();
_clickTimerFired = true;

if(_clickFired)
{
PerformSingleClick();
}
}
}

protected virtual void PerformClick()
{
this.OnClick(new System.EventArgs());
}

protected virtual void PerformSingleClick()
{
this.OnSingleClick(new System.EventArgs());
}

protected virtual void PerformDoubleClick()
{
this.OnDoubleClick(new System.EventArgs());
}
}
}
 
S

Stoitcho Goutsev \(100\) [C# MVP]

Hi Gregg,

I played a bit with your code. And it works extremely well.
However, I have some comments.
1. The panel is little bit slugish on SingleClick. It can't be otherwise
ofcourse. Hopefully no one will set too long time interval because I believe
it will be pain to use windows with such settings.
2. If you click the right mouse button and then left mouse button you miss
one single click.
3. If you click the right mouse button and then doubleclick the left one in
this case you may get one single click or may not. Probably you should Stop
and Start again the timer in
if(e.Clicks < 2)
{
_clickTimer.Stop();
_clickTimer.Start();
}

This will reset the time interval. Of course it depends on how you want to
deal with situations when the clicks come from different mouse buttons.
4. You set double-click time interval in the panel's constructor. Don't
forget this settings can be changed as your application running and then it
may stop handling clicks correctly.


--
B\rgds
100

Gregg Walker said:
Hello Stoitcho,

Thanks for the information. I have written an inheritable panel in which
I've implemented the Click, SingleClick and DoubleClick
logic. Since I presume the the DoubleClick in the framework is already
taking the metrics you described into account I am
concluding that if a double click doesn't occur within
SystemInformation.DoubleClickTime interval then I have a SingleClick. This
makes it fairly trivial to determine if a click is Single or Double.


After using my class and understanding that the timing involved in a
double-click is sytem dependant I wholeheartedly agree with
your statement above. Wherever both single and double clicks are allowed
the click event needs to be used where it can be repeated
over and over again. The way I've implemented the SingleClick event has
the side-effect of the DoubleClick actually being handled
quicker than a SingleClick. This is because the SingleClick event can't
be fired until the timeout for the DoubleClick has elapsed.
Depending on how a user configured their mouse in the control panel that
could be a long or short time and when on the long side may
not give the desired result as far as UI is concerned.

Thanks much for the information and comments. I really appreciate your input.

Sincerely,
Gregg Walker

p.s. Here's the code for my base class panel. Let me know if you have any comments.

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

using Rmca.Common;

namespace Rmca.Forms
{
/// <summary>
/// Summary description for EntryPanel.
/// </summary>
public class EntryPanel : System.Windows.Forms.Panel
{
public event System.EventHandler SingleClick;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

private System.Timers.Timer _clickTimer = null;
private bool _clickFired = false;
private bool _clickTimerFired = false;

public EntryPanel()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();

// TODO: Add any initialization after the InitComponent call
_clickTimer = new System.Timers.Timer(SystemInformation.DoubleClickTime);
_clickTimer.Enabled = false;
_clickTimer.Elapsed += new System.Timers.ElapsedEventHandler(_clickTimer_Elapsed);
_clickTimer.SynchronizingObject = this;
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if( components != null )
components.Dispose();
}

if(_clickTimer != null)
{
_clickTimer.Stop();
_clickTimer.Dispose();
_clickTimer = null;
}

base.Dispose( disposing );
}

#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion

protected override void OnPaint(PaintEventArgs pe)
{
// TODO: Add custom paint code here

// Calling the base class OnPaint
base.OnPaint(pe);
}

protected override void OnClick(EventArgs e)
{
_clickFired = true;

base.OnClick(e);
}

protected virtual void OnSingleClick(System.EventArgs e)
{
if(_clickTimer != null)
{
if(_clickTimer.Enabled)
{
_clickTimer.Stop();
_clickTimerFired = false;
}
}

if(this.SingleClick != null)
{
this.SingleClick(this, e);
}
}

protected override void OnDoubleClick(EventArgs e)
{
if(_clickTimer != null)
{
if(_clickTimer.Enabled)
{
_clickTimer.Stop();
_clickTimerFired = false;
}
}

base.OnDoubleClick(e);
}

protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);

if(_clickTimer != null)
{
if(this.SingleClick != null)
{
_clickFired = false;
_clickTimerFired = false;

if(e.Clicks < 2)
{
_clickTimer.Start();
}
else
{
_clickTimer.Stop();
_clickTimerFired = false;
}
}
}
}

protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);

if(_clickTimer != null)
{
if(_clickTimerFired)
{
PerformSingleClick();
}
}
}

private void _clickTimer_Elapsed(object sender,
System.Timers.ElapsedEventArgs e)
 
G

Gregg Walker

Hi Stoitcho,

Thanks for taking the time to work with the code. Your comments are extremely valuable. I will have to think about how I want to
handle the cases you bring up.

Cheers,
Gregg Walker
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top