[ Next ] [ Previous ] | Chapter 20 | [ Contents ] [ Chapter 19: Other Window Classes ] [ Chapter 21:Value Set ]

### Drag and Drop

While the capability to drag and drop an icon from one window to another has been present since OS/2 1.1, a standardized, robust method for providing this essential function was not introduced until OS/2 1.3 with the Drg functions and their associated DM_ messages. But what is drag and drop, really ?
Drag and drop is the capability of using the mouse to manipulate directly the transfer and placement of data within single or multiple applications. Objects can either be "moved" or "copied" from a source window to a target window. ("Moved" and "copied" are application-defined concepts.)
Drag and drop can be seen from two viewpoints: from the viewpoint of the source, who initiates the drag; and from the aspect of the target, which can accept or reject a dragging operation. We will examine both of those as well as what to do once the target is established.

#### Tennis, Anyone ?

In a nutshell, the source window is responsible for determining that the user is attempting to drag an object, initializing the appropriate data structures, and finally calling either DrgDrag or DrgDragFiles (a version of DrgDrag specifically for file objects). Determining that the user is attempting to drag an object is the easiest part, since the system will send a WM_BEGINDRAG message with the pointer position in mpParpm1. (This is not entirely true. If a child control receives a WM_BEGINDRAG message, it might alert the programmer to this through a WM_CONTROL message, but it is not required that it do so).

After it has been decided that a drag operation is necessary, the application needs to allocate and initialize three structure types: DRAGINFO, DRAGITEM and DRAGIMAGE. (There are actually four; the DRAGTRANSFER structure is used ones a target has been established.) The DRAGINFO structure contains information about the drag as an entity. The DRAGITEM structures describe each object being dragged. Finally, the DRAGIMAGE structures each describe the appearance of the object under the pointer while it is being dragged.
`typedef struct _DRAGINFO /* dinfo */
{
ULONG cbDraginfo; /* Size of DRAGINFO and DRAGITEMs*/
USHORT cbDragitem; /* size of DRAGITEM */
USHORT usOperation; /* current drag operation */
HWND hwndSource; /* window handle of source */
SHORT xDrop; /* x coordinate of drop position */
SHORT yDrop; /* y coordinate of drop position */
USHORT cditem; /* count of DRAGITEMs */
USHORT usReserved; /* reserved for future use */
} DRAGINFO;
typedef DRAGINFO *PDRAGINFO;
` In the DRAGINFO structure, cbDraginfo is the size of the DRAGINFO structure in bytes. cbDragitem is the size of the DRAGITEM structure contained therein. usOperation is the default operation that can be, but is not required to be, set by the source and inspected by the target; it is a DO_ constant. hwndSource is the only field not initialized by DrgAllocDragInfo, and is the handle of the window initiating the drag-and-drop operation. xDrop and yDrop are the coordinates of the object as dropped. cdItem specifies the number of DRAGITEM structures stores along with the DRAGINFO structure. usReserved is reserved and must be set to 0.
`typedef struct _DRAGITEM /* ditem */
{
HWND hwndItem; /* conversation partner */
ULONG ulItemID; /* identifies item being dragged */
HSTR hstrType; /* type of item */
HSTR hstrRMF; /* rendering mechanism and format*/
HSTR hstrContainerName; /* name of source container */
HSTR hstrSourceName; /* name of item at source */
HSTR hstrTargetName; /* suggested name of item at dest*/
SHORT cxOffset; /* x offset of the origin of the */
/* image from the mouse hotspot*/
SHORT cyOffset; /* y offset of the origin of the */
/* image from the mouse hotspot*/
USHORT fsControl; /* source item control flags */
USHORT fsSupportedOps; /* ops supported by source */
} DRAGITEM;
typedef DRAGITEM *PDRAGITEM;
` In the DRAGITEM structure, hwndItem is the handle of the window with which the target should communicate to transfer the information necessary to complete the operation. The only time this would be different from the hwndSource field of the DRAGINFO structure is when an application contains many "standard" windows as a children of the main window. hstrType is the type of the item represented by the DRAGITEM structure. hstrRMF is the rendering mechanism used to transfer the information and format of data being transfered. hstrContainerName is the name of the container that holds the object being dragged. With a file object, for example, this would be the directory where the file resides. hstrSourceName and hstrContainerName is the names of the object at its original location and the suggested name of the object at the target location. The target does not have to use the suggested name; it is up to the application programmer. cxOffset and cyOffet specify the offset from the hotspot of the pointer to the lower left corner of the image representing the object and is copied here by the system from the corresponding fields in the DRAGIMAGE structure. fsControl specifies one or more DC_ constants describing any special attributes of the objects being dragged. Finally, fsSupportedOps specifies the operations that can be performed as part of the drag - the object may be copied, moved, linked ("shadowed"), and so on.

