Tuesday, August 23, 2005

Differentiating between a single click and a double click C#

In a Windows control, whenever the user performs a double click with his mouse, it generates 2 sets of events: first a single click event, followed by a double click event. In some controls this does not pose a problem. For example in a text box, the first click places the caret in the control, the double click selects the text. In others, this behaviour can lead to problems. In a listview, that contains a checkbox, for example. Single clicking it toggles the checkbox on and off. If you want a double click event to do something, like pop up a messagebox, that displays the information in the row that was double clicked, then the double click will cause the checkbox to toggle (because of the single click that gets generated). The only way to get around this behaviour, is to wait for the time allowed between two clicks, so as to consider them as part of one double click event. If in this time a double click did not follow, the fire the single click event (or as my code shows, toggles the checkbox), if it does, then just call the double click event. Here is the sample code, that I used to extend the ListBox control, so as to not confuse the single and double click events. Here is what you need to know: The listview has an ImageList assigned to it. Image 0 is a CheckedBox image and Image 1 is a un-checked box image. The timer fires every 50 milliseconds. The timer is started everytime a single click event is fired and stoped after the time given for a double click has elapsed. (The doubleClickTime, is the maximum allowed time between 2 single clicks so as to be considered as a single click.) If a double click event is detected from when a single click was fired, to the expiry of the doubleClickTime, the OnDoubleClick method is called. If a double click event is not received, then its assumed that the user wants to process a single click event. (in my case, I set the image index to toggle the checkbox and also call the OnItemCheck event). C# code example: using System; using System.Drawing; namespace System.Windows.Forms { public class ListViewEx : System.Windows.Forms.ListView { private const int WM_LBUTTONDBLCLK = 0x0203; private const int WM_LBUTTONDOWN = 0x0201; private const int WM_RBUTTONDBLCLK = 0x0206; Timer _timer = new Timer(); bool _dblClick; Point _p = Point.Empty; ListViewItem _lvi = null; private int milliseconds = 0; private Rectangle _dblClickHitTest = Rectangle.Empty; public ListViewEx():base() { _timer.Interval = 50; _timer.Tick +=new EventHandler(_timer_Tick); } protected override void WndProc(ref Message m) { _p = Point.Empty; Point translatedPt = Point.Empty; switch (m.Msg) { case WM_RBUTTONDBLCLK: break; case WM_LBUTTONDBLCLK: _dblClick = true; _p = PointToClient(new Point(Cursor.Position.X, Cursor.Position.Y)); _lvi = GetItemAt(_p.X, _p.Y); break; case WM_LBUTTONDOWN: _dblClick = false; _p = PointToClient(new Point(Cursor.Position.X, Cursor.Position.Y)); _lvi = GetItemAt(_p.X, _p.Y); translatedPt = new Point(_p.X - SystemInformation.DoubleClickSize.Width / 2, _p.Y - SystemInformation.DoubleClickSize.Height/2); _dblClickHitTest = new Rectangle(translatedPt,SystemInformation.DoubleClickSize); _timer.Start(); break; default: base.WndProc (ref m); break; } } private void _timer_Tick(object sender, EventArgs e) { milliseconds += _timer.Interval; if (milliseconds >= SystemInformation.DoubleClickTime/2) { milliseconds = 0; _timer.Stop(); if (_dblClick == true && _dblClickHitTest.Contains(_p)) { //double click if (_lvi != null) _lvi.Selected = true; this.OnDoubleClick(null); } else { //single click if (_lvi != null) { _lvi.Selected = true; _lvi.ImageIndex = 1 - _lvi.ImageIndex; this.OnItemCheck(new ItemCheckEventArgs(this.Items.IndexOf(_lvi), _lvi.ImageIndex == 0 ? CheckState.Checked : CheckState.Unchecked, _lvi.ImageIndex == 0 ? CheckState.Unchecked: CheckState.Checked)); } } } } } }

1 comment:

Anonymous said...

The following also works in you ListView derived class:

private const int WM_LBUTTONDBLCLK = 0x0203;

protected override void WndProc(ref Message m)
{
switch (m.Msg) {
case WM_LBUTTONDBLCLK:
OnDoubleClick(EventArgs.Empty);
return;
}
base.WndProc(ref m);
}

It simply overrides the default behaviour which is:
1) Generate Click Event
2) Generate DoubleClick Event

And you don't want the first event