Using Drag and Drop in your Applications

Andrew MacNeill (andrew@aksel.com)
(originally written – July 2001)

 

Drag and Drop one of the features that I find are tremendously under-used in applications. Certainly, Visual FoxPro supports it. You can see it running in the Class Browser/Component Gallery, however when it comes time to put it into your own application, many developers start working with it, then throw their hands in the air and groan "maybe next build".

 

Like many developers, I have resisted getting too involved with it. Sure, it's fun but how much benefit do you really get from it? Recently, I was faced with a challenge that pointed directly towards a drag and drop solution. This article shows some of the results of those challenges and the quick and easy way of putting it to good use.

 

Drag and Drop Basics

 

Visual FoxPro has supported basic drag and drop operations since version 3.0. Set the Drag property of an object to Automatic and an object can be moved around. Add code into the Drop method of the form or another object and the object being dragged can now be manipulated. But there were some features lagging.

 

How do you drag an individual item from a listbox? Setting the Drag property to .T. lets the user drag the entire list box, not an individual item. Aside from being able to create some very cool designer dialogs, the initial approach didn't have much to offer regular application development.

 

VFP 6.0 introduced OLE Drag/Drop which offered much more functionality for the end-user. Set the OLEDragMode and OLEDropMode to 1 (automatic) on a text box and your users can immediately drag and drop text between their controls. If you prefer NOT to support the moving of text but rather copying instead, add the word NODEFAULT to the OLEDragComplete method of the control where items are dragged from and the user can't remove the content by dragging it out. Simplicity itself for most developers, right? Not quite.

 

For the developer, OLE Drag/Drop introduced some new challenges. For one, what objects can you drag and drop? If you are working with FoxPro objects, the standard Drag/Drop properties are all you need to worry about. But that functionality was related directly to the objects themselves, not the content. Textboxes are easily handled but what about dragging and dropping from other controls?

 

 

Where does the information come from?

 

If users just wanted to move text around, there’s always the clipboard. Drag and Drop isn't just about moving text - it's about moving information. However, if I drag text from a text box into a list box, what should happen? Should I get a new list item? What textbox did it originally come from? What type of data is it? All these questions usually need to be answered in order to provide a complete drag/drop solution.

 

Consider the following form with two list boxes on it (figure 1). The OLEDragMode and OLEDropMode properties on both list boxes are set to 1.

 

 

Figure 1 – Drag and Drop List Boxes. A user can drag text from the first list and drop it onto the second list easily.

 

Before an object is dropped onto another, it is useful to know the type of object it is. A reference to the object being dragged is passed in the OLEDragOver and OLEDragDrop events. The oDataObject variable doesn't contain the object but rather information about it. Call the GetFormat method to find out about the type of object it is.

 

* Method OLEDragOver

LPARAMETERS oDataObject, nEffect, nButton, nShift, nXCoord, nYCoord, nState

LcType = odataobject.GETFORMAT(1)

 

The GetFormat method is passed a format type and returns .T. or .F. if the object in question supports that data type. In the example above, the parameter of 1 returns .T. if the object being dropped is text. GetFormat accepts both numeric and string parameters. A commonly used string parameter ("VFP Source Object") indicates if the dataobject is coming from within the current VFP application. If I drag and drop text from a textbox on a FoxPro form, GetFormat("VFP Source Object") would return .T.. If I drag and drop text from Word, GetFormat("VFP Source Object") returns .F.. However, the format of an object is not independent of other formats. In both cases of dropping text from Word or from FoxPro, GetFormat(1) returns .T. (see below for more information on formats).

 

Once you know the format of the object, call the GetData method to return the formatted content of the object being dragged. For an object that has a format of 1, GetData returns the text.

 

In figure 1, when you drag an item from the first list, the object being dragged can be accessed multiple ways. In one sense, it is simply text so the following code could be put into the OLEDragDrop method.

 

* Method OLEDragDrop

LPARAMETERS oDataObject, nEffect, nButton, nShift, nXCoord, nYCoord, nState

IF oDataobject.getformat(1)

   Lc = oDataobject.GetData(1)

   THIS.AddItem(lc)

ENDIF

 

On the other hand, it is also coming from a VFP object so you could also pull information from the object itself. This code goes through the items in the list and displays each one that is selected. This assumes that the MultiSelect property in the list is .T..

 