`typedef struct _DRAGIMAGE /* dimg */
{
USHORT cb; /* size control block */
USHORT cptl; /* count of pts, if DRG_POLYGON */
LHANDLE hImage; /* image handle passed to DrgDrag*/
SIZEL sizlStretch; /* size to stretch ico or bmp to */
ULONG fl; /* flags passed to DrgDrag */
SHORT cxOffset; /* x offset of the origin of the */
/* image from the mouse hotspot*/
SHORT cyOffset; /* y offset of the origin of the */
/* image from the mouse hotspot*/
} DRAGIMAGE;
typedef DRAGIMAGE *PDRAGIMAGE;`

In the DRAGIMAGE structure, cb specifies the size of the structure in bytes. fl specifies a number of DRG_ constants describing the type of data that is given in this structure.

Table 20.1 DRG_ Constants
Constant | Description
DRG_BITMAP | hImage specifies a bitmap handle
DRG_CLOSED | The polygon specified is to be closed. If specified, DRG_POLYGON also must be specified.
DRG_ICON | hImage specifies an icon handle.
DRG_POLYGON | hImage specifies an array of POINTL structures.
DRG_STRETCH | The bitmap or icon is to be stretched to fit the specified size. If specified, DRG_BITMAP or DRG_ICON also must be specified.
DRG_TRANSPARENT | An outline of the icon is to be shown only. If specified, DRG_ICON also must be specified.

cPtl specifies the number of points if fl contains DRG_POLYGON. hImage can specify one of many things, depending on what flags are set in fl, as seen in Table 20.1. sizlStretch specifies the size that the bitmap or icon should be stretched to. cxOffet and cyOffest specify the offset of the lower left corner of the image, relative to the hotspot of the cursor as the object is dragged. these two fields are copied into the DRAGITEM structure.
At this point, probably few of the fields in these structures make any sense. It is important to realize that, because the target will more likely than not exist as part of another process, simple allocation of these structures will not suffice, due to OS/2's memory protection features. They must be allocated in shared memory through the use of the DrgAllocDraginfo and DrgAddStrHandle functions.
`PDRAGINFO APIENTRY DrgAllocDraginfo(ULONG cditem);
HSTR APIENTRY DrgAddStrHandle(PCSZ psz);
`
The former accepts the number of items being dragged and returns a pointer to the shared DRAGINFO structure, whose individual DRAGITEM structures must be initialized using the DrgSetDragitem function. The latter takes a pointer to a string and returns a "string handle" - a pointer to a shared memory block containing (among other things) the string passed to the function.

#### Initialization Code for Drag and Drop Source.

The following is the typical initialization code used in a Presentation Manager application to initiate a drag-and-drop operation.
`HWND hwndWindow;
PDRAGINFO pdiDrag;
DRAGITEM ditem;
pdiDrag = DrgAllocDraginfo(1);
//-------------------------------------------------------------
// Note that DrgAllocDraginfo() initializes all of the DRAGINFO
// fields *except* hwndSource.
//-------------------------------------------------------------
pdiDrag->hwndSource = hwndWindow;
diItem.hwndItem = hwndWindow;
diItem.ulItemID = 1L; //Unique identifier
diItem.hstrType = DrgAddStrHandle(DRT_TEXT);
diItem.hstrRMF = DrgAddStrHandle("<DRM_OS2FILE,DRF_TEXT>");
diItem.hstrContainerName = DrgAddStrHandle("C:\");
diItem.hstrSourceName = DrgAddStrHandle("CONFIG.SYS");
diItem.hstrTargetName = DrgAddStrHandle("CONFIG.BAK");
diItem.cxOffset = 0;
diItem.cyOffset = 0;
diItem.fsControl = 0;
DrgSetDragItem(pdiDrag, &diItem, sizeof(diItem), 0);
`
The following sections will explain this listing in more detail.

#### Things Never Told to the Programmer That Should Have Been.

