Click here to Skip to main content
15,886,004 members
Articles / Programming Languages / Visual Basic
Article

How to enable a control on double click in Visual Basic .NET

Rate me:
Please Sign up or sign in to vote.
4.28/5 (8 votes)
10 Sep 20047 min read 67.2K   184   18   2
A simple class written in Visual Basic .NET which enables disabled controls on double click.

Content of the ZIP file

The ZIP file contains a demonstration application (source code and binary) which was created with Visual Studio 2003 as well as an additional copy of the file CtrlActivator.vb which contains the complete source code of the CtrlActivator class.

Introduction

I recently read a post by a fellow programmer who wanted to have a disabled TextBox in a GroupBox and to enable it when the user would double click on it. The question was how to do it while the TextBox does not receive any messages as long as it is disabled. Well, I'm still not sure if that is a good UI design but I was curious what a decent solution might look like. So, I created this little class I'm going to introduce in this article.

First, I tried to generalize the problem a little bit. What the asker wanted to do was to have a disabled control on a container that should be activated on a double click. And that's what my CtrlActivator class does.

How to use it: A simple example

Just add the file CtrlActivator.vb to your project. Now, let's assume you have a form MainForm in your project and a button DisabledButton (which of course is disabled). Since the CtrlActivator class does not contain any static members, we need an instance of the class. The constructor of CtrlActivator expects a reference of the type Control, and because Form and UserControl are derived from Control it also works fine with them. This control must be the parent of the controls we want to enable on a double click. Why the parent? Well, messages which would normally be sent to a control don't vanish when the particular control is disabled. They are send to the disabled control's parent (given that the parent is not disabled itself). This of course means that if our Button is nested in a GroupBox, we must pass a reference to the GroupBox to the CtrlActivator constructor. If you are unsure about the parent, you can use the Parent property of the control you want to enable (for example, TextboxActivator = New CtrlActivator(DisabledButton.Parent)).

VB
Public Class MainForm

  Inherits System.Windows.Forms.Form

  Private TextboxActivator As CtrlActivator = New CtrlActivator(Me)

[Designer generated code]

End Class

To let the CtrlActivator know what we want to enable, we call CtrlActivator.Register and pass a reference to DisableButton.

VB
TextboxActivator.RegisterControl(DisabledButton)

That's all. When you execute the code, you can enable the DisableButton by double-clicking on it. Because the class uses a collection to store the objects that were registered by the caller, you can register as many objects as you want. The only requirement is that they all have the same parent control. But what if you later want not to have a certain control handled by the CtrlActivator object after you registered it? Fortunately, there is a correspondent UnRegisterControl method. Just call it and pass a reference to the control you registered earlier. Again, that's all.

VB
TextboxActivator.UnRegisterControl(DisabledButton)

There is just one thing you should keep in mind: if you continue reading, you will find out that the CtrlActivator class relies on registering new event handlers to the parent control as well as, of course, keeping references to the controls it is supposed to enable. Therefore, you should always call CtrlActivator.Dispose after you're done. In the following paragraphs, I will describe every public member of the class.

How to use it (public members): Sub New(ByVal Parent As Control)

The constructor of the class has one parameter which is used to pass a reference to the parent of the controls you want to enable. If you want to handle multiple parent controls, you need to create one instance of the CtrlActivator class for each parent control. The constructor will throw a NullReferenceException exception if Nothing is passed as its parameter.

VB
Sub New(ByVal Parent As Control)

How to use it (public members): Public Sub RegisterControl(ByVal Ctrl As Control)

RegisterControl is used to register controls that will be enabled on a double click. No exception will be thrown when Nothing or a reference to an already registered control is passed. However, an ObjectDisposedException exception will be thrown when this method is called after Dispose.

VB
Public Sub RegisterControl(ByVal Ctrl As Control)

How to use it (public members): Public Sub UnRegisterControl(ByVal Ctrl As Object)

