Basically Visual

March/April 2000

by Peter G. Aitken (c)2000

Originally published in Visual Developer magazine


What a Drag!

If you are like most people, there are times when you find yourself taking things for granted. In the regular world this can range from really important things, such as your spouse or your health, to more mundane matters such as your car starting every morning (usually!). In the land of computers, the things you take for granted may not become obvious until they are missing. There's nothing wrong with this - after all, your computer hardware and software are tools, and there's no reason you should be constantly aware of the features of your computing tools any more than I am amazed every time I get a crescent wrench out of my toolbox and find I can actually adjust it!

 From the perspective of the developer, however, I think it is wise to pay a bit more attention. You want to be sure that the applications that you create will in fact have all those features that your end users take for granted. Some of these features are pretty much automatic in an Visual Basic application, such as the ability to resize and minimize windows. Others however are not - you the developer must explicitly include them in the code. This was brought home to me recently when I was evaluating a Visual Basic program written by a friend as a potential entry in the shareware market. The program was clearly useful and has a nice visual interface, but something was missing that made the program difficult to use. It took me a bit of thought to realize just what was missing. it turned out to be one of those things that we all take for granted in Windows programs: drag-and-drop capability. My friend is hard at work adding these features to his program, and the experience made me think that a column of how to implement drag and drop capability would be a good reminder to many readers to not overlook those things your users will expect your program to have.

Traditional Drag and Drop

Of Visual Basic's two types of drag-and-drop, the traditional method has been around the longest. It is relatively simple in concept and execution, but still offers a great deal of power and flexibility. Basically, traditional drag-and-drop lets the user drag a control to another location on a form, with other objects (controls or the form itself) being aware when the control is either dragged over them or dropped on them. The control being dragged does not actually move (while you can use drag-and-drop to achieve this, it is rarely useful in my experience), but the mouse pointer changes to indicate that something is being dragged. traditional drag-and-drop is used to implement drag-and-drop within Visual Basic programs, but cannot be used to drag to or from other applications.

As far as I know, any and all Visual Basic controls can be dragged. In a drag-and-drop operation the control being dragged is referred to as the source and the object receiving the action (being dragged over or dropped on) is the target. Controls have two properties that are related to drag-and-drop:

DragMode. Set to vbManual (value = 0, the default) for manual drag-and-drop, which requires use of the Drag method to initiate a drag-and-drop operation. Set to vbAutomatic (value = 1) to have drag-and-drop initiated automatically when the user depresses the mouse on the control.

DragIcon. Specifies the mouse pointer that is displayed while the control is being dragged. The default setting displays an arrow with a rectangle. For a custom mouse icon, set this property at design-time to a .ICO or .CUR file, or at run-time use the LoadPicture function to load an .ICO file.

Controls have two events related to drag-and-drop. One is used to detect when an object is dragged over a control, and the other to detect when an object is dropped on a control:

object_DragDrop(source As Control, x As Single, y As Single)

object_DragOver(source As Control, x As Single, y As Single, _

    State As Integer)

Object identifies the target object (the one being dragged over or dropped on). It can be a form, an MDI form, or a control. If the target is a control that is part of a control array, these event procedures will have an additional argument, as the first in the list, identifying the Index property of the control.

Source identifies the control being dragged or dropped.

X and y give the horizontal and vertical position of the mouse pointer with respect to object. These values are always expressed according to the object's coordinate system.

State specifies the relationship between the source and the target, as follows:

                - A value of 0 indicates the source just entered the target.

                - A value of 1 indicates the source is leaving the target.

                - A value of 2 indicates that the source is moving within the target.

When a source control is dragged and dropped, here's what happens. This assumes that the source control is located directly on a form and not in a Frame or Picture Box:

1. The Form receives a single DragOver event with the State argument equal to 0.

2. The Form receives multiple DragOver events with the State argument equal to 2.

3. When the source is dragged over another control on the Form, the Form receives a DragOver event with the State argument equal to 1, and the control over which the source was just dragged receives a DragOver event with the State argument equal to 0.

4. When the control is dropped, the object it is currently over receives a DragDrop event.

Executing the Drag method is required only when the source control's DragMode property is set to vbManual, but can be used also when DragMode is set to vbAutomatic for additional control. The syntax for this method is:

object.Drag action

Set action to vbCancel, vbBeginDrag, or vbEndDrag (values 0, 1, and 2 respectively) to cancel an ongoing drag-and-drop operation, to initiate a drag-and-drop operation, or to end a drag-and-drop operation (i.e., drop the control).