Before actually taking our forceps to the code, a few concepts need to be introduced. The first is that of the type and the true type of an object being dragged. The type is just that - a string that describes what the object consists of. The true type is a type that more accurately describes the object, if such a true type exists. For example, a file that contains C source code might have the type "Plain Text" but have a true type of "C code". An object can have more than one type, with each separated by commas and the true type appearing as the first type listed. Thus, the hstrType field for the C source code would be initialized as DrgAddStrHandle("C Code, Plain Text"). OS/2 defines a set of standard types in the form of DRT_ constants.
The second concept that needs to be discussed is the rendering mechanism and format (RMF). The rendering mechanism is the method by which the data will be communicated from the source to the target. The format is the format of the data if the corresponding rendering mechanism as used to transfer the data. These RMF pairs take the form "<rendering mechanism, format>", with multiple RMF pairs separated by commas. OS/2 also defines a set of rendering mechanisms, also no constants are defined for them.
Note that if programmers have a fully populated set of RMF pairs ("fully populated" meaning that for every rendering mechanism, every format is available), a shorthand cross-product notation can be used. For example, if there are the rendering mechanisms RA, RB and RC and the formats FA, FB and FC, and the following RMF pairs are available:
`
"<RA,FA>,<RA,FB>,<RA,FC>,<RB,FA>,<RB,FB>,<RB,FC>,<RC,FA>,<RC,FB>,<RC,FC>"
` then this can be represented as "(RA,RB,RC) X (FA,FB,FC)". Obviously, this is a much more concise way of describing the mess. If the thought of having to parse such a monster with so many different combinations just to discover if <RD, FD> is supported drives programmers crazy, they should have no fear - there are functions that will determine this.
Analogous to the relationship between type and true type, there also exists a native RMF, which describes the preferred RMF for this object. It is always the first RMF pair listed or the first RMF pair generated in a cross-product. The native RMF might employ faster data transfer algorithms or other such performance boosters, so it should be used by the target whenever possible.
Just because OS/2 defines set of types, rendering mechanisms, and formats doesn't mean programmers are limited to those sets. If an application needs to use a new format, it can register the appropriate strings describing this with the DrgAddStrHandle function. However, the transfer protocol for the rendering mechanisms and the corresponding data formats also should be published so that other applications can understand the new type of RMF.
The next concepts are that of source name, source container Drag and drop:, and target name Drag and drop:. The source name is the name of the object being dragged. It is useful because the target application may be able to perform the requested operation without having to interact with the source application. Typically, this is used when dealing with files. The source container describes where the object resides. This, again, is useful when deciding how to complete the action. When dealing with files, for example, the source container would be directory name containing the file. Finally, the target name is actually a suggested name, since the target could determine that an object with that name already exists and that the object will receive a new, unique name.
Now that these concepts have been explained, the structures and sample code shown earlier in this chapter should be easier to understand. We are dragging one item, as evidenced in the DrgAllocDragInfo call. The one item is of type "text" and will be transferred via the file system using the format "unknown". The file system object resides in the container/directory "C:\" and has the name "CONFIG.SYS". The suggested target name is "CONFIG.BAK", although the target application is free to select a different name.

#### Direct Manipulation Is a Real Drag

Assuming that the last section has been understood and that programmers have successfully (and correctly) initialized the DRAGINFO structure and each DRAGITEM structure for each object, we are now ready to call the function that makes all of this hard work worthwhile: DrgDrag:
`HWND APIENTRY DrgDrag(HWND hwndSource,
PDRAGINFO pdinfo,
PDRAGIMAGE pdimg,
ULONG cdimg,
LONG vkTerminate,
PVOID pRsvd);
` hwndSource is the handle of the window initiating the drag operation. pdinfo points to the DRAGINFO structure returned from DrgAllocDraginfo. pdimg points to an array of one or more DRAGIMAGE structures, and cdimg specifies how many images the array contains. vkTerminate describes the manner by which the drag is ended and is a VK_ constant.

Table 20.2 Description of VK_ Constants in a Drag Operation
Constant | Description
VK_BUTTON1 | Drag is ended using mouse button 1.
VK_BUTTON2 | Drag is ended using mouse button 2.
VK_BUTTON3 | Drag is ended using mouse button 3.
VK_ENDDRAG | Drag is ended by the mouse button defined in the "System Setup" folder to end a drag. This should be used when dragging is performed in response to a WM_BEGINDRAG message.

The DRAGIMAGE structure describes the image to be displayed as the object is being dragged. Since only the DrgDrag function needs to access this, and since the DrgDrag function executes in the context of the process calling it, this structure is not part of the DRAGITEM structure (although having it there would have made things slightly less complicated).
DrgDrag returns the window handle of the target window, if one is established. If the user pressed either the ESC key (to end the drag) or the F1 key (to get help for dropping on the current target), NULLHANDLE is returned, and the source is responsible for returning any shared resources consumed by calling DrgDeleteDraginfoStrHandles to delete all of string handles in the DRAGINFO structure, DrgDeleteStrHandle for each HSTR allocated that is not present in the DRAGINFO structure, and DrgFreeDraginfo to free the DRAGINFO structure. If this occurred frequently, nothing more would have to be discussed; instead we will assume that the user selected a target window and released the appropriate mouse button to initiate the transfer.

#### And Now a Word from Our Sponsor

Since the data transfer actively involves both the source and target windows, now is a good time to view the target's perspective from the beginning. Remember that it is the target's responsibility to provide visual feedback to the user during the drag operation and to initiate the data transfer once the drop has occurred. Visual feedback is accomplished by responding to the appropriate DM_ messages that are sent to the target during the drag.
DM_DRAGOVER This message is sent whenever the pointer enters the target window space to allow it the opportunity to add target emphasis to the destination of the drag. This is also sent whenever a key is pressed or released. The message contains a pointer to the DRAGINFO structure which cab accessed by calling DrgAccessDragInfo.
DM_DRAGLEAVE This message is sent to any window previously sent a DM_DRAGOVER message whenever the pointer leaves the target window space to allow it the opportunity to remove any "target emphasis" previously drawn. Note that since this occurs only for a window, the target is responsible for monitoring the mouse position of the DM_DRAGOVER messages when it is a container for other items. This message is not sent if the object(s) are dropped on the window.
DM_DROP This message is sent to the target window when the user drops the object(s) on it. As with DN_DRAGLEAVE, any target emphasis should be removed ones this message is received. Normally this message is responded to before any data transfer takes place so that the source can learn the window handle of the target.
DM_DROPHELP This message is sent whenever the user presses F1 during a drag operation. The target should respond by displaying help on the actions that would occur if the object(s) were dropped at the point where F1 was pressed.
Whenever a DM_DRAGOVER message is received, the potential target must determine if the drag operation is valid. For example, a C source file could be dropped on a C compiler object, but not a Pascal source file; by holding down the CTRL key, a file could be copied to the printer, but it is (probably) unlikely that a file could be moved to the printer. At a minimum, the following two conditions must be met for a drop to be possible:

