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:
- Both the source and target must understand at least one common type of each object being dragged.
- 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.