UnRegisterControl is used to un-register a previously registered control. Like RegisterControl, this method is fault tolerant ignoring Nothing and references to controls which where not registered previously. However, an ObjectDisposedException exception will be thrown when this method is called after Dispose.

VB
Public Sub UnRegisterControl(ByVal Ctrl As Object)

How to use it (public members): Public Function Exists(ByVal Ctrl As Control) As Boolean

Exists checks if a given object has been registered and returns True if it has been registered. Otherwise, the method returns False.

VB
Public Function Exists(ByVal Ctrl As Control) As Boolean

How to use it (public members): Public Overloads Sub Dispose()

Dispose deletes all external references stored within the CtrlActivator object and therefore should be called when the object is not needed anymore.

VB
Public Overloads Sub Dispose() Implements IDisposable.Dispose

How to use it (public members): Public ReadOnly Property Count() As Integer

The property Count returns the number of controls currently registered.

VB
Public ReadOnly Property Count() As Integer

How it works: Global declarations

The class implements the IDisposable interface to allow a controlled deconstruction of an instance. It also has four private members. p_CallingCtrl is used to store a reference to the parent control of the controls we want to enable. p_CtrlCollection is a reference to the collection that actually stores references to the controls we want to handle. p_MousePos is used to buffer the current location of the cursor in the coordinate system of the parent control. p_Disposed is a flag which is set to True after Dispose has been executed.

VB
Implements IDisposable

Private p_CallingCtrl As Control = Nothing
Private p_CtrlCollection As Collection = New Collection
Private p_MousePos As Point = New Point(-1, -1)
Private p_Disposed As Boolean = False

How it works: Sub New(ByVal Parent As Control)

The constructor is responsible for copying a parent control reference to p_CallingCtrl and registering the CtrlActivator object's own DoubleClick and MouseMove event handlers to the parent control. The constructor throws a NullReferenceException exception if Nothing is passed to it.

VB
Sub New(ByVal Parent As Control)
  If Parent Is Nothing Then
    Throw New NullReferenceException
  End If
  p_CallingCtrl = Parent
  AddHandler p_CallingCtrl.DoubleClick, AddressOf p_CallingCtrl_DoubleClick
  AddHandler p_CallingCtrl.MouseMove, AddressOf p_CallingCtrl_MouseMove
End Sub

How it works: Public Sub RegisterControl(ByVal Ctrl As Control)

RegisterControl adds control references to the p_CtrlCollection. If the method Dispose has already been executed, an ObjectDisposedException exception is thrown. Furthermore, a control reference is only added to the collection if it is not Nothing and has not already been added to the collection before. However, it is not ensured that the control is indeed a child control of the control referenced by p_CallingCtrl.

VB
Public Sub RegisterControl(ByVal Ctrl As Control)
  If Me.p_Disposed Then
    Throw New ObjectDisposedException(Me.ToString())
  End If

  If Not (Ctrl Is Nothing) Then
    If Not Exists(Ctrl) Then
      p_CtrlCollection.Add(Ctrl)
    End If
  End If
End Sub

How it works: Public Sub UnRegisterControl(ByVal Ctrl As Object)

UnRegisterControl removes controls from the collection and also throws an ObjectDisposedException exception if it is called after Dispose. However, no exception is thrown when the control reference passed to the method is not found in the collection or when it is Nothing.

VB
Public Sub UnRegisterControl(ByVal Ctrl As Object)
  Dim Counter As Integer

  If Me.p_Disposed Then
    Throw New ObjectDisposedException(Me.ToString())
  End If

  For Counter = 1 To p_CtrlCollection.Count
    If p_CtrlCollection.Item(Counter) Is Ctrl Then
      p_CtrlCollection.Remove(Counter)
      Exit For
    End If
  Next Counter
End Sub

How it works: Public ReadOnly Property Count() As Integer

After ensuring that Dispose has not been executed before, the Count property returns the number of controls in the collection by simply forwarding the value of p_CtrlCollection.Count.