OK, let's look at some examples. We'll start with something really simple. Create a Visual Basic project and on the form place a Text Box and a Label. Set the Text Box's DragMode property to vbAutomatic. Put the following code in the Label's DragDrop event procedure:

Private Sub Label1_DragDrop(Source As Control, _
   
X As Single, Y As Single)

Label1.Caption = Source.Text

End Sub

When you run the project, enter some text in the Text Box then drag the Text Box to the Label. When you do, you'll see that the text is copied to the Label. Pretty easy, no?

Now let's add some enhancements. Suppose you do not want the drag operation to be possible if the Text Box is empty. Change the Text Box's DragMode property back to the default setting of vbManual. Then, add this code to the Text Box's MouseDown event procedure:

Private Sub Text1_MouseDown(Button As Integer, _
   
Shift As Integer, X As Single, Y As Single)

If Len(Text1.Text) > 0 And Button = 1 Then
   
Text1.Drag vbBeginDrag
End If

End Sub

Note that the If statement checks not only to see if the Text Box contains text, but also makes sure that the left mouse button is depressed (traditional for drag-and-drop operations).

Next, let's change the code so the drag icon indicates whether or not a drop is possible as the cursor moves around the form. To do this I used an icon editor (see the sidebar) to create two icons, one a red circle with a slash through it named NO.ICO and the other a green checkmark called YES.ICO. Both icon files were placed in the Visual Basic project folder.

Icons and cursors. Icons and cursors are nothing more complicated than special 32x32 pixel bitmaps. Visual Basic and Visual Studio come with a decent selection of ready to use icons and cursors, located in \Program Files\Microsoft Visual Studio\Common\Graphics. You can also create your own using one of the many shareware and freeware icon editors that are available. A quick search at Yahoo or another web search engine will turn up plenty. I have been using IconEdit Pro version 6.0 which is shareware available on many of the web download sites.

The first step is to modify the DragIcon property of the Text Box to NO.ICO. Next we must add code to change the icon to YES.ICO when the cursor is dragged over the Label control, and to change it back to NO.ICO when the cursor re-enters to Form. The following code in the Label's and Form's DragOver event procedures does the trick:

Private Sub Label1_DragOver(Source As Control, _
   
X As Single, Y As Single, State As Integer)

If Source.Name = "Text1" And State = 0 Then
   
Text1.DragIcon = LoadPicture(App.Path & "\yes.ico")
End If

End Sub

 

Private Sub Form_DragOver(Source As Control, _
   
X As Single, Y As Single, State As Integer)

If Source.Name = "Text1" And State = 0 Then
   
Text1.DragIcon = LoadPicture(App.Path & "\no.ico")
End If

End Sub

As I stated earlier, traditional drag-and-drop is limited to moving data between controls within the same Visual Basic application. This can indeed be very useful, but you get more power and flexibility with the second type of drag-and-drop, which is called OLE drag-and-drop. It's also more trouble to program, but life's like that!

OLE Drag and Drop

Visual Basic's second type of drag-and-drop was introduced with version 5. It is more powerful than traditional drag-and-drop, permitting different kinds of data to be moved between controls or between applications by dragging and dropping. As you might expect, OLE drag-and-drop works only between applications that support it. Most Microsoft applications have this support, and many programs from other vendors do as well. If you try to use OLE drag-and-drop with an application that does not support it, the operation simply will not work.

The basic idea behind OLE drag-and-drop is when an OLE drag-and-drop operation is initiated, whatever data is contained in the source is "packaged" as a DataObject object. When the item is dropped, the target can examine the DataObject to see if the data it contains is in a recognized format. If so, the data is accepted by the target. To see OLE drag-and-drop in action, start Microsoft Word and Excel. Select some text in Word and drag it to Excel. Presto, the text appears in the Excel worksheet cell. OLE drag-and-drop works with more complex data as well, such as charts. Usually, OLE drag-and-drop moves the data or, if the Ctrl key is depressed, copies the data.

Visual Basic controls have several properties and events related to OLE drag-and-drop, some relevant to sources and some to targets. They are described in Tables 1 and 2.

Table 1. OLE drag-and-drop related properties.

Property

Relevant to

Description

OLEDragMode

Source

Determines whether OLR drag-and-drop operations are initiated manually (the default) or automatically.

OLEDropMode

Target

Specifies whether OLE drops are ignored (vbOLEDropNone), handled manually (vbOLEDropManual), or handled automatically (vbOLEDropAutomatic).