IF oDataobject.getformat("VFP Source Object")

   lo = oDataobject.GetData("VFP Source Object")

   ** lo is now a pointer to the object itself

   FOR lni = 1 TO lo.ListItems

      IF lo.Selected(lni)

         MESSAGEBOX(lo.ListItems(lni))

      ENDIF

   ENDFOR

ENDIF

 

When an object is text or from FoxPro, FoxPro knows how to deal with it automatically. However, when objects come from other sources, FoxPro isn't always aware of the format or the content within it. As a result, dragging an object onto another might result with the "No Drop" mouse pointer being displayed, effectively canceling the action.

 

Set the property OLEDropHasData property to 1 to indicate the object being dropped is valid.  This property is set to –1 by default, which means that VFP decides if the dropped object has data or not. Set it to 0 to disable the drop or 1 to enable it. I’ll provide a key example of this below when discussing dragging files from the Windows Explorer.

 

Dealing with Formats

 

One of the most difficult tricks involved with OLE Drag and Drop is figuring out the format of the object you've got. Table 1 for a further description of formats provided.

 

Format

Description of data returned with GetData

1

The text of the object.

13

UniCode text format.

6

TIFF file

15

File handle

Biff

Microsoft Excel 2.x format. There is also Biff3, Biff4, Biff5, Biff7 and Biff8, related to the subsequent Excel versions.

CSV

Comma Separated values.

UniformResourceLocator

A URL. This gets passed by highlighting a link in a Web Browser and dragging it onto your object.

VFP Source Object

A FoxPro object.

Table 1 – Finding formats. The GetFormat method is passed a numeric or text parameter to identify what type of object is being sent. Here are some of the more common formats.

 

This might appear to be a limited list. In fact, there are many other formats however they may not appear in regular operations. With FoxPro objects, you can also create your own formats in the OLEStartDrag method. Let’s use the example of the ListBox that displays customer names in the first column but stores the customer number in the second column. The following code creates a new Format named "CustomerData" that will hold the customer ID.

 

** List OLEStartDrag Event **

LPARAMETERS oDataObject, nEffect

 

IF oDataObject.GetFormat("VFP Source Object")

   oDataObject.SetFormat("CustomerData")

   oDataObject.SetData(THIS.List(THIS.ListIndex,2),"CustomerData")

ENDIF

 

Now in the OLEDragDrop event of the second listbox, it can react to information in the CustomerData format.

 

** List2 OLEDragDrop event **

LPARAMETERS oDataObject, nEffect, nButton, nShift, nXCoord, nYCoord, nState

 

IF oDataObject.GetFormat("CustomerData")

   lc = odataobject.getdata("CustomerData")

   MESSAGEBOX("The dropped customer is "+ lc)

ENDIF

 

There are some other properties that affect how the user sees drag and drop. The OLEDragPicture property indicates the picture displayed under the mouse pointer. When dropping text, set the OLEDropTextInsertion property to 1 to ensure that the user doesn’t split any existing words. By default, the value is zero, which allows text to be dropped into an existing word.

 

Drag and Drop Within ActiveX Controls

 

Similar logic can be applied to Drag and Drop with ActiveX controls.  Figure 2 shows a TreeView and ListView on the same form. The OLEDragMode and OLEDropMode properties have been set for both controls. The following code has been added to the OLEDragDrop method of the ListView.

 

*** OLEDragDrop Event of ListView ***

LPARAMETERS data, effect, button, shift, x, y

IF data.getformat(1)

   LOCAL lc

   lc = data.getdata(1)

   THIS.ListItems.Add(,SYS(2015),lc)

ENDIF

 

Figure 2 – Exploring Drag and Drop. The TEMPORE item in the tree on the left is being dragged into the ListView on the left.

 

When the user drags a node from the Tree and drops it onto the ListView, the ListView will add a new item with the same NAME as the text on the node from the Tree. In a real application, it is more likely that the node in the tree will have a unique identifier stored in the Key property. In the OLEStartDrag event of the TreeView, set the data value of the object to the node element.

 

*** TreeView OLEStartDrag event***

LPARAMETERS data, allowedeffects

 

THISFORM.MyNode = THIS.SelectedItem

data.setdata(THIS.SelectedItem.Key,1)

 

This approach works great when dropping items onto empty areas like lists. It becomes a little trickier when working with TreeViews. Consider the difficulty of identifying what node the user dragged an item on top of. When the OLEDragDrop method fires, it is passed the x and y coordinates of where the user dropped the object. Using the TreeView’s HitTest method, you can identify what node the user was on.

 