VB
Public ReadOnly Property Count() As Integer
  Get
    If Me.p_Disposed Then
      Throw New ObjectDisposedException(Me.ToString())
    End If

    Return p_CtrlCollection.Count
  End Get
End Property

How it works: The Deconstructor Logic

The methods Dispose, Dispose(ByVal disposing As Boolean), and Finalize were created by implementing the Microsoft dispose pattern.

VB
Public Overloads Sub Dispose() Implements IDisposable.Dispose
  Dispose(True)
  GC.SuppressFinalize(Me)
End Sub

Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean)
  If Not (Me.p_Disposed) Then
    If disposing Then
      RemoveHandler p_CallingCtrl.DoubleClick, AddressOf p_CallingCtrl_DoubleClick
      RemoveHandler p_CallingCtrl.MouseMove, AddressOf p_CallingCtrl_MouseMove
      p_CtrlCollection = Nothing
      p_CallingCtrl = Nothing
    End If
    Me.p_Disposed = True
  End If
End Sub

Protected Overrides Sub Finalize()
  Dispose(False)
End Sub

How it works: Private Sub p_CallingCtrl_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs)

This event handler is triggered when the DoubleClick event occurs at the parent control. A loop casts every item back to Control and copies the reference to the local variable Ctrl. After that Ctrl can be used to call Bounds.Contains(Point). The Point used here is p_MousePos, our own buffer of the last cursor position on our parent control. If the cursor is above the control which is currently checked, the particular control is enabled by setting Ctrl.Enabled to True.

VB
Private Sub p_CallingCtrl_DoubleClick(ByVal sender As Object, _
                                           ByVal e As System.EventArgs)
  Dim Counter As Integer
  Dim Ctrl As Control = Nothing

  For Counter = 1 To p_CtrlCollection.Count
    Ctrl = CType(p_CtrlCollection.Item(Counter), Control)
    If Ctrl.Bounds.Contains(p_MousePos) Then
      Ctrl.Enabled = True
    End If
  Next Counter
End Sub

How it works: Private Sub p_CallingCtrl_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)

This event handler is triggered when the MouseMove event occurs at the parent control. This is necessary because of two reasons. First, we can't use the DoubleClick event to get the cursor position because it only passes an EventArgs object which does not contain this information. Second, it is possible to use the Cursor object but this object returns the absolute cursor position on the screen, and frankly, I failed to manually calculate the relative cursor position (always had an offset of a few pixel on both axes) or find an appropriate class for getting the relative position (your feedback is welcome if you find one). Anyway, this solution might be a little bit dirty but at least it provides us exactly the position information we need when processing the DoubleClick event.

VB
Private Sub p_CallingCtrl_MouseMove(ByVal sender As Object, _
                    ByVal e As System.Windows.Forms.MouseEventArgs)
  p_MousePos.X = e.X
  p_MousePos.Y = e.Y
End Sub

How it works: Public Function Exists(ByVal Ctrl As Control) As Boolean

Last but not least, there is Exists which checks whether a certain control has been registered or not. This is done by using a loop and comparing each reference with the Ctrl parameter. Like any other public member, Exists ensures that Dispose has not been executed.

VB
Public Function Exists(ByVal Ctrl As Control) As Boolean
  Dim Counter As Integer

  If Me.p_Disposed Then
    Throw New ObjectDisposedException(Me.ToString())
  End If

  Exists = False
  For Counter = 1 To p_CtrlCollection.Count
    If p_CtrlCollection.Item(Counter) Is Ctrl Then
      Exists = True
      Exit For
    End If
  Next Counter
End Function

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
drjhodges15-Jan-12 22:23
drjhodges15-Jan-12 22:23 
GeneralIs it possible to Edit details by doubleclick on the gridview.net n C# Pin
LynnOhio12-Mar-09 0:42
LynnOhio12-Mar-09 0:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.