OLEDragPicture

Source

Specifies the image displayed under the mouse cursor during an OLE drag-and-drop operation. Possible formats are .bmp, .dib, .jpg, .gif, .ani, .cur, and .ico.

OLEDropEffects

Target

Specifies the type of drop operations supported by the target.

OLEDropHasData

Target

Specifies how the target handles a drop operation.

 

 Table 2. Events related to OLE drag-and-drop operations. 

Event

Fires in

Fires when

OLEDragOver

Target

A source object is dragged over the target.

OLEDragDrop

Target

A source object is dropped on the target.

OLEStartDrag

Source

An OLE drag operation is initiated.

OLECompleteDrag

Source

The source is dropped on a target.

OLEGiveFeedback

Source

After every OLEDragOverEvent.

OLESetData

Source

When the source is dropped on a target but the data has not been loaded into the DataObject.

Before getting to the details, let's look at a simple example using the automatic settings. Start a Visual Basic project and place one Text Box and two Picture Box controls on the form. Set the OLEDragMode and OLEDropMode properties to Automatic for all three controls. Run the project, then start up a graphics program that supports OLE drag-and-drop. I know that PhotoShop does, and the Microsoft Photo Editor does not - for other programs you'll have to try and see if it works. Load a picture, select it, then drag to one of the Picture Box controls on the form. You'll see that the image is moved (or copied, if you hold down Ctrl) to the Picture Box. Also try dragging the picture from one Picture Box to the other. Note however that you cannot drag a picture to the Text Box. Next start a word processor and select some text. You'll see that you can drag the text to both the Text Box and the Picture Box on your form.

Clearly, automatic OLE drag-and-drop is quite easy to implement, requiring only that you set a coupler of properties on the relevant controls, but it is undoubtedly more limited than what is possible when you take manual control. I cannot cover all aspects of manual OLE drag-and-drop but I can get you started.

To use manual OLE drag-and-drop you need to understand the DataObject object. This object is used to store the data, as well as information about the format of the data, that is being moved or copied in an OLE drag-and-drop operation. Most of the OLE drag-and-drop related event procedures receive, as an argument, a reference to the DataObject that is part of the current operation. In the event procedures, you use the object's properties (or property, as it has only one) and methods to manage the data.

The single property of the DataObject object is Files, which references a 1-based collection containing the names of files that are being dragged to or from the Windows Explorer. This collection uses standard Visual Basic collection syntax, and will not be covered further here.

There are four methods for the DataObject object. Clear removes all data and other information from the DataObject object, and takes no arguments. The GetData method returns the data from a DataObject object, as a type Variant. The GetData method's single argument specifies the format of the data, as shown in Table 3.

Table 3. Format argument values for the GetData method.

Argument

Value

Format

0 or omitted

-

Automatic format detection based on the data.

vbCFText

1

Text

vbCFBitmap

2

Bitmap

vbCFMetafile

3

Metafile

vbCFEMetafile

14

Enhanced metafile

vbCFDIB

8

Device-independent bitmap

vbCFPalette

9

Color palette

vbCFFIles

15

List of files

vbCFRTF

-16639

Rich text format.

 The SetData method inserts data into a DataObject object using a specified format. The syntax is:

 SetData data, format

Both arguments are optional (although you must use at least one). Data is a type Variant containing the data to insert, and format is a constant (from Table 3) specifying the format of the data. If you specify the data argument but not format then Visual Basic will try to determine the format by examining the data. If you specify format without data then the specified format is added to the list of formats the existing data is available in. An example will make this clearer. Suppose the variable X contains some text data, and myDO refers to a DataObject object. The following code makes the data in X available in myDO as text, as rich text, and as a bitmap:

myDO.SetData X, vbCFText
myDO.SetData , vbCFRTF
myDO.SetData , vbCFBitmap

The GetFormat method returns a Boolean value indicating whether data is available in the specified format. The method takes a single argument, one of the format constants from Table 3, and it returns True only if the DataFormat object contains data in the specified format.

The drag-and-drop related events work together, and the relationship between them is somewhat confusing. Some actions cause events to fire in both the source and target objects. Table 4 explains how this works.

Table 4. User actions and corresponding OLE drag-and-drop events. 

Action

Event procedure(s) fired

Drag operation starts

OLEStartDrag (source)

Cursor moves over a potential target.

OLEDragOver (target), then OLEGiveFeedback (source).

Object is dropped.

OLEDragDrop (target), then OLECompleteDrag (source). OLESetData also fires if the DataObject object has not been loaded.