1. Both the source and target must understand at least one common type of each object being dragged.
2. Both the source and target must understand at least one common RMF for each object being dragged.

When determining the state of these conditions, the functions DrgVerifyType, DrgVerifyRMF, DrgVerifyTrueType, and DrgVerifyNativeRMF help considerably.

       BOOL      APIENTRY DrgVerifyType(PDRAGITEM pditem, PCSZ  pszType);
       BOOL      APIENTRY DrgVerifyRMF (PDRAGITEM pditem, PCSZ  pszMech,  PCSZ  pszFmt);
       BOOL      APIENTRY DrgVerifyTrueType (PDRAGITEM pditem, PCSZ  pszType);
       BOOL      APIENTRY DrgVerifyNativeRMF(PDRAGITEM pditem, PSZ pszRMF);

In all of these functions, pditem points to the DRAGITEM structure describing the item being tested. pszType specifies the type to compare with. pszMech specifies the rendering mechanism. pszFmt specifies the data format. pszRMF specifies a rendering mechanism and format. All of these functions return TRUE if the condition is met and FALSE if not.
The target responds to the DM_DRAGOVER message with a DOR_ constant.

Table 20.3 DOR_ Constants
Constant | Description
DOR_DROP | Returned whenever the drag is acceptable. This is the only response that can be equated with "Yes, you can drop here".
DOR_NODROP | Returned whenever the location of the object(s) in the target window is unacceptable
DOR_NODROPOP | Returned whenever the operation (copy or move) is unacceptable; this implies that the drag might be valid if the operation is changed.
DOR_NEVERDROP | Returned whenever a drag is never acceptable; no further DM_DRAGOVER messages will be sent to the application until the mouse leaves the window and returns.

| Gotcha! Although the DRAGINFO structure is allocated in shared memory and the pointer is passed to the target, the target cannot access the structure until the DrgAccessDraginfo is called.

#### Data transfer

