Previous Book Contents Book Index Next

Inside Macintosh: Programmer's Guide to MacApp / Part 2 - Working With MacApp
Chapter 28 - Working With Drag and Drop


Recipes--Drag and Drop

The recipes and sample code in this section demonstrate how to include and initialize MacApp's drag-and-drop support, how to enable dragging or dropping for a view, and how to add drag-and-drop support to a custom view class.

Figure 28-1 shows classes and methods used to provide drag-and-drop support in your application.

Figure 28-1 Drag-and-drop classes and methods

Recipe--Including and Initializing MacApp's Drag-and-Drop Support

To use MacApp's drag-and-drop support in your application, you perform these steps:

  1. Include MacApp's drag-and-drop code in your application.
  2. Call the InitUDragManager initialization routine.

Include MacApp's Drag-and-Drop Code in Your Application

MacApp uses the qDrag compiler flag to control compilation of drag-and-drop code. To include MacApp's drag-and-drop support, you need to set this flag to TRUE (or 1) while building your application. If you are using MPW and the MABuild system (described in Appendix A), you use a line like the following:

MABuild "{MASamples}DemoText:DemoText" -Drag -AutoBuild
This line builds the DemoText sample application and the MacApp class library, with MacApp's drag-and-drop support included.

If you are working with another development system, set the qDrag compiler flag to TRUE (or 1) before compiling MacApp and your application. You can do so by placing a line like the following in a header file that is included by all files (both yours and MacApp's) that contain drag-related code:

#define qDrag 1

Call the InitUDragManager Initialization Routine

To initialize MacApp's drag-and-drop support, your application should call the InitUDragManager routine. You call this routine from your main routine, with code similar to the following:

#if qDrag
   if (HasDragManager())
      InitUDragManager();
#endif
MacApp supplies the HasDragManager routine to determine whether the Drag Manager is available on the current machine.

IMPORTANT
Isolate any code that deals with drag-and-drop support, using conditional compilation (#if qDrag) or conditional testing (if (HasDragManager)). Otherwise, your application may try to call MacApp code or reference a MacApp variable that was not included in the current build, with unpleasant results.

Recipe--Turning on Drag-and-Drop Support for a View

To turn on drag-and-drop support for a view, you perform this step:

  1. Set the view's drag-and-drop fields

    • fDraggable (to indicate the view can initiate drags)
    • fDragMoveDeterminer, fDragMoveFamily (to indicate how data is dragged)
    • fDroppable (to indicate the view can accept drops)

This recipe shows how to set view fields directly with accessor methods supplied by MacApp, and how to set them by adding a TDragDropBehavior class to the view resource definition.

The sample code shown in this recipe is for an arbitrary view class.

Setting Drag-and-Drop Fields Directly

You can set the drag-and-drop fields of a view class directly with code like the following:

// Set fDraggable to TRUE, indicating the view can initiate drags.
fYourView->SetDraggable(TRUE);
// Set fDroppable to TRUE, indicating the view can accept drops.
fYourView->SetDroppable(TRUE);
// Set fDragMoveDeterminer to indicate that a drag between views within
// this window should be treated as a move, not a copy.
fYourView->SetDragMoveDeterminer(kMoveWithinWindow);
// Set fDragMoveFamily to an arbitray value; dragging between any views
// with this value is a move if the relationship specified by the
// fDragMoveDeterminer field is also met. Using kNoIdentifier just
// indicates that we don't want to define a special value.
fYourView->SetDragMoveFamily(kNoIdentifier);
The code in this sample specifies that the view object fYourView can initiate drags and can accept drops. If data is dragged from the fYourView object to another view within the same window that has a drag move family of kNoIdentifier, the dragged data will be moved, not copied. (For more information on drag moves, see "Drag Copy Versus Drag Move," beginning on page 268.)

You can set drag-and-drop fields in the initialization method for your view (IYourView). For a view class instantiated from a view resource definition, you can override the view's DoPostCreate method and set the drag-and-drop fields there. Or you can use the approach shown in the next section.

Setting Drag-and-Drop Fields Using the TDragDropBehavior Class

MacApp supplies the TDragDropBehavior class as a convenience for setting a view's drag-and-drop fields. The TDragDropBehavior class has four fields that duplicate the identically named view fields: fDraggable, fDroppable, fDragMoveDeterminer, and fDragMoveFamily.

When you call AddBehavior to add a behavior to a view, the AddBehavior method calls the behavior's SetOwner method. The SetOwner method for TDragDropBehavior calls the SetDraggable, SetDroppable, SetDragMoveDeterminer, and SetDragMoveFamily methods of its view to set the view's drag-and-drop fields to match those of the behavior.

The advantage of the TDragDropBehavior class is that you can use a view-editing application to add a TDragDropBehavior object to a view in a view resource definition. When your application uses NewTemplateWindow to create a view based on that definition, the TEventHandler::ReadFrom method creates any behavior objects attached to the view and calls AddBehavior for each behavior object. As a result, the SetOwner method sets the view's drag-and-drop fields to match those of the TDragDropBehavior, which you specified in the view resource.

Recipe--Adding Drag-and-Drop Support to a Custom
View Class

This recipe demonstrates how to add drag-and-drop support to a custom view class, using MacApp's TPicture class as an example. The TPicture class is a view class that displays a picture specified by its fRsrcID field and its fDataHandle field. The resource ID identifies a resource of type 'PICT', and the data handle field refers to a picture handle (type PicHandle). TPicture is a subclass of TControl, so the displayed picture can be used as a button, if desired.

To add drag-and-drop support to a custom view, you perform these steps:

  1. Define a subclass of TView or of one of MacApp's other view classes.
  2. If the view will initiate drags, override certain methods:

    • Override the DoAddDragContent method to supply the view's draggable content.
    • If the DoAddDragContent method promises any data, override the DoFulfillPromise method to supply the promised data.

  3. If the view will accept drops, override the WillAcceptDrop method.
  4. Override the DoMakeDragDropCommand method.
  5. Include and initialize MacApp's drag-and-drop support.
  6. Turn on dragging or dropping for the view.
  7. If MacApp's default drag-and-drop behavior is not sufficient, you may have to override additional methods:

    • Override the WillDrag method.
    • Override DoMakeDragCursorRegion or DoMakeDropHiliteRegion.
    • Override DoMakeDragOutlineRegion.
    • Override methods to set the drag cursor.

The sample code shown in this recipe is based on MacApp's TPicture class.

Define a Subclass of TView or Another View Class

This recipe assumes that you have defined a custom view class. For more information on defining a class, see "Recipe--Defining a Class," beginning on page 277, and "Recipe--Defining a Subclass of TApplication," beginning on page 289. MacApp's TPicture class is defined in the files UDialog.h and UDialog.cp.

For a View That Initiates Drags, Override Certain Methods

This section describes two TView methods your custom view class is likely to override to provide drag support: DoAddDragContent and DoFulfillPromise.

DoAddDragContent

The TView::HandleDrag method calls DoAddDragContent to add content for a drag operation. In TView, DoAddDragContent does nothing, so your custom view must override this method to supply its draggable content.

The TPicture class overrides DoAddDragContent to promise picture data for the drag operation (see "Promising Data," beginning on page 267):

void TPicture::DoAddDragContent()
{
   TDragItem * dragItem =
               TDragDropSession::fgDragDropSession->AddDragItem(1);
   CFlavorFlags flags;
   
   dragItem->PromiseFlavor('PICT', flags);
}
The DoAddDragContent method first calls a method of the global drag session object to add a drag item to the drag operation. The passed value (1) is an arbitrary value used as an item reference in subsequent calls to Drag Manager routines involving this item. DoAddDragContent then calls the drag item's PromiseFlavor method to make a promise to deliver 'PICT' data on request.

When a drag operation is initiated by a user, the dragged data may eventually be dropped or the drag may be ended without delivering any data. The dragged data, such as a picture handle, may be large, so it is worthwhile to promise data for future delivery, rather than actually supply it. The next section describes how to deliver promised data when it is needed.

DoFulfillPromise

If your custom view class promises data in its DoAddDragContent method, as TPicture does, it must override DoFulfillPromise to supply the promised data.

void TPicture::DoFulfillPromise(TDragItem* promisedItem)
{
   switch(promisedItem->GetFlavorType())
   {
      case 'PICT':
         {
            if (IsAResource((Handle)fDataHandle))
               LoadResource((Handle)fDataHandle);
            FailNIL(fDataHandle);
            FailNIL(*fDataHandle);
            MAVolatileInit(SignedByte, savedState,
                              HGetState((Handle)fDataHandle));
            FailInfo fi;
            Try(fi)
            {
               HNoPurge((Handle)fDataHandle);
               promisedItem->SetDataFromHandle((Handle)fDataHandle);
               HSetState((Handle)fDataHandle, savedState);
               fi.Success();
            }
            else
            {
               HSetState((Handle)fDataHandle, savedState);
               fi.ReSignal();
            }
         }
         break;

      default:
         Inherited::DoFulfillPromise(promisedItem);
         break;
   }
}
The TPicture::DoFulfillPromise method is passed a drag item that knows what type of information has been promised. If the promised flavor is 'PICT', DoFulfillPromise first makes sure the resource handle for the picture is loaded and, if so, marks it as not purgeable. It then calls a method of the drag item (SetDataFromHandle) to supply the promised data. DoFulfillPromise includes failure-handling code to restore the state of the picture handle if any error occurs. If the promised type is anything other than 'PICT', it calls Inherited to pass on the request.

For a View That Accepts Drops, Override the WillAcceptDrop Method

When a user drags information over your custom view during a drag operation, the TWindow::MouseToDropTarget method calls your view's WillAcceptDrop method to determine whether the view can accept the dragged data. A view normally accepts a drop only if it can accept at least one flavor for each drag item in the drag operation. The TPicture::WillAcceptDrop method accepts the drop only if there is exactly one drag item and it contains the 'PICT' flavor.

Boolean TPicture::WillAcceptDrop(CDragItemIterator& dragItemIterator)
{
   if (TDragDropSession::fgDragDropSession->GetItemCount() != 1)
      return FALSE;
      
   TDragItem *firstItem = dragItemIterator.FirstDragItem();
   return firstItem->FlavorExists('PICT');
}
The WillAcceptDrop method calls on a method of the global drag session object to get the number of items in the current drag. If the number of items is not one, or if the first drag item does not contain (or promise for future delivery) the flavor 'PICT', WillAcceptDrop returns FALSE; otherwise, it returns TRUE.

Override the DoMakeDragDropCommand Method

Two methods of the global drag session object, DragReceiveHandler and HandleDragToTrash, call the view's DoMakeDragDropCommand method. The DoMakeDragDropCommand method supplies a command object to perform the operation: drag, drop, or drag move (specified by the command constants cDrag, cDrop, and cDragMove). The DoMakeDragDropCommand method for the TPicture class is shown on the following page.

TCommand* TPicture::DoMakeDragDropCommand(CommandNumber itsCommandNumber,
                              CDragItemIterator&dragItemIterator)
{
   TCommand *returnCommand = NULL;
   TCommandHandler *itsContext = this->GetContext(itsCommandNumber);
   
   switch(itsCommandNumber)
   {
      case cDrag:
         {
            TPictureCommand *dragCommand = new TPictureCommand;
            dragCommand->IPictureCommand(kNoResource, NULL, this, itsCommandNumber, 
               itsContext, kCanUndo, kCausesChange, itsContext);
            returnCommand = dragCommand;
         }
         break;
      case cDrop:
         {
            HandlepictureHandle = NULL;
            TDragItem*firstItem = dragItemIterator.FirstDragItem();
            
            firstItem->FocusOnFlavor('PICT');
            pictureHandle = firstItem->GetDataAsHandle();
               
            TPictureCommand *dropCommand = new TPictureCommand;
            dropCommand->IPictureCommand(kNoResource,(PicHandle)pictureHandle,
               this, itsCommandNumber, itsContext, kCanUndo, kCausesChange,
               itsContext);
            returnCommand = dropCommand;
         }
         break;
      case cDragMove: // Does nothing--causes return value to be NULL.
         break;
      default:
         returnCommand = Inherited::DoMakeDragDropCommand(
                                    itsCommandNumber, dragItemIterator);
         break;
   }
   return returnCommand;
}
The TPicture::DoMakeDragDropCommand method makes the same kind of command, a TPictureCommand, for a drag or a drop. For a drag move it does nothing.

IMPORTANT
The DoMakeDragDropCommand method is the one and only opportunity for the target view to extract data from the drag session. Don't assume you can extract data at any later point.
In the case of a drag, the DoMakeDragDropCommand method initializes a picture command object with the values kNoResource and NULL for the picture resource ID and PicHandle. The DoIt method for TPictureCommand sets the view's resource ID and PicHandle fields to the stored values, in effect zeroing out the picture. As a result, dragging from a TPicture view is always the equivalent of a cut operation.

In the case of a drop, the DoMakeDragDropCommand method initializes a picture command object in a slightly different way--instead of specifying NULL for the PicHandle, it passes the picture handle from the current view (the TPicture or other view from which the data for the drop is being dragged). The DoIt method for TPictureCommand sets the view's PicHandle field to the passed picture handle, in effect dropping in the picture data.

Since a drag from a TPicture view cannot be dropped in the same view, there is no need for DoMakeDragDropCommand to supply a special command for a drag move.

Include and Initialize MacApp's Drag-and-Drop Support

To use MacApp's drag-and-drop support in your custom view, your application must build with drag-and-drop support and initialize it. Those tasks are described in "Recipe--Including and Initializing MacApp's Drag-and-Drop Support," beginning on page 608.

Turn on Dragging or Dropping for the View

For your custom view to initiate drags or accept drops, you must set the appropriate drag-and-drop fields. "Recipe--Turning on Drag-and-Drop Support for a View," beginning on page 610, describes how to turn on dragging or dropping for a view.

Override Additional Methods

For some custom views, including MacApp's TPicture, the previous steps are sufficient to provide drag-and-drop support. However, other views may find it necessary to override additional drag-and-drop methods of TView.

WillDrag

The TView::HandleMouseDown method calls the DoMakeDragCursorRegion method (described in the next section) and the WillDrag method to determine whether a view can initiate a drag. In the TView class, DoMakeDragCursorRegion creates a region that is equal to the view's extent. TView::WillDrag returns TRUE if the current mouse location is within the drag region and the fDraggable field has the value TRUE.

If your view requires different or additional handling, it can override WillDrag. The TPicture class overrides WillDrag to ensure that the view has picture data to drag:

Boolean TPicture::WillDrag(const VPoint& localMouse,
                     const RgnHandledragCursorRegion)
{
   if (Inherited::WillDrag(localMouse, dragCursorRegion))
      return ((fDataHandle != NULL) || (fRsrcID != kNoResource));
   else
      return FALSE;
}
This method first calls Inherited to determine whether the mouse location is in the drag region and whether the view supports dragging. If so, and if the picture view has either a PicHandle (fDataHandle != NULL) or a picture resource ID (fRsrcID != kNoResource), WillDrag returns TRUE; otherwise, it returns FALSE.

Override the DoMakeDragCursorRegion or DoMakeDropHiliteRegion Method

The TView::DoMakeDragCursorRegion method creates a region to describe the specific portion of the view's content that is draggable. Draggable content is often the area within the view that the user perceives as selected, such as highlighted cells in a grid view or selected text in a text view. The DoMakeDragCursorRegion method is called by the TView methods HandleMouseDown, DoSetDragCursor, and DoMakeDragOutlineRegion.

In the TView class, DoMakeDragCursorRegion calls the DoMakeDropHiliteRegion method, which creates a region that matches the view's extent. The DoMakeDropHiliteRegion method is also called by the global drag session object's ShowDropTargetHilite method when setting a drop target.

As a result, your application has several options if it needs to modify any of the drag-and-drop regions supplied by the TView class:

The TGridView view class, for example, overrides DoMakeDragCursorRegion to create a drag region that includes all selected cells in the gridview.

The TEditText view class overrides DoMakeDropHiliteRegion to provide a region that is inset by 1 pixel to compensate for a border area.

Override the DoMakeDragOutlineRegion Method

In the TView class, the DoMakeDragOutlineRegion method calls on the DoMakeDragCursorRegion method, so the drag outline region matches the drag cursor region, which matches the view's extent. If your custom view class needs a specialized drag region (for example, if the drag outline region, drag cursor region, and view's extent are not identical), it can override the DoMakeDragOutlineRegion method.

Setting the Drag Cursor

The default behavior supplied by the TView class is to display an open-hand cursor when a drag can be initiated, and a closed-hand cursor when a drag is under way. This behavior is appropriate for dragging data such as a picture.

Classes such as TTEView and TEditText override the GetWillDragCursorID method to return the kNoResource ID constant. This results in the display of an arrow cursor--an image appropriate for dragging text--when a drag can be initiated. These classes also override the GetIsDraggingCursorID method to return kNoResource, so that the arrow cursor is also used during the actual drag operation.

Your view class can also override these methods to specify a particular cursor when a drag can be initiated or when a drag is actually in progress. For more information, see "Setting the Drag Cursor," beginning on page 257.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
25 JUL 1996