Introduction
Once again, I became frustrated at the way I seem to have to handle click and double-click events in Windows Forms. Once again, I sat down and wrote a multi-click handler because I couldn't find where I stashed the last one. This one is the simplest one I've written so far, and it will allow me to handle any number of mouse clicks I feel like throwing at any Windows control that inherits System.Windows.Forms.Control
.
You have probably noticed that in Microsoft Word, a double-click selects the whole word, and a triple-click selects the entire paragraph. This snippet will allow you to implement the quadruple-click to select the whole page, and the quintuple-click to select the whole hard drive (but not in Microsoft Word). Now, I can implement that secret application backdoor where I click 15 times on the bottom-left of the form and it gives me access to the gold vault.
How it works
I have written a simple VB.NET snippet which is pasted into the form that manages your controls. A small piece of code that implements:
- A
Timer
that is started when you click on a multi-click-enabled control;
- A counter for how many times you click the control before the
Timer
goes off;
- A method that handles the
Timer
"Tick
" event that is triggered when it goes off.
This time, there is no class, no call-back, no fancy anything. It is just a snippet that needs not much more than three lines modified per control enabled. The rest is just cut/copy/paste.
The snippet below is a single private method for managing clicks on the form itself. Management of other controls is almost identical. The method sets itself up to handle two events:
Me.MouseClick
; and
Me.MouseDoubleClick
.
It calls a standard embedded method, ClickTimer_Click
, passing its ID string, the mouse button, and the location of the mouse click. This method is just replicated and hacked for every control that needs multi-click enabling - easy.
#Region "[=== CLICK EVENTS FOR CONTROL FORM1 ===]"
Private Sub Form1_Clicked (ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles Me.MouseClick, Me.MouseDoubleClick
Const kCLICKEVENT_OWNER As String = "FORM1"
Me.ClickTimer_Click(kCLICKEVENT_OWNER, e.Button, e.Location)
End Sub
#End Region
Notes to Form1_Clicked:
Notice that the Form1_Clicked
method handles two events. In fact, an event handler can be set to handle many different events. The two events handled are "MouseClick
" and "MouseDoubleClick
", but there are two other click events: "Click
" and "DoubleClick
", what about them? OK, we could handle them also by defining the method as:
Private Sub Form1_Clicked (ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles Me.MouseClick, Me.MouseDoubleClick, Me.Click, Me.DoubleClick
However, this would double-count the number of clicks to Form1
.
The way control click events seem to work is that if multiple event handlers are set up to handle an event, they are all triggered sequentially when it occurs. MouseClick
handles a mouse-click event, so does Click
- they will both go off in sequence when the mouse is clicked. Furthermore, DoubleClick
is first and foremost a click event as well, so when there is a DoubleClick
action, the Click
event goes off first, followed by the MouseClick
event, followed by the DoubleClick
event, followed by the MouseDoubleClick
event. Four events for one double-click has to be constrained somewhat.
I have not implemented the Click
or DoubleClick
events. On a double-click, I count once for the MouseClick
event and once for the MouseDoubleClick
event, and get two clicks - very straightforward. If you click three times, I get a double-click followed by a single-click and count three clicks. Four clicks gives double-click then double-click, etc., etc.
The standard I have followed for identifying multi-clicks is a standard Windows methodology. To be considered as part of the same multi-click:
- A click must follow the previous click within a limited amount of time.
- Subsequent clicks must take place within a limited area in the vicinity of the first click.
- The same button must be clicked each time.
If any of the above conditions are not met, the multi-click count starts again at one.
- The limited time between clicks is defined by
System.Windows.Forms.SystemInformation.DoubleClickTime
.
- The limited area for subsequent clicks is defined by
System.Windows.Forms.SystemInformation.DoubleClickSize
.
- Each time an additional valid click occurs, the
Timer
's Interval
is reset to DoubleClickTime
, allowing the entry of any number of clicks, limited only by a 32-bit signed integer (some two-billion-plus - go for it).
The ClickTimer_Click code used for every control click:
Private Sub ClickTimer_Click _
(ByVal aOwnerControl As String _
, ByVal aMouseButton As System.Windows.Forms.MouseButtons _
, ByVal aClickPoint As System.Drawing.Point)
If Me.fClickTimer.Enabled Then
Me.fClickTimer.Stop()
If (Me.fClickOwner = aOwnerControl) _
AndAlso (Me.fClickButton = aMouseButton) _
AndAlso Me.fClickArea.Contains(aClickPoint) Then
Me.fClickCount += 1
Me.fClickTimer.Interval = Me.fClickDelay
Me.fClickTimer.Start()
Else
Me.ClickTimer_Initialise(aOwnerControl, aMouseButton, aClickPoint)
End If
Else
Me.ClickTimer_Initialise(aOwnerControl, aMouseButton, aClickPoint)
End If
End Sub
The infrastructure
The infrastructure methods and data are simply pasted directly into the form. They comprise of a single region containing several data fields to manage click activity and counting, plus three methods: one to initialise, one to manage the clicks, and one to handle the timer going off. All of this code collapses down in its own region, and can be pasted to the bottom of the Form
class, or wherever.
When the user stops clicking and the Timer
goes off, the event handler has data specifying the:
- ID of the control that was clicked;
- Total number of clicks;
- Mouse button that was clicked; and
- Limited region that was clicked.
Inside the Timer
event handler method, the key customisation occurs, within a Select/Case
statement, with a Case
for each control ID, fairly straightforward - it can be a router method.
The ClickTimer_TickHandler code used for every control click:
Private Sub ClickTimer_TickHandler (ByVal sender As Object, _
ByVal e As EventArgs) Handles fClickTimer.Tick
Dim xStr, xCtl As String
Me.fClickTimer.Stop()
Select Case Me.fClickOwner
Case "BUTTON1"
xCtl = "Button1"
Case "FORM1"
xCtl = "Form1"
Case "LABEL1"
xCtl = "Label1"
Case "PICTUREBOX1"
xCtl = "PictureBox1"
Case "RICHTEXTBOX1"
xCtl = "RichTextBox1"
Case "TEXTBOX1"
xCtl = "TextBox1"
Case Else
xCtl = "Unknown Control"
End Select
xStr = "Number of clicks for [" + xCtl _
+ "] are [" + Me.fClickCount.ToString + "]."
xStr += vbCrLf + "Mouse button clicked was [" _
+ Me.fClickButton.ToString + "]."
xStr += vbCrLf + vbCrLf + "Sub ClickTimerTickHandler needs to be" _
+ " customised to process the clicks for " + xCtl + "."
MsgBox(xStr)
Me.fClickCount = 0
End Sub
The samples
I have provided two small download packages:
- The source code - "multiclick_src.zip" - a VB.NET project; and
- A demonstration executable - "multiclick_exe.zip".
The demonstration is a simple form with several controls: Button
, Label
, TextBox
, PictureBox
, and RichTextBox
. All of these controls can be multi-clicked, plus the form itself, each time resulting in a message-box telling you what was clicked and how many times.
The multi-click functionality is implemented in the form itself - it is about all that is actually in there, so it is easy to find.
Finally
If anyone wants to port the snippet to other languages, go for it, and maybe post the results here in the message section, say under "C# Implementation", etc. If there are bugs or better solutions, please let me know. Meanwhile, enjoy!
If anyone actually implements more than a four-click variation, tell us about it. I've never done more than three in anger.
History