Okay, let's assume that the user selected one or more objects, depressed the appropriate mouse button, dragged the object(s) over a window, received the feedback that the target is willing to accept the object(s), and let go of the mouse button. What happens next ? The answer to this depends on the RMF chosen to transfer the data with. For example, if DRM_OS2FILE is chosen, the target could choose to render the data itself, or maybe it doesn't know the name of the source data (e.g. for security reasons, the source window didn't fill this in), so it must ask the source window to render the data before it can complete the drop operation.
Let us consider each of the three system-defined rendering mechanisms to see the possible chain of events within each.
DRM_OS2FILE This mechanism would be used to transfer the data via the file system. The data does not have to exist already in this form, but could be placed there by the source after receiving a DM_RENDER message from the target.
If the target understands the native RMF and if the true type of the object, then the target can render the operation without the intervention of the source. However, this might not be feasible; in that case, a DN_RENDER message would need to be sent to the source so that it can perform the operation. (This could occur if the source does not know the name of the file containing the data to be transferred.) If so, the target needs to allocate a DRAGTRANSFER structure (via DrgAllocDragtransfer) and fill in the hstrRenderToName field; the source sends back a DM_RENDERCOMPLETE message to indicate that the operation is done.
DRM_PRINT This mechanism would be used when the data is dropped onto a printer, and should be used only if the source understands and can process the DM_PRINT message that will be sent to it by the target. This message contains the name of the print queue to which the operation is to be performed.

| Gotcha! We have experienced trouble using the pdriv field of the pdosData field of the PRINTDEST structure passed in as a pointer in mpParm2 for the DM_PRINTOBJECT message; the printer consistently rejects the data as being invalid when we call DevOpenDC. Unfortunately, one cannot simply call DevPostDeviceModes (see Chapter 25 for more information) to get a good set of driver data, because the device name is not specified anywhere. The workaround is to call SplQueryQueue first using the queue name in pszLogAddress field of the pdosData field of the PRINTDEST structure to get the PRQINFO3 structure containing the device name.

DRM_DDE This mechanism could be used when the other two do not provide the capability to complete the desired operation. While this is the most flexible of the three mechanisms, it is also the most cumbersome.
The source must understand and be able to process the appropriate WM_DDE_ messages sent to it by the target. Note that a WM_DDE_INITIATE is not required since the target already has the window handle with which it wishes to converse.
Since the topic of DDE could fill an entire chapter by itself, we will not present any more information on this type of data transfer in this chapter.

#### A Concrete Example

A lot of material has been explained so far, and an example is sorely needed to cross the boundary from the abstract to the applied. The following application can act as both source and target for direct manipulation. While it is a simple program, it demonstrates the concepts previously described. DRAG1.C DRAG1.MAK DRAG1.DEF Since main is fairly standard, we'll ignore it except for the fact that we're reserving space for a pointer in a call to WinRegisterClass. This will be used to store a pointer to the client's instance data, so that we can avoid global variables. This instance data is allocated and initialized in the WM_CREATE message and is freed in the WM_DESTROY message.
    typedef struct _CLIENTINFO
    { PDRAGINFO        pdiDrag;
       BOOL             bDragging;
       BOOL             bEmphasis;
       CHAR             achLine[256];
    } CLIENTINFO,*PCLIENTINFO;
The pdiDrag field is used only by the source window and points to the DRAGINFO structure allocated via DrgAllocDraginfo. bDragging and bEmphasis specify whether a dragging operation is in progress and whether the client is displaying emphasis, respectively. achLine is used only by the target window and contains the line of text that was dropped on the window. for clarity, the processing of the direct-manipulation messages has been separated into those usually associated with the source and the target windows. (See doSource and doTarget.) |
What the program does is allow the dragging of text from the left half of the window into either the right half of this window or another instance of this window. (Try starting two copies of DRAG1.EXE to do this.) Whenever the source receives a WM_BEGINDRAG message, the appropriate data structures are initialized and DrgDrag is called. The target adds emphasis whenever it receives a DM_DRAGOVER message and returns the appropriate DOR_ value. After the object has been dropped, the target completely renders the data provided by the source and sends the source a DM_ENDCONVERSATION message to terminate the dragging operation. |

Readers probably are wondering why we return DOR_NODROP from the DRAGOVER message when we find that we cannot accept the drop because the objects are in an unrecognized type or use an unrecognized RMF. It is true that normally DOR_NEVERDROP would be returned, but it must be remembered that we allow dropping only on the right half of the window; once the pointer moves into the left half, we must remove the target emphasis. However, if we return DOR_NEVERDROP, we never receive another DM_DRAGOVER message until the mouse moves out of the window and than back into the window. This technique is required for container window (where container is a concept and does not specify the WC_CONTAINER window class) when the potential targets are not child windows.

| Gotcha! It needs to be stated somewhere, and what a better place than here, that there appears to be a bug in OS/2 Warp when using DRG_BITMAP for the DRAGIMAGE to be displayed. The first time the drag and drop is performed, everything works fine; but if the application is exited and restarted, dragging the object using DRG_BITMAP leaves "mouse droppings" behind, making the display quite ugly. We have no information regarding the availability of a fix.

| Gotcha! Another important item is that the cxOffset and cyOffset fields of the DRAGITEM structure cannot be used for the programmer's own purposes, since DrgDrag copies the corresponding fields from the DRAGIMAGE structure here. Likewise, hwndItem should specify a valid window handle, or unexpected results will occur. Any associated structures that need to be "attached" to a DRAGITEM structure may do so safely by casting the structure to a ULONG and passing the pointer to the ulItemID field.

#### More Cement, Please

Let's complicate things by modifying our program to have the source window render the data.

> DRAG2.C
> DRAG2.MAK
> DRAG2.DEF

As can be seen, the case when the source does not render the data prior to calling DrgDrag is a bit more involved. This is communicated to the target by not specifying the source name in hstrSourceName. After determining that this did not happen, the program allocates another shared structure - DRAGTRANSFER - using a call to DrgAllocDragtransfer and sends the source a DM_RENDER message with the target name in the DRAGTRANSFER structure.

       PDRAGTRANSFER APIENTRY DrgAllocDragtransfer(ULONG cdxfer);

cdxfer specifies the number of structures to allocate and must be greater than 0. It returns a pointer to the array of structures allocated.

       typedef struct _DRAGTRANSFER     /* dxfer */
       {
          ULONG      cb;                     /* size of control block         */
          HWND       hwndClient;             /* handle of target              */
          PDRAGITEM  pditem;                 /* DRAGITEM being transferred    */
          HSTR       hstrSelectedRMF;        /* rendering mech & fmt of choice*/
          HSTR       hstrRenderToName;       /* name source will use          */
          ULONG      ulTargetInfo;           /* reserved for target's use     */
          USHORT     usOperation;            /* operation being performed     */
          USHORT     fsReply;                /* reply flags                   */
       } DRAGTRANSFER;
       typedef DRAGTRANSFER *PDRAGTRANSFER;

cb is the size of the structure in bytes. hwndClient specifies the handle of the window on which the item was dropped. pditem points to the DRAGITEM structure withing the DRAGINFO structure that was passed via the DM_DROP message representing the item of interest.

hstrSelectedRMF specifies a string handle that describes the RMF to use when transferring the item. hstrRenderToName specifies a string handle that describes the name to be used when rendering the data. ulTargetInfo specifies any application-specific data that the target window wishes to communicate to the source. usOperation specifies the operation to use - for example, copy, move, or link. fsReply is filled in by the source window and specifies a DMFL_ constant. Table 20.4 lists the available constants.

Table 20.4 DMFL_ Constants
Constant | Description
DMFL_NATIVERENDER | The source does not support rendering of the object. This should not be specified unless the source gives enough information for the target to perform the rendering
DMFL_RENDERRETRY | The source does support rendering of the object, but not using the RMF specified.

hstrSelectedRMF and hstrRenderToName must have been allocated using the DrgAddStrHandle function.
The obvious question here is why to use DrgSendTransferMsg instead of the old reliable WinSendMsg. The answer is that the DRAGTRANSFER structure, like the DRAGINFO structure, is allocated in shared memory but is not automatically accessible by the other process. The DrgSendTransferMsg ensures that the recipient of the message can access the DRAGTRANSFER message in addition to calling WinSendMsg on behalf of the source.
Resources must be freed via appropriate Drg functions by both the source and target windows, except for of the two HSTR handles in the DRAGTRANSFER structure. The target window is responsible for freeing of these handles.

#### DrgDragFiles

For drag operations involving only files, a much simplified version of DrgDrag can be used: DrgDragFiles.

       BOOL      APIENTRY DrgDragFiles(HWND hwnd,
                                       PCSZ  *apszFiles,
                                       PCSZ  *apszTypes,
                                       PCSZ  *apszTargets,
                                       ULONG cFiles,
                                       HPOINTER hptrDrag,
                                       ULONG vkTerm,
                                       BOOL fSourceRender,
                                       ULONG ulRsvd);

hwnd is the handle of the window calling the function. apszFiles, apszTypes, and apszTargets are array of pointers to the filenames, file types and target filenames, respectively. cFiles specifies the number of pointers in the apszFiles, apszTypes, and apszTargets arrays. hptrDrag is the handle to the pointer to display while dragging. vkTerm has the same meaning as in DrgDrag, discussed earlier. fSourceRender specifies whether the caller needs to render the files before the transfer can take place. If so, a DM_RENDERFILE message is sent for each file.
That's it! The system takes care of the rest, since files are the only allowed object type.

#### From the Top Now

Table 20.5 details the chain of events from the beginning of the drag notification to the end of the data transfer.

Table 20.5 Steps in a Drag/Drop Operation
Step | Source | Target
1 | Receives a WM_BEGINDRAG message |
2 | Allocates the DRAGINFO/DRAGITEM structures using DrgAllocDraginfo |
3 | Creates the strings for the type and RMF using DrgAddStrHandle |
4 | Initializes the appropriate number of DRAGIMAGE structures |
5 | Calls DrgDrag |
6 |  | Receives DM_DRAGOVER
7 |  | Calls DrgAccessDraginfo
8 |  | Decides if object are acceptable (both type ans RMF).
9 |  | Returns the appropriate DOR_ value; if not DOR_DROP, go to step 20.
10 |  | If the user presses F1, target receives a DM_DROPHELP; after providing help, go to step 20
11 |  | If the user presses ESC, go to step 20
12 |  | User drops objects on target.
13 |  | If target can render the objects on its own, do so. Go to step 18
14 |  | Allocates DRAGTRANSFER structures for each object (DrgAllocDragtransfer)
15 | Renders the object |
16 |  | Copies the objects and deletes the from the source.
17 |  | Frees HSTRs for DRAGTRANSFER and DRAGTRANSFER structures (DrgDeleteStrHandle and DrgFreeDragtransfer).
18 |  | Frees HSTRs for DRAGINFO and DRAGINFO structure (DrgDeleteDraginfoStrHandles and DrgFreeDragtransfer).
19 |  | Sends source a DM_ENDCONVERSATION message.
20 | Free HSTR for DRAGINFO and DRAGINFO structure (DrgDeleteDraginfoStrHandles and DrgFreeDragtransfer ). |

#### Pickup and Drop

OS/2 Warp introduced a new twist on the direct manipulation concept. Because drag and drop is a modal operation - meaning that nothing else can occur while a direct manipulation is in progress - it can be limiting at times. What happens if you start to drag an object and then realize that the target window isn't open yet ? You have to press Escape, find the target window and open it, then repeat the operation.
Pickup and drop alleviates the headaches cause in these situations by allowing the user to continue using the mouse in the normal fashion while the operation is in progress. Because of this characteristics, pickup and drop is often referred to as lazy drag and drop.
Obviously, there are some profound differences from the user's perspective between the modal and modeless versions of direct manipulation. And this means that there are differences in the coding of the two types; fortunately, IBM decided in its wisdom to minimize the impact of choosing one or the other (or both) in your application by changing as little as possible in the manner in which the modeless version is coded. The interface differences are listed here:

- The operation is initiated by holding down the Alt key in addition to using direct manipulation mouse button.
- Instead of receiving a WM_BEGINDRAG message, the potential source window receives a WM_PICKUP message.
- Whereas in modal operation all objects to be dragged must be selected before beginning the operation, in pickup and drop, objects can be added to the pickup set dynamically. In OS/2 Warp, however, all objects must originate from the same source window.
- Because the mouse is still usable after the pickup is initiated, the operation can not be ended by releasing the mouse button like the modal operation is ended in this fashion. The only way to end a direct manipulation operation is to call DrgCancelLazyDrag function. And since the user must communicate to the program that the operation is to be canceled, the most common method of indicating this is through a menu item.
- Another change that is related to using the mouse is the use of the DRAGIMAGE structures. Since the operation is modeless, the pointer displayed is still subject to the WinSetPointer function (via the WM_MOUSEMOVE and WM_CONTROLPOINTER messages). Thus, instead of displaying the DRAGIMAGEs provided by the application initiating the operation, the mouse pointer is only slightly augmented to indicate that the operation is in progress. The DRAGIMAGEs structures are still passed to the DrgLazyDrag function for "compatibility" with the parameter list given to DrgDrag but the are not used.
- Because the user could request help for any subject during a lazy drag, the DM_DROPHELP message will not be sent during a lazy drag. Help can only be provided via a menu item, for example, and it is the programmer's responsibility to code this support explicitly.
- Because the operation can potentially take a long time to complete, DrgLazyDrag returns immediately and the source window is sent a DM_DROPNOTIFY message whenever the user "drops" the objects on a target window via some interface (e.g. menu item). This is probably the most significant change of which the programmer needs to be aware.

#### Functions Used for Lazy Drag

In order to make the programmer's job easier, IBM provided many new functions specifically for use with lazy drag.

       PDRAGINFO APIENTRY DrgReallocDraginfo (PDRAGINFO pdinfoOld,  ULONG cditem);

This function reallocates memory to hold a new number of DRAGITEM structures when additional items are to be added to the pickup item set. pdinfoOld points to the old DRAGINFO structure. cditem specifies the new number of DRAGITEM structures to be contained by the new DRAGINFO structure. This function returns a pointer to the new DRAGINFO structure and frees the memory pointed to by the old structure. Once this function is called, DrgLazyDrag must be called again to reinitiate the lazy drag operation.

       PDRAGINFO APIENTRY DrgQueryDraginfoPtr( PDRAGINFO pRsvd );

pRsvd is reserved and must be NULL. This function returns a pointer to the DRAGINFO structure currently in use by a direct manipulation operation. DrgQueryDragStatus must be called to determine what type of operation is in progress, however. If NULL is returned, no operation is in progress.

       PDRAGINFO APIENTRY DrgQueryDraginfoPtrFromDragitem( PDRAGITEM pditem );

pditem points to a DRAGITEM structure returned from DrgQueryDragitemPtr. This function returns a pointer to the DRAGINFO structure with which the DRAGITEM is associated.

       PDRAGINFO APIENTRY DrgQueryDraginfoPtrFromHwnd( HWND hwndSource );

hwndSource is the handle to the source window in a direct manipulation operation. This function returns a pointer to the DRAGINFO structure allocated by the source window.

       ULONG APIENTRY DrgQueryDragStatus(VOID);

This function returns a DGS_ constant specifying what type of drag operation is in progress. Table 20.6 lists the available constants.

Table 20.6 Values of DGS_* Constants
Constant | Description
0 | No direct manipulation operation in progress
DGS_DRAGINFOPROGRESS | Modal operation is in progress
DGS_LAZYDRAGINPROGRESS | Modeless operation is in progress

Note that this function could conceivably be handy for determining whether the "standard" function or the version which replaces it when direct manipulation is in progress should be called, for example, WinGetPS or DrgGetPS.

       BOOL APIENTRY DrgLazyDrag( HWND hwndSource,
                                  PDRAGINFO pdinfo,
                                  PDRAGIMAGE pdimg,
                                  ULONG cdimg,
                                  PVOID pRsvd );

This function initiates a lazy drag operation. hwndSource specifies the source window handle. pdinfo points to the DRAGINFO structure. pdimg points to one or more DRAGIMAGE structures. cdimg specifies the number of DRAGIMAGE structures pointed by pdimg. pRsvd is reserved and must be NULL.

       BOOL APIENTRY DrgLazyDrop( HWND hwndTarget,
    			      ULONG ulOperation,
    		              PPOINTL pptlDrop );

This function is called by a target to complete the lazy drag operation. hwndTarget is the target window handle. ulOperation specifies the operation to be performed and is a D)_ constant. pptlDrop points to a POINTL structure containing the mouse position in desktop-related coordinates. This function returns TRUE if the operation was successfully initiated or FALSE otherwise.

      BOOL APIENTRY DrgCancelLazyDrag( VOID );

