September 1997

A drop in the bucket

by Kent Reisdorph

As we mention in the article "What a Drag!", VCL provides built-in support for drag-and-drop operations between components. Implementing this type of drag-and-drop operation is fairly simple, but a couple of small issues can hold you up if you're not familiar with how VCL's drag-and-drop mechanism actually works. In this article, we'll describe how to accomplish drag-and-drop between components. To do so, we'll examine the OnDragDrop, OnDragOver, OnEndDrag, and OnStartDrag events. We'll also take a look at the DragMode property.

Drag and drop the easy way

If you want to perform simple drag-and-drop operations between VCL components, the process is fairly simple:
  1. Set up the source component to allow dragging.
  2. Provide event handlers in the destination component for the OnDragOver and OnDragDrop events.
The following sections describe how to accomplish these tasks.

Setting up the source component

You'll begin by setting up the drag source component to allow dragging. The DragMode property controls whether dragging is enabled for a component. This property has two possible values: dmAutomatic and dmManual. The default value is dmManual. When Drag-Mode is set to dmAutomatic, the control will automatically support dragging. When you do support dragging, you'll usually want to set DragMode to dmAutomatic. If, however, you wish to control when dragging is enabled, you'll want to set this property to dmManual. When you do so, dragging won't be enabled until you call the component's BeginDrag() method.

For example, you may want dragging to be possible only when a particular flag is set to true in your application. In this case, your OnMouseDown event handler might contain code like the following:

if (allowDrag == true) 
ListBox1->BeginDrag(false);
The BeginDrag() method's Immediate parameter, determines whether dragging should begin immediately or whether the mouse has to be moved a short distance before dragging begins. Typically, you'll call BeginDrag() with the Immediate parameter set to false so that a mouse click on a control doesn't register as a drag operation. (The rest of this article will assume that you have the DragMode property set to dmAutomatic.)

Once you've prepared the source component for dragging, VCL will display one of two drag cursors during dragging. The no-drop cursor (a white circle with a diagonal slash through it) will appear over a component that doesn't accept dropped items. When the cursor passes over a component that allows dropping, then the cursor will change to the drag cursor (an arrow with a blank piece of paper). The destination component controls whether or not it allows dropping.

The source component is now ready to allow dragging. So, let's turn our attention to the destination component.

Setting up the destination component

Basically, the destination component is where the action is. The first thing you need to do is tell VCL that you'll allow dragging, via the OnDragOver event. When you have C++Builder create an event handler for this event, the program will generate a function like the following:
void __fastcall TForm1::Memo1DragOver(
TObject *Sender, TObject *Source, int X,
int Y, TDragState State, bool &Accept)
{
}
While this function includes a lot of parameters, you'll mostly be concerned with the Accept parameter. Set this parameter to true to allow dropping and to false to disallow dropping. The Source parameter determines the component that originated dragging. For example, let's say you have a form with two list boxes (ListBox1 and ListBox2) and a Memo component (Memo1). You want to let the user drag from ListBox1 to the Memo component, but you don't want to let the user drag from ListBox2 to the memo. The OnDragOver event handler will look something like this:
void __fastcall TForm1::Memo1DragOver(
TObject *Sender, TObject *Source, int X,
int Y, TDragState State, bool &Accept)
{
if (Source == ListBox1) Accept = true;
else Accept = false;
}
That's all the code you have to write. VCL takes care of displaying the drag cursor when the user drags from ListBox1 to the memo and the no-drop cursor when the user tries to drag from ListBox2 to the memo. The user can let go of the mouse when the no-drop cursor is displayed, but VCL won't generate an OnDragDrop event unless the component has said that it will accept dropping. Speaking of the OnDragDrop event, let's take a look at it now. (For a description of the OnDragOver event's other parameters, see the VCL online help.) If a component says that it will accept dropping, then an OnDragDrop event will fire when the user drops something on the component.

For instance, continuing with our previous example, suppose you want to display the contents of the item dragged from ListBox1 to the Memo component. The OnDragDrop event handler will be as follows:

void __fastcall TForm1::Memo1DragDrop(
TObject *Sender, TObject *Source, int X, int Y)
{
int index = ListBox1->ItemIndex;
String item = ListBox1->Items->Strings[index];
Memo1->Lines->Add(item);
}
You've already determined that the only list box for which you'll allow dropping is ListBox1. Accordingly, you know that if you receive an OnDragDrop event, the source component is ListBox1. As a result, you don't perform any checks to see who the sender is--you simply get the text for the current list box index and add it to the memo's text.

Odds and ends

You can use two other events to further control drag-and-drop operations. The OnEndDrag event is fired for the source component when the user releases the mouse during a drag operation. This event will be fired regardless of whether the mouse cursor was released over a component that accepts dropping. If the mouse was released over a component that accepts dropping, then the event's Target parameter will contain a pointer to the destination component. If the mouse was released over any other component, then the Target parameter will be NULL. Similarly, the OnStartDrag event is fired when dragging begins. This event gives you an opportunity to perform any initialization needed at the start of the drag operation. The OnStartDrag event handler has a DragObject parameter that you can use to create a drag cursor or other drag objects; this parameter also initiates dragging within a control. If you set the DragObject parameter to NULL, then the drag operation will take place within the control itself. List boxes frequently use this technique to let the user change the item order by dragging items from one position to another. Note that when you use this technique, the object must still respond to the OnDragOver event and return true for the Accept parameter.

Listing A contains a program that let you drag items from a list box to a Memo component. (You can download our sample project files from www.cobb.com/cpb.) The Memo component won't accept items dropped from a second list box on the form. To run this program, place a Memo component, a Label component, and two ListBox components on a form. Set the DragMode property for both list boxes to dmAutomatic. Double-click on the events for the list boxes and memos to generate the event handlers. Run the program and observe how the different components react when you attempt to drag and drop between them. Listing A: VCLDDTst.CPP

//-----------------------------------------------------
#include 
#pragma hdrstop
#include "DDMain2.h"
//-----------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//-----------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//-----------------------------------------------------
void __fastcall TForm1::Memo1DragOver(
TObject *Sender,
TObject *Source, int X, int Y, 
TDragState State, bool &Accept)
{
if (Source == ListBox1) Accept = true;
else Accept = false;
}
//-----------------------------------------------------
void __fastcall TForm1::Memo1DragDrop(
TObject *Sender,
TObject *Source, int X, int Y)
{
Memo1->Lines->Add(
ListBox1->Items->Strings[
ListBox1->ItemIndex]);
}
//-----------------------------------------------------
void __fastcall TForm1::ListBox1StartDrag(
TObject *Sender, TDragObject *&DragObject)
{
Label1->Caption = "Status: Drag Started";
}
//-----------------------------------------------------
void __fastcall TForm1::ListBox1EndDrag(
TObject *Sender, TObject *Target, int X, int Y)
{
if (Target == Memo1)
Label1->Caption = "Status: Drag Accepted";
else
Label1->Caption = "Status: Drag Aborted";
}

Kent Reisdorph is a senior software engineer at TurboPower Software and a member of TeamB, Borland's volunteer online support group. He's the author of Teach Yourself C++Builder in 21 Days and Teach Yourself C++Builder in 14 Days. You can contact Kent at kentr@turbopower.com.