Implementing Drag and Drop in Visual Basic .NET
Visual Basic® .NET Team
Summary: This article demonstrates how to implement drag and drop in Microsoft® Visual Basic® .NET Windows® Forms applications in several key scenarios. (11 printed pages)
Windows users fall into two general categories: those who prefer to use the keyboard and those who prefer to use the mouse. Programmers have been taught to look after the needs of keyboard users by providing access keys (the underlined letter in a command or menu) and shortcuts (such as a CTRL + letter combination), but the needs of mouse users have largely been ignored. Programmers tend to primarily be keyboard users, so the emphasis on keyboard-oriented features is understandable, but every programmer should consider providing mouse support as well.
One thing that mouse users expect is the ability to drag and drop. If you look at most major applications or at Windows itself, drag and drop is everywhere. For example, users are accustomed to dragging and dropping files in the Windows Explorer and to dragging and dropping text in Microsoft Word.
Despite these expectations, few Visual Basic programmers provide drag-and-drop capability in their applications — most likely because implementing drag and drop appears to be much more difficult than it actually is. This article will demonstrate how easy drag and drop really is, with examples showing how to move text, pictures, and files within forms, between forms, and even between applications.
Drag and drop is actually the same as cutting and pasting (or copying and pasting) using the mouse instead of the keyboard. In both cases you have a source (where you are cutting or copying from) and a target (where you are pasting to). During either operation, a copy of the data is maintained in memory. Cut and paste uses the Clipboard; drag and drop uses a DataObject object, which is in essence a private clipboard.
Here is the sequence of events in a typical drag-and-drop operation:
- Dragging is initiated by calling the DoDragDrop method for the source control.
The DoDragDrop method takes two parameters:
- data, specifying the data to pass
- allowedEffects, specifying which operations (copying and/or moving) are allowed
A new DataObject object is automatically created.
- This in turn raises the GiveFeedback event. In most cases you do not need to worry about the GiveFeedback event, but if you wanted to display a custom mouse pointer during the drag, this is where you would add your code.
- Any control with its AllowDrop property set to True is a potential drop target. The AllowDrop property can be set in the Properties window at design time, or programmatically in the Form_Load event.
- As the mouse passes over each control, the DragEnter event for that control is raised. The GetDataPresent method is used to make sure that the format of the data is appropriate to the target control, and the Effect property is used to display the appropriate mouse pointer.
- If the user releases the mouse button over a valid drop target, the DragDrop event is raised. Code in the DragDrop event handler extracts the data from the DataObject object and displays it in the target control.
For most simple cases, you can enable dragging with a single line of code, and you can enable dropping with just a few more lines of code.
If you are familiar with drag-and-drop techniques in Visual Basic 6.0 or earlier, there are some major differences that you will want to be aware of. If you are not familiar with Visual Basic 6.0, you can skip this section to avoid possible confusion.
Visual Basic 6.0 supported several different types of drag and drop: automatic versus manual modes, and standard versus OLE drag and drop. Drag and drop in Visual Basic .NET is most similar to manual OLE drag and drop; you can forget that any of the other variations ever existed.
The basic concept behind drag and drop remains the same — initiate a drag in the source, extract the data in the target. The names and sequence of events, however, are significantly different.
- In Visual Basic 6.0, dragging was initiated by calling the OLEDrag method, then the data was set and the allowed effects were specified in the OLEStartDrag event handler.
In Visual Basic .NET, the DoDragDrop method is used to initiate the drag, set the data, and specify allowed effects; there is no need for a start drag event.
- In Visual Basic 6.0 the OLEDropMode property was used to specify a drop target.
In Visual Basic .NET the AllowDrop property does the same. Note that not all controls expose the AllowDrop property in the Properties window; some controls only allow the property to be set programmatically.
- In Visual Basic 6.0, the appearance of the mouse pointer changed automatically when over a drop-enabled control to give visual feedback that dropping was allowed.
In Visual Basic .NET, code must be added to the DragEnter event handler to specify the allowed effects for each drop-enabled control; otherwise the no-drop mouse pointer will be displayed and dropping will not be allowed.
- In Visual Basic 6.0, the GetFormat method was used to check for the appropriate data format; data formats were expressed as Clipboard Format constants.
In Visual Basic .NET, the GetDataPresent method is used and data formats are expressed as DataFormat enumerations. In addition, any custom format can be passed as an object.
- In Visual Basic 6.0, code had to be added to the OLEDragDrop event handler for each target control. This could become tedious if you needed to set up a number of drop targets.
In Visual Basic .NET you can put your code in a single DragDrop event handler and add Handles clauses for multiple controls, greatly simplifying the process.
- Visual Basic 6.0 had an OLECompleteDrag event that notified the source control when the drag-and-drop operation was complete. This was typically used to remove the data from the source for a cut and paste operation.
There is no equivalent event in Visual Basic .NET; you must remove the data in the DragDrop event handler or set a flag to notify the source.
Note At first glance, it would appear that the QueryContinueDrag event might work for this purpose. On closer inspection, it is raised every time a drop occurs even if it is not over a valid target, causing you to prematurely remove the data.
- Visual Basic 6.0 had an OLESetData method that was raised in the source control when a drop occurred, allowing you to defer setting the data in the Data object until the drag was complete. This technique conserved memory when dragging large amounts of data.
There is no equivalent event in Visual Basic .NET; however, you can achieve the same effect by passing an empty custom data format and using the SetData method in the DragDrop event handler.
- Visual Basic 6.0 had an optional OLEDragOver event that could be used to specify a custom mouse pointer; this event had to be coded separately for each target control. It also had an OLEGiveFeedback in the source control that was raised whenever the mouse passed over a target.
In Visual Basic .NET both events are replaced by the optional GiveFeedback event; a single event handler specifies the mouse pointer for all drop-enabled controls.
One final note: Because of these differences, when you upgrade a Visual Basic 6.0 application using the Upgrade Wizard, any drag-and-drop code is not upgraded and must be rewritten. For the same reasons, you should not attempt to duplicate your Visual Basic 6.0 code when implementing drag and drop in Visual Basic .NET. The following examples should help you get started.
A simple scenario where drag and drop is useful involves copying text from one TextBox control to another. Typically keyboard commands are available in this scenario as well (select the source TextBox, press CTRL + C, select the target TextBox, press CTRL + V), but drag and drop is more efficient because it only requires a single motion (select and drag).
To enable drag and drop for text
- Add two TextBox controls to a form and set the AllowDrop property of the second TextBox control to True.
- Add the following code:
Private MouseIsDown As Boolean = False Private Sub TextBox1_MouseDown(ByVal sender As Object, ByVal e As _ System.Windows.Forms.MouseEventArgs) Handles TextBox1.MouseDown ' Set a flag to show that the mouse is down. MouseIsDown = True End Sub Private Sub TextBox1_MouseMove(ByVal sender As Object, ByVal e As _ System.Windows.Forms.MouseEventArgs) Handles TextBox1.MouseMove If MouseIsDown Then ' Initiate dragging. TextBox1.DoDragDrop(TextBox1.Text, DragDropEffects.Copy) End If MouseIsDown = False End Sub Private Sub TextBox2_DragEnter(ByVal sender As Object, ByVal e As _ System.Windows.Forms.DragEventArgs) Handles TextBox2.DragEnter ' Check the format of the data being dropped. If (e.Data.GetDataPresent(DataFormats.Text)) Then ' Display the copy cursor. e.Effect = DragDropEffects.Copy Else ' Display the no-drop cursor. e.Effect = DragDropEffects.None End If End Sub Private Sub TextBox2_DragDrop(ByVal sender As Object, ByVal e As _ System.Windows.Forms.DragEventArgs) Handles TextBox2.DragDrop ' Paste the text. TextBox2.Text = e.Data.GetData(DataFormats.Text) End Sub
In the above example, the MouseDown event is used to set a flag showing that the mouse is down, and then the DoDragDrop method is called in the MouseMove event. Although you could initiate the drag in the MouseDown event, doing so would create undesirable behavior: Every time a user clicks the control, the no-drag cursor would be displayed.
The DoDragDrop method takes two parameters:
- data parameter, which in this case takes the Text property of the TextBox
- allowedEffects parameter, which in this case only allows copying
Also in the MouseMove event the MouseIsDown flag is set to False. Although unnecessary in this example, if you had multiple controls that support dragging you could get a run-time exception.
In the DragEnter event, the GetDataPresent method checks the format of the data being dragged. In this case it is text, so the Effect property is set to Copy, which in turn displays the copy cursor.
In the DragDrop event, the GetData method is used to retrieve the text from the DataObject and assign it to the target TextBox.
The next section provides an example of dragging a different type of data and providing support for both cutting and copying.
Although not as common as dragging and dropping text, dragging and dropping pictures is useful for many applications. There really is not much difference between the two — it is just a different type of data.
There are many cases where a user wants to choose between copying or moving data in a drag-and-drop operation. The Windows user interface guidelines dictate that dragging moves an item and holding down the CTRL key while dragging copies an item.
To enable drag and drop for a picture
- Add two PictureBox controls to a form.
- Add the following code:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles MyBase.Load ' Enable dropping. PictureBox2.AllowDrop = True End Sub Private Sub PictureBox1_MouseDown(ByVal sender As Object, ByVal e As _ System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown If Not PictureBox1.Image Is Nothing Then ' Set a flag to show that the mouse is down. m_MouseIsDown = True End If End Sub Private Sub PictureBox1_MouseMove(ByVal sender As Object, ByVal e As _ System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove If m_MouseIsDown Then ' Initiate dragging and allow either copy or move. PictureBox1.DoDragDrop(PictureBox1.Image, DragDropEffects.Copy Or _ DragDropEffects.Move) End If m_MouseIsDown = False End Sub Private Sub PictureBox2_DragEnter(ByVal sender As Object, ByVal e As _ System.Windows.Forms.DragEventArgs) Handles PictureBox2.DragEnter If e.Data.GetDataPresent(DataFormats.Bitmap) Then ' Check for the CTRL key. If e.KeyState = 9 Then e.Effect = DragDropEffects.Copy Else e.Effect = DragDropEffects.Move End If Else e.Effect = DragDropEffects.None End If End Sub Private Sub PictureBox2_DragDrop(ByVal sender As Object, ByVal e As _ System.Windows.Forms.DragEventArgs) Handles PictureBox2.DragDrop ' Assign the image to the PictureBox. PictureBox2.Image = e.Data.GetData(DataFormats.Bitmap) ' If the CTRL key is not pressed, delete the source picture. If Not e.KeyState = 8 Then PictureBox1.Image = Nothing End If End Sub
In the above example, note that the AllowDrop property for the second PictureBox control is set in the Form1_Load event. This is necessary because the AllowDrop property is not available at design time.
In the MouseDown event, the code first checks to make sure that there is an image assigned to the PictureBox; otherwise, after you moved the picture, subsequent clicks would raise an exception.
Also note that in both the DragEnter and DragDrop events the code checks to see if the CTRL key is pressed to determine whether to copy or move the picture. Why are the values different? In the DragEnter event, the left mouse button is down, resulting in a value of 8 for the CTRL key plus 1 for the left mouse button. For a list of KeyState enumerations, see DragEventArgs.KeyState Property.
Both examples so far have dealt with dragging between two controls on the same form; they would also work for dragging items between controls on different forms within an application. The next example demonstrates accepting items dropped from another application — in this case, files that are dragged from Windows Explorer.
Drag and drop is used pervasively in Windows for moving or copying files. Windows Explorer fully supports drag and drop, and for many users this is the preferred method of working with files. In addition, many users are accustomed to dropping files onto an application to open them — for example, dragging and dropping a .doc file onto Microsoft Word.
In this example drag and drop is used to populate a ListBox control with a list of files dragged from Windows Explorer.
To enable drag and drop for a file
- Add a ListBox control to a form and set its AllowDrop property to True.
- Add the following code:
Private Sub ListBox1_DragEnter(ByVal sender As Object, ByVal e As _ System.Windows.Forms.DragEventArgs) Handles ListBox1.DragEnter If e.Data.GetDataPresent(DataFormats.FileDrop) Then e.Effect = DragDropEffects.All End If End Sub Private Sub ListBox1_DragDrop(ByVal sender As Object, ByVal e As _ System.Windows.Forms.DragEventArgs) Handles ListBox1.DragDrop If e.Data.GetDataPresent(DataFormats.FileDrop) Then Dim MyFiles() As String Dim i As Integer ' Assign the files to an array. MyFiles = e.Data.GetData(DataFormats.FileDrop) ' Loop through the array and add the files to the list. For i = 0 To MyFiles.Length - 1 ListBox1.Items.Add(MyFiles(i)) Next End If End Sub
You may notice that in the DragEnter event the Effect is set to DragDropEffects.All. Because the files themselves are not actually being moved or copied, it does not really matter which AllowedEffects were set by the source, so specifying All means that dropping is enabled for any FileDrop.
In the above example the FileDrop format contains the full path for each file being dropped. Rather than populating a list, you could just as easily perform other operations on the files — for example, opening them in MDI (multiple-document interface) document windows.
The next example demonstrates a special case for drag and drop: dragging items back and forth between two lists.
Another common scenario where drag and drop is expected (and highly appreciated) involves moving items from one list of items to another. Typically buttons are also available in this scenario, but they require two mouse clicks (select the item, click the button). Drag and drop is more efficient here, because it only requires a single motion (select and drag).
To move multiple items back and forth between two lists
- Add two ListView controls to a form.
- Set the AllowDrop property of each ListView control to true.
- Set the MultiSelect property of each ListView control to true.
- Set the View property of each ListView control to List.
- Add the following code:
Private Sub ListView_ItemDrag(ByVal sender As Object, ByVal e As _ System.Windows.Forms.ItemDragEventArgs) Handles ListView1.ItemDrag, _ ListView2.ItemDrag Dim myItem As ListViewItem Dim myItems(sender.SelectedItems.Count - 1) As ListViewItem Dim i As Integer = 0 ' Loop though the SelectedItems collection for the source. For Each myItem In sender.SelectedItems ' Add the ListViewItem to the array of ListViewItems. myItems(i) = myItem i = i + 1 Next ' Create a DataObject containg the array of ListViewItems. sender.DoDragDrop(New _ DataObject("System.Windows.Forms.ListViewItem()", myItems), _ DragDropEffects.Move) End Sub Private Sub ListView_DragEnter(ByVal sender As Object, ByVal e As _ System.Windows.Forms.DragEventArgs) Handles ListView1.DragEnter, _ ListView2.DragEnter ' Check for the custom DataFormat ListViewItem array. If e.Data.GetDataPresent("System.Windows.Forms.ListViewItem()") Then e.Effect = DragDropEffects.Move Else e.Effect = DragDropEffects.None End If End Sub Private Sub ListView_DragDrop(ByVal sender As Object, ByVal e As _ System.Windows.Forms.DragEventArgs) Handles ListView1.DragDrop, _ ListView2.DragDrop Dim myItem As ListViewItem Dim myItems() As ListViewItem = _ e.Data.GetData("System.Windows.Forms.ListViewItem()") Dim i As Integer = 0 For Each myItem In myItems ' Add the item to the target list. sender.Items.Add(myItems(i).Text) ' Remove the item from the source list. If sender Is ListView1 Then ListView2.Items.Remove(ListView2.SelectedItems.Item(0)) Else ListView1.Items.Remove(ListView1.SelectedItems.Item(0)) End If i = i + 1 Next End Sub
You might wonder why this example uses ListView controls rather than ListBox controls. There is a good reason: The ListBox control does not support dragging multiple items. Clicking the list invalidates the multiple selection.
The ListView and TreeView controls have an ItemDrag event that facilitates dragging. In the above example, a single ItemDrag event handler covers both controls; they are listed in the Handles clause. The sender parameter represents whichever control is initiating the drag.
Because the DataFormats class does not include a member of type ListViewItem, the data must be passed as a system Type instead. The ItemDrag code creates an array of the type ListViewItem and populates it by looping through the SelectedItems collection. In the DoDragDrop method, a new DataObject is created and populated with the array. This same technique can be used to drag and drop any system Type.
In the DragDrop event, the array is copied from the DataObject into a new ListViewItem array, and each ListViewItem is added to the Items collection of the target ListView control.
As you can see from these examples, adding drag-and-drop capabilities is not terribly difficult. When you understand the basic techniques, you can add your own customized drag-and-drop code to your applications. Most developers have application standards for such things as access and shortcut keys. Consider adding drag and drop to those standards.