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.
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?
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.
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.
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.
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.