The event procedure OLEStartDrag, which fires when a manual OLE drag-and-drop operation begins, has an AllowedEffects argument which you use to specify the drag-and-drop effects which you want to be permitted. The constants for this argument are listed in Table 5, and can be combined with the Or operator. Which of the allowed operations actually occurs (at the end of the operation when the drop is done) is determined elsewhere in code.

Table 5. Constants for the effects argument.

Constant

Value

Description

vbDropEffectNone

0

Drop target cannot accept the data.

vbDropEffectCopy

1

Drop results in data being copied from source to target.

vbDropEffectMove

2

Drop results in data being moved from source to target.

The event procedures OLEDragOver, OLEDragDrop, OLECompleteDrag and OLEGiveFeedback have an argument effects (among other arguments) which is used to specify and to determine the drag-and-drop operations permitted. The constants listed in Table 5 are used for this argument as well. Code in the various event procedures can read the effects argument to see what effects have been permitted by the source, and can also change the value as needed. You'll see how this works in the sample application to be developed later.

Before we get to some sample code, let's look at the basic steps required to implement manual OLE drag-and-drop for Visual Basic controls. There are two things to consider: a control that serves as a source, and a  control that serves as a target. The same control can do both, of course. In either case, the control's OLEDragMode and OLEDropMode properties must be set to Manual. For a target control:

1. Place code in the control's OLEDragOver event procedure that checks the format of the data in the DataObject object. Depending on whether the available format or formats are appropriate for the control, set the effect argument to the appropriate value (vbDropEffectNone, vbDropEffectCopy, or vbDropEffectMove). This makes the mouse cursor display the appropriate symbol while dragging over a potential target control.

2. Place code in the control's OLEDragDrop event procedure that checks the format of the data in the DataObject object. The code can also check the state of the keyboard - for example to see if the Ctrl key is depressed (traditionally indicating a Copy operation). Then, if the format of the data is acceptable, and depending on other factors such as the state of the keyboard, set the effect argument to the appropriate action (vbDropEffectNone, vbDropEffectCopy, or vbDropEffectMove) and retrieve the data from the DataObject object.

The programming is a bit more complex when a control is acting as an OLE drag-and-drop source.

1. In the control's MouseMove event procedure, check the state of the mouse buttons and if the left button is depressed initiate an OLE drag-and-drop operation by calling the control's OLEDrag method.

2. In the control's OLEStartDrag event procedure (which is called as soon as the OLEDrag method is executed), insert the data from the control into the DataObject object using the SetData method. Remember to call SetData additional times as needed to specify additional data formats. Also, set the AllowedEffects argument as appropriate.

