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

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

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.