Advanced Basics
Windows Forms Controls
Ken Spencer
Code download available at:AdvancedBasics0305.exe(144 KB)
Contents
Conclusion
Q I have two questions about building Windows® Forms controls. First, how do you build a control that allows the user to drag and drop other controls onto the new control at run time? Second, how do you allow the user to move the control around on a form?
Q I have two questions about building Windows® Forms controls. First, how do you build a control that allows the user to drag and drop other controls onto the new control at run time? Second, how do you allow the user to move the control around on a form?
A The answers to these questions was not obvious from the documentation, but they turned out to be fairly simple once I figured out the basics. I created a sample application to demonstrate the solution. The application retrieves a list of the tables from a SQL Server™ database and inserts them in a TreeView control. Then you can drag a table name and drop it onto the form to add the new user control. Once you have created the new user control, you can also add the columns in a table by dragging them and dropping them onto the new control, as shown in Figure 1.
A The answers to these questions was not obvious from the documentation, but they turned out to be fairly simple once I figured out the basics. I created a sample application to demonstrate the solution. The application retrieves a list of the tables from a SQL Server™ database and inserts them in a TreeView control. Then you can drag a table name and drop it onto the form to add the new user control. Once you have created the new user control, you can also add the columns in a table by dragging them and dropping them onto the new control, as shown in Figure 1.
Figure 1** Dropping Controls on a Form **
In this column, I'll cover only some of the control's code, but the download at the link at the top of this article contains the complete application. I derived my control from the UserControl class, which provides a generic custom control named Lister. Next, I added a label control to the top of the user control. The label will contain the title for the control. Then I added a ContextMenu control to the user control and named it mnuFitHeight. The items in the menu are shown in Figure 2.
Figure 2** Menu **
Now, let's take a look at how to handle the visual operations. I set up this control to allow the user to grab the label and move the control. The MouseDown event is used to trap the user's mouse click and store the coordinate data:
Private Sub lblTitle_MouseDown(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles lblTitle.MouseDown
localMouseIsDown = True
localXClickedPosition = e.X
localYClickedPosition = e.Y
End Sub
The localMouseIsDown variable is used to indicate that the control is being moved. The other two variables hold the X and Y locations, which correspond to the left and top.
The MouseMove event procedure fires the DragStarted event. This allows any application that is hosting this control to pick up the move operation by using this event:
Private Sub lblTitle_MouseMove(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles lblTitle.MouseMove
If localMouseIsDown And localAllowDragMove Then
RaiseEvent DragStarted(Me, Me.Location.X, _
Me.Location.Y)
End If
End Sub
The real work of the move operation is performed by the label's MouseUp event. When the user releases the mouse, this event actually moves the control. The move only occurs if the AllowDragMove public property is set to True. You can set this property to False, which prevents moving the control. The localAllowDragMove variable mirrors the AllowDragMove property locally in the procedure.
The localXClickedPosition and localYClickedPosition variables are used to track the X and Y coordinates of the mouse when the user clicked it. This allows the move to place the control relative to the user's click point and release point. If either of these variables are greater than 0, they are subtracted from the final location of the control. Finally, the new coordinates are set and used to create a new point which is used to place the control in its final position (see Figure 3).
Figure 3 Locating the Control
Private Sub lblTitle_MouseUp(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles lblTitle.MouseUp
Dim x, y As Integer
If localAllowDragMove Then
If localXClickedPosition > 0 Then
x = Me.Location.X + e.X - localXClickedPosition
Else
x = Me.Location.X + e.X
End If
If localYClickedPosition > 0 Then
y = Me.Location.Y + e.Y - localYClickedPosition
Else
y = Me.Location.Y + e.Y
End If
Me.Location = New Point(x, y)
localMouseIsDown = False
End If
End Sub
The Add method of the control simply adds a new checkbox to the user control and then wires up the event handlers for the new checkbox using the AddressOf method to point to the event handler code.
Now let's switch to Form1, which uses the Lister control. When the form loads, the tree is loaded with tables and the corresponding column names for those tables. How do you allow the user to drag a table or column name and drop it onto the form? First, you must create the ItemDrag event for the TreeView. This is where you set up the drag and drop operation. The following code creates a new variable (trNode) of type TreeNode and sets it to the instance of the node selected by the user. Then the DoDragDrop function is called to set up the drag effects (copy in this instance):
Private Sub trvwColumns_ItemDrag(ByVal sender As Object, _
ByVal e As System.Windows.Forms.ItemDragEventArgs) _
Handles trvwColumns.ItemDrag
Dim trNode As TreeNode
trNode = CType(e.Item, TreeNode)
DoDragDrop(trNode, DragDropEffects.Copy)
End Sub
If you don't set up this event, drag and drop won't work. The form's DragEnter event handles the start of the drag and drop operation. If the data is present (GetDataPresent call), then the code checks to make sure the tree node is a root node because you do not want to add a new instance of a Lister control for a column name, only a table name. You can see that handling the DragEnter event properly provides you fine control over what is dropped onto the form or control (see Figure 4).
Figure 4 Handling the DragEnter Event
Private Sub frm_DragEnter(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.DragEventArgs) _
Handles MyBase.DragEnter
Dim FullPath As String
Dim i As Integer
Dim NewControlName As String
Dim NewNode As TreeNode
Try
If e.Data.GetDataPresent(_
"System.Windows.Forms.TreeNode") _
Then
NewNode = CType(e.Data.GetData( _
"System.Windows.Forms.TreeNode"), TreeNode)
FullPath = NewNode.FullPath
i = InStr(FullPath, "\")
'Determine if item is table (root node)
If i = 0 Then
NewControlName = "lst" & NewNode.Text
If Not CheckFormForThisListItem( _
NewControlName) _
Then
End If
e.Effect = DragDropEffects.Copy
End If
End If
Catch exc As Exception
MsgBox("Error " & exc.Message)
End Try
End Sub
Now, let's turn to the form's DragDrop event handler, shown in Figure 5. This event performs a number of tasks. This is where drag and drop operations become really interesting because a number of things usually happen during the drop phase. In this example, the code makes sure it is the right node type and then calls the FindControlOnForm method to determine if a control by that name already exists on the form. If it does, then the drop does not take place and the user is presented with a message box indicating that only tables can be dropped.
Figure 5 DragDrop Event Handler
Private Sub frm_DragDrop(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.DragEventArgs) _
Handles MyBase.DragDrop
Dim x, y As Integer
Dim FullPath, NewControlName As String
Dim oControl As Control
Dim oListControl, oNewListControl As Lister
Dim i As Integer
Dim NewLabel As New Label()
Dim LocalMousePosition As Point
Dim oNewCheckLister As Lister
Dim NewNode As TreeNode
x = e.X
y = e.Y
Try
If e.Data.GetDataPresent(_
"System.Windows.Forms.TreeNode") Then
NewNode = CType(e.Data.GetData(_
"System.Windows.Forms.TreeNode"), TreeNode)
FullPath = NewNode.FullPath
NewControlName = "lst" & NewNode.Text
If IsNothing(FindControlOnForm( _
NewControlName)) Then
i = InStr(FullPath, "\")
If i > 0 Then
MsgBox( Only tables may be dropped here")
Else
oNewListControl = New Lister()
oNewListControl.Name = NewControlName
oNewListControl.AllowDragMove = True
oNewListControl.Title = FullPath
oNewListControl.AllowDrop = True
LocalMousePosition = Me.PointToClient( _
New Point(x, y))
oNewListControl.Location = _
LocalMousePosition
Me.Controls.Add(oNewListControl)
'Add control to form
Me.Controls.Add(oNewListControl)
AddHandler oNewListControl.DragEnter, _
AddressOf TableListBoxDragEnter
AddHandler oNewListControl.DragDrop, _
AddressOf TableListBoxDragDrop
AddHandler oNewListControl.ItemChecked, _
AddressOf ListItemChecked
End If
End If
End If
Catch exc As Exception
MsgBox("Error " & exc.Message)
End Try
End Sub
If the dropped node is a root node (table name), then a new instance of the Lister control is created and its properties are set. Next, the new control is added to the Controls collection of the form. Note that you can add controls to tab pages and other container controls using this technique. After the control is added to the Controls collection, the AddHandler statement is used to wire up the event handlers for the new control. This allows you to dynamically add event handlers for things like DragDrop and other events you're interested in.
Now let's check out the events for the Lister control. The TableListBoxDragEnter procedure handles the DragEnter event for the new control. When a user performs a drag and drop operation and enters the new control, this event makes sure the dragged item is a child node, then sets up the drag and drop operation. Notice that the signature of this event matches the DragEnter event even though the Handles clause is not used (see Figure 6).
Figure 6 Make Sure Item is Child Node
Sub TableListBoxDragEnter(ByVal sender As Object, _
ByVal e As System.Windows.Forms.DragEventArgs)
Dim FullPath As String
Dim i As Integer
Dim NewNode As TreeNode
Dim TableName As String
Dim ListCurrent As Lister
ListCurrent = CType(sender, Lister)
Try
If e.Data.GetDataPresent("System.Windows.Forms.TreeNode") Then
NewNode = _
CType(e.Data.GetData("System.Windows.Forms.TreeNode"), _
TreeNode)
FullPath = NewNode.FullPath
i = InStr(FullPath, "\")
If i > 0 Then
TableName = FullPath.Substring(0, i - 1)
If LCase(TableName) = LCase(ListCurrent.Title) Then
e.Effect = DragDropEffects.Copy
End If
End If
End If
Catch exc As Exception
MsgBox("Error " & exc.Message)
End Try
End Sub
The other half of the drag and drop operation is the DragDrop event shown here. This code grabs a reference to the dropped node and calls the AddListBoxItem method to add this node to the Lister control:
Sub TableListBoxDragDrop(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.DragEventArgs)
Dim ListCurrent As Lister
Dim NewNode As TreeNode
ListCurrent = CType(sender, Lister)
NewNode = CType(e.Data.GetData(_
"System.Windows.Forms.TreeNode"), TreeNode)
AddListBoxItem(ListCurrent, NewNode.Text)
End Sub
The AddListBoxItem procedure which is called by the DragDrop code simply calls the Add method of the Lister control to add the new item to the user control:
Sub AddListBoxItem(ByVal ListCurrent As Lister, _
ByVal Item As String)
ListCurrent.Add(Item, Item, False)
ListCurrent = Nothing
End Sub
Now, let me show you one other little issue I uncovered in building and using this control. I added the AllowDragMove property to allow you to turn this capability on or off. My first attempt at this property looked like this:
<Browsable(True)> _
Property AllowDragMove() As Boolean
Get
Return localAllowDragMove
End Get
Set(ByVal Value As Boolean)
AllowDragMove = Value
End Set
End Property
When I tried the control on a form, setting the property in the Properties window didn't work. I could select True or False, but the actual value did not change.
After puzzling over this for a while, I finally realized my mistake. In the Set part of the property, I was setting the AllowDragMove property instead of setting the localAllowDragMove variable. Here's the correct code:
<Browsable(True)> _
Property AllowDragMove() As Boolean
Get
Return localAllowDragMove
End Get
Set(ByVal Value As Boolean)
localAllowDragMove = Value
End Set
End Property
Whenever you are having problems with controls or properties, always remember to check that the proper variables are being set.
Conclusion
As you can see, working with drag and drop operations is pretty straightforward once you understand how the DragEnter and DragDrop events work. You also need to understand how specific events for particular controls work, such as the TreeView control's ItemDrag event.
The Microsoft® .NET Framework has a rich set of classes for building applications, which make visual operations like drag and drop or move simple to implement.
Send your questions and comments for Ken to basics@microsoft.com.
Ken Spencerworks for 32X Tech (https://www.32X.com), where he provides training, software development, and consulting services on Microsoft technologies.