This function is used to cancel a lazy drag operation. It returns TRUE if successful, or FALSE otherwise.

| Gotcha! With the DrgQueryDraginfoPtr, DrgQueryinfoPtrFromHwnd and DrgQueryDraginfoPtrFromDragitem functions, the application must still call DrgAccessDragInfo to get access to the structure returned.

| Gotcha! Be sure that if you initiate a lazy drag operation it is either completed or canceled before your application terminates. The authors noticed that when the sample application (see below) was terminated without doing this that the direct manipulation subsystem seemed to get confused and no longer worked correctly.

| Gotcha! The Workplace Shell seems to be able to correctly determine if a lazy drag operation is in progress because it offers a "Cancel drag" menu item on context-sensitive menus. However, selecting the menu item has no apparent effect. We cannot determine why this happens. (?)

#### Lazy Drag Sample

Below is a sample application which demonstrates the use of lazy drag and drop.

> DRAG3.C
> DRAG3.RC
> DRAG3.H
> DRAG3.MAK
> DRAG3.DEF

This sample was based on DRAG1, allowing the target to render the data so that the sample is not burdened with details not necessary to the discussion.
The first difference that you will note are the use of WM_PICKUP instead of WM_BEGINDRAG to begin the operation and the processing of the DM_DROPNOTIFY as the signal of the completion of the operation.

          case  WM_PICKUP :
          case  DM_DROPNOTIFY :
          case  DM_ENDCONVERSATION :
             return  doSource(hwndClient,
                              ulMsg,
                              mpParm1,
                              mpParm2);