*** TreeView OLEDragDrop Event ***

LPARAMETERS data, effect, button, shift, x, y

 

IF data.GetFormat(1)

   lc = data.getdata(1)

   lnXFactor=1440/96*(13/FONTMETRIC(1,"MS Sans Serif",8,""))

   lnYFactor=1440/96*(11/FONTMETRIC(7,"MS Sans Serif",8,""))

   lc2 = THIS.HitTest(x * lnXFactor,y * lnYFactor)

   IF NOT ISNULL(lc2)

                THIS.Nodes.Add(lc2,4,SYS(2015),THISFORM.mynode.text,1)

      LnIndex = THISFORM.myNode.Index

      THIS.Nodes.Remove(lnIndex)

      THISFORM.MyNode = .NULL.

   ELSE

      * Nothing was hit

   ENDIF

ENDIF

 

In this code, the call to HitTest is modified by use of the FONTMETRIC settings. This is because the x and y coordinates that are passed to the event are not your typical pixel coordinates. The two calls to FONTMETRIC pass the font and size of the form, along with a calculation that returns the real x and y coordinates.

 

HitTest returns either a NULL value or the Node that may be found at the coordinates provided. If there is a node there, a new node is added directly underneath it and the originally dragged node is removed from the list.

 

One problem with using the TreeView and other ActiveX controls is that they do not expose the SetFormat and SetData methods like FoxPro does. Attempts to set the format result in errors. This means that the "CustomerData" custom format approach that was used for the ListBox above doesn't work well with the TreeView.

 

Dropping from Explorer and Outlook

 

So far, all of the examples deal with dragging objects in a FoxPro application. The special challenge I faced was tracking documents that were created outside the application. The application tracked parts in an inventory as well as orders. The user received documents from their suppliers (instructions to put things together), purchase orders (sent by email) as well as graphic documents, showing pictures. They didn't want to remove the files but they needed a way of tracking all of these documents easily.

 

The result was a listbox where the user could drag and drop files from Windows Explorer as well as Outlook. When the object was dropped onto the control, the entry was stored into a table and logged in the application. The setup was exactly like we did above with one notable exception: the OLEDragOver event.

 

By default, Visual FoxPro only supports drag and drop for those objects that it knows it can handle. This usually means text. To handle other types of files, the control needs to be told that the object is supported.

 

* Method OLEDragOver

LPARAMETERS oDataObject, nEffect, nButton, nShift, nXCoord, nYCoord, nState

IF odataobject.GETFORMAT(15)

   THIS.OLEDROPHASDATA = 1

ENDIF

 

When a file is dropped from the Window Explorer, the format of the object is 15 (see table 1). The object's data is in an array that can be retrieved using GetData.

 

** Method OLEDragDrop

LPARAMETERS oDataObject, nEffect, nButton, nShift, nXCoord, nYCoord

LOCAL cfilename, afiles[ 1 ]

 

IF odataobject.GETFORMAT( 15 )

   odataobject.GETDATA( 15, @afiles )

   FOR EACH cfilename IN afiles

      ** Do something with the file

   NEXT

ENDIF

 

When dropping files from Outlook, the solution is a little different. The format for a dropped Outlook object is not a file nor is it necessarily an OLE object. Instead, the code looked at the text that was passed in the object. If the text contained the word "Subject" and had multiple lines, it was assumed to be an Outlook object. The currently selected Outlook item was identified using the Outlook ActiveExplorer (see previous columns on Outlook) and then the message was checked for the content.

 

IF odataobject.GETFORMAT( 1 )

   lc = odataobject.GETDATA( 1)

IF MEMLINES(lc) = 2 AND "Subject"$MLINE(lc,1)

   lo = CREATEOBJECT("outlook.application")

   loX = lo.ActiveExplorer

   loItem = lox.Selection.Item(1)

   ** loItem is now a pointer to the mail item

   lcText = loItem.Body

 

ENDIF

ENDIF

 

This approach provides the foundation for using drag and drop with a variety of other data formats.

 

Drag and Drop isn't hard to use but it does requires a lot of planning to decide what objects you want to support and how you want to support them. When implemented properly, developers can build applications that appear to meld right into the operating environment, making it easier for users to work with. And that can make the difference between a mediocre and a great application.