3. If necessary, put code in the control's OLEGiveFeedback event procedure to provide visual feedback as the data is dragged over various parts of the screen. Information set in a target's OLEDragOver event procedure, specifically the effects argument, is available in the OLEGiveFeedback event procedure. You can use this event procedure to change the appearance of the source control depending on where it is being dragged (as we'll do in the sample project).

4. In the control's OLECompleteDrag event procedure place any code that is required to complete the operation. Typically this consists of erasing the original data from the control after a move operation.

Now let's see these techniques in action. This is a simple application, but demonstrates almost all of the techniques you'll need to implement full-featured OLE drag-and-drop in your Visual Basic programs. The application requires a form with two Text Box controls and one Picture Box control. For all three controls, set the OLEDropMode and OLEDragMode properties to Manual. The application's code is shown in Listing 1. Here's what the program does:

- If you drag the first Text Box, its data can be copied to a target such as the second Text Box or another program that supports OLE drag-and-drop (such as Microsoft Word). If you drag the first text Box over the form or Picture Box, where a drop is not permitted, the text in the Text Box changes to red.

- The second Text Box operates the same as the first Text Box except you can specify whether the data is to be copied (by depressing Ctrl) or moved (no keys depressed).

- Bitmap data from another program, such as PhotoShop, can be dragged to the Picture Box. If bitmap data is dragged over either of the Text Box controls, the "no drop" icon is displayed.

The code is commented so you should have no problems understanding how it works. OLE drag-and-drop may not be the simplest aspect of Visual Basic programming, but once you get a good understanding of how it works, you'll find it an extremely useful addition to many of your projects. And that is not a drag!

Listing 1. Demonstrating source and target controls for OLE drag-and-drop.

Option Explicit

' This code demonstrates OLE drag and drop. It requires
' a form with two Text Box controls and one Picture Box
' control. All controls should have their OLEDragMode and
' OLEDropMode properties set to Manual.

Private Sub Picture1_OLEDragOver(Data As DataObject, _
   
Effect As Long, Button As Integer, _
   
Shift As Integer, X As Single, Y As Single, _
   
State As Integer)

' Indicate a drop is OK only if bitmap
' data is available.

If Data.GetFormat(vbCFBitmap) Then
   
Effect = vbDropEffectCopy
Else
   
Effect = vbDropEffectNone
End If

End Sub

Private Sub Text1_MouseMove(Button As Integer, _
   
Shift As Integer, X As Single, Y As Single)

' If the left button is depressed,
' initiate an OLE drag operation.

If Button = 1 Then Text1.OLEDrag

End Sub

Private Sub Text1_OLEDragDrop(Data As DataObject, _
   
Effect As Long, Button As Integer, _
   
Shift As Integer, X As Single, Y As Single)

' Accept data only if text format data is
' available in the DataObject.

If Data.GetFormat(vbCFText) Then
   
' Copy or move depending on the Ctrl key.
   
If Shift And vbCtrlMask Then
 
      Effect = vbDropEffectCopy
   
Else
       
Effect = vbDropEffectMove
   
End If

    ' Get the data.
   
Text1.Text = Data.GetData(vbCFText)
Else
   
Effect = vbDropEffectNone
End If

End Sub

Private Sub Text1_OLEDragOver(Data As DataObject, _
   
Effect As Long, Button As Integer, _
   
Shift As Integer, X As Single, Y As Single, _
   
State As Integer)

' Indicate a drop is OK only if
' text format data is available.
If Data.GetFormat(vbCFText) Then
   
Effect = vbDropEffectCopy Or vbDropEffectMove
Else
   
Effect = vbDropEffectNone
End If

End Sub

Private Sub Text1_OLEGiveFeedback(Effect As Long, _
   
DefaultCursors As Boolean)

' Change the text in the textbox to red if it
' is being dragged over a non-allowed drop target.

If Effect = vbDropEffectNone Then
    Text1.ForeColor = RGB(255, 0, 0)
Else
   
Text1.ForeColor = RGB(0, 0, 0)
End If

End Sub

Private Sub Text1_OLEStartDrag(Data As DataObject, _
    AllowedEffects As Long)

' Insert the data into the Data object and
' specify the allowed effects.

Data.SetData Text1.Text, vbCFText
AllowedEffects = vbDropEffectCopy

End Sub

Private Sub Text2_MouseMove(Button As Integer, _
    Shift As Integer, X As Single, Y As Single)

' If the left button is depressed,
' initiate an OLE drag operation.

If Button = 1 Then Text2.OLEDrag

End Sub

Private Sub Text2_OLECompleteDrag(Effect As Long)

' If the target signals that a move operations
' was requested, delete the original data.

If Effect = vbDropEffectMove Then Text2.Text = ""

End Sub

 

Private Sub Text2_OLEDragDrop(Data As DataObject, _
   
Effect As Long, Button As Integer, Shift As Integer, _
   
X As Single, Y As Single)

' Accept data as copy operation only if text
' format data is available in the DataObject.

If Data.GetFormat(vbCFText) Then
   
Effect = vbDropEffectCopy
   
Text2.Text = Data.GetData(vbCFText)
End If

End Sub

Private Sub Text2_OLEDragOver(Data As DataObject, _
   
Effect As Long, Button As Integer, _
   
Shift As Integer, X As Single, Y As Single, _
   
State As Integer)

' Indicate a drop is OK only if text
' data is available.

If Data.GetFormat(vbCFText) Then
   
Effect = vbDropEffectCopy
Else
   
Effect = vbDropEffectNone
End If

End Sub

Private Sub Text2_OLEGiveFeedback(Effect As Long, _
   
DefaultCursors As Boolean)

' Change the text in the textbox to red if it
' is being dragged over a non-allowed drop target.

If Effect = vbDropEffectNone Then
   
Text2.ForeColor = RGB(255, 0, 0)
Else
   
Text2.ForeColor = RGB(0, 0, 0)
End If

End Sub

Private Sub Text2_OLEStartDrag(Data As DataObject, _
   
AllowedEffects As Long)

' When the OLE drag starts, insert the data into the
' data object and indicate that either a move or copy
' operation is OK.

Data.SetData Text2.Text, vbCFText
AllowedEffects = vbDropEffectCopy Or vbDropEffectMove

End Sub