Also, since the user must specify to the application that the operation is to be completed or canceled, the WM_CONTEXTMENU, WM_MENUEND, and WM_COMMAND messages are processed to handle the user interface.

          case  DM_DRAGOVER :
          case  DM_DRAGLEAVE :
          case  DM_DROP :
          case  DM_DROPHELP :
          case  MYM_DEWDROP :
          case  WM_CONTEXTMENU :
          case  WM_MENUEND :
          case  WM_COMMAND :
             return  doTarget(hwndClient,
                              ulMsg,
                              mpParm1,
                              mpParm2);

The real work is done in doSource and doTarget, as was the case in the earlier samples.

          case  WM_PICKUP :
             {
                RECTL            rclWindow;
                FILE             *pfFile;
                DRAGITEM         diItem;
                DRAGIMAGE        diImage;
                BOOL             bSuccess;

                if (DrgQueryDragStatus() == DGS_LAZYDRAGINPROGRESS)
                {
                   return  MRFROMSHORT(FALSE);
                }                          /* endif                */

Note how we check for a lazy-drag-in-progress and return immediately if this is true. This was done to keep the sample simple. The processing of WM_PICKUP then continues as it did for WM_BEGINDRAG exept we call DrgLazyDrag instead of DrgDrag.

                bSuccess = DrgLazyDrag(hwndClient,
                                       pciInfo->pdiDrag,
                                       &diImage,
                                       1L,
                                       NULL);

From the target's perspective, we need to provide an interface to the user allow them to complete or cancel the operation. This is done via the WM_CONTEXTMENU, WM_MENUEND, and WM_COMMAND messages.

          case  WM_CONTEXTMENU :
             {  POINTL           ptlPoint;
                RECTL            rclWindow;
                HWND             hwndMenu;

                if (DrgQueryDragStatus() == DGS_LAZYDRAGINPROGRESS)
                {  WinQueryPointerPos(HWND_DESKTOP,
                                      &ptlPoint);

                   WinQueryWindowRect(hwndClient,
                                      &rclWindow);

                   if (ptlPoint.x < rclWindow.xRight/2)
                   {   return  MRFROMSHORT(FALSE);
                   }                       /* endif                */

                   hwndMenu = WinLoadMenu(HWND_OBJECT,
                                          NULLHANDLE,
                                          M_LAZYDRAG);

                   WinPopupMenu(HWND_DESKTOP,
                                hwndClient,
                                hwndMenu,
                                ptlPoint.x,
                                ptlPoint.y,
                                0,
                                PU_MOUSEBUTTON1|PU_KEYBOARD);
                }                          /* endif                */
             }
             break;
          case  WM_MENUEND :
             if (SHORT1FROMMP(mpParm1) == FID_MENU)
             {   WinDestroyWindow(HWNDFROMMP(mpParm2));
             }                             /* endif                */
             break;
          case  WM_COMMAND :
             switch (SHORT1FROMMP(mpParm1))
             {     case  MI_DROP :
                   {  POINTL           ptlPoint;
                      WinQueryPointerPos(HWND_DESKTOP,
                                         &ptlPoint);
                      DrgLazyDrop(hwndClient,
                                  DO_DEFAULT,
                                  &ptlPoint);
                   }
                   break;
                case  MI_CANCELDRAG :
                   DrgCancelLazyDrag();
                   break;
                default  :
                   return  WinDefWindowProc(hwndClient,
                                            ulMsg,
                                            mpParm1,
                                            mpParm2);
             }                             /* endswitch            */
             break;

          default  :
             break;

       }                                   /* endswitch            */

It should be pretty obvious that we are simply providing a popup menu for the user to select one of two choices - drop or cancel - and handling each choice appropriately.
Everything else about this sample is as it was in DRAG1, which demonstrates the ease with which a programmer can switch between using one mode or the other.

Before we close this topic, a question must be asked: how does the target specify whether or not a set of objects that were picked up can be dropped on it or not ? In modal drag and drop, you receive the DM_DRAGOVER and DM_DRAGLEAVE to allow for user feedback, but these messages are not sent automatically by the system when a lazy drag operation is in progress. IBM's documentation states that these messages are sent when the user presses a key indicating that intention to drop the object, but nowhere do they state what this mythical key is. It is the opinion of the authors that this "key" is a concept and not an actual key on the keyboard, and we chose to implement the "key" concept as a popup menu. It is then, therefore, that the target determines the validity of the operation and acts appropriately.

[ Next ] [ Previous ] | Chapter 20 | [ Contents ] [ Chapter 19: Other Window Classes ] [ Chapter 21:Value Set ]