Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > August 1997
August 1997


Code for this article: Wicked0897.exe (38KB)
Jeff Prosise is the author of Programming Windows 95 with MFC (Microsoft Press, 1996). He also teaches Visual C++®/MFC programming seminars. For more information, visit http://www.solsem.com.

There's an old axiom in the software industry that programmers won't switch tools unless there's a compelling reason for them to do so. Lately, more and more Windows® SDK programmers are migrating to Visual C++® and MFC for one reason and one reason only: MFC vastly simplifies the process of writing COM, OLE, and ActiveX applications. Writing COM, OLE, and ActiveX code without a good class library is about as much fun as a root canal. Maybe you enjoy having holes carved in your jawbone, but me, I'll take the easy way out every time. I'd no sooner tackle ActiveX without MFC than say no when my dentist offers me the loopy gas.
MFC isn't the best tool available for writing simple COM servers and clients, but it really shines when it comes to writing OLE and ActiveX document servers, Automation servers, and other applications that rely on the myriad protocols built on top of COM. MFC also makes it relatively easy to write applications that exchange data through the OLE clipboard and OLE drag and drop. Building a modern user interface means supporting drag and drop whenever and wherever possible. Even novice users find grabbing an object with the mouse and dragging it across the screen to be a clean, easy-to-execute, and for the most part intuitive operation.
This month, I'd like to share a fun little programming technique that will help you write better drag and drop code. The sample application I'll present uses MFC, but the technique isn't specific to MFC; it's applicable to any language or development environment.
You've all seen drag and drop implementations that tack a little rectangle onto the cursor to represent whatever it is that's being dragged. That's good—for wimps. A really cool application draws an outline of the object (or objects) being dragged and moves the outline with the cursor. A good example of this behavior can be seen in the Microsoft Windows
® 95 and Windows NT® 4.0 shells. Open a Windows Explorer window, group-select a bunch of files, and then drag the files to another folder. The shell paints a clear picture of what's being dragged with a wireframe drag image silhouetting the files you selected (see Figure 1).
Figure 1: Dragging files in Windows 95
Figure 1: Dragging files in Windows 95

I've received a number of email messages in recent months asking how the shell draws the drag image. Common sense says that there's more to it than simply changing the cursor image because a cursor image is limited to 32 X 32 pixels. I can't tell you exactly how the shell does it because I haven't seen the source code, but I can tell you how I've achieved a similar effect in my own applications. The secret is to let the drop target (not the drop source, which is where most people assume the image originates) draw the drag image. How? By including information describing how to draw the image in the OLE data object that links the drop source to the drop target. This information can take any form you'd like: a series of x-y coordinates describing the endpoints of lines, a list of private metacommands, or even a handle to an enhanced metafile. When the drop target's IDropTarget::OnDragEnter function is called, the drop target can extract the information from the data object and use it to draw a drag image.
In the sample application presented in the next section, the drop target draws a drag image by creating a temporary object just like the one that's being dragged and passing a CDC pointer (MFC's equivalent of a device context handle) to the object's DrawDragImage function. In other words, the object draws its own drag image. It makes sense when you think about it, because who knows what the object looks like better than the object itself?

The WIDGET Application

WIDGET is an SDI doc/view application that lets you create widgets of various shapes and colors (see Figure 2). Widgets are created with commands in the application's Insert menu. Once created, they can be moved and copied using drag and drop. When a widget is dragged, WIDGET draws a drag image depicting the widget's shape, which could be a circle, a triangle, or a square. You can drop a widget inside the application that created it or in another instance of WIDGET. To demonstrate, start two copies of WIDGET and insert a widget or two in instance A. Then grab a widget in instance A and release it over instance B. As if by magic, the widget will disappear from A and appear in B. (To copy a widget, repeat this procedure with the Ctrl key held down.) One of the strengths of OLE's Uniform Data Transfer (UDT) model is that it is not limited by process boundaries. OLE drag and drop is a subset of UDT.
Figure 2:  Dragging a Widget
Figure 2: Dragging a Widget

The code that makes all this work will seem pretty straightforward if you're at all familiar with OLE drag and drop as implemented by MFC. WIDGET is both an OLE drop source and an OLE drop target. When a widget is clicked with the left mouse button, the view's OnLButtonDown handler initializes a global memory block (HGLOBAL) with data describing the widget. Then it creates a COleDataSource object, transfers the HGLOBAL to the data source by calling COleDataSource::CacheGlobalData, and calls COleDataSource::DoDragDrop to initiate a drag and drop data transfer. If DoDragDrop returns DROPEFFECT_ MOVE, indicating the widget was moved rather than copied, WIDGET deletes the widget from the document.
On the flip side of the data transfer, WIDGET's view registers itself as a drop target by creating a COleDropTarget object and calling COleDropTarget::Register. Calls to the drop target's IDropTarget functions generate calls to the view's OnDragEnter, OnDragOver, OnDragLeave, and OnDrop functions. WIDGET's CWidgetView class implements OnDrop by calling COleDataObject::GetGlobalData to retrieve the HGLOBAL created by the drop source and calling the document's AddWidget function to create a new widget from the data in the HGLOBAL.
When an object is transferred through OLE drag and drop, its type is identified with a clipboard format code. WIDGET uses ::RegisterClipboardFormat to register a private clipboard format for widgets. The format's integer ID is stored in a public member variable named m_nFormat in the application object, and retrieved through the pointer returned by AfxGetApp.
You can see how all this is implemented by browsing the source code for WIDGET's view class, which is reproduced along with other pertinent parts of the application's source code in Figure 3. However, what's more interesting is how WIDGET draws drag images. As you drag a widget over a WIDGET window, notice that an outline of the widget travels with the cursor. The code responsible for drawing the drag image lies partly in the view class and partly in the classes that implement the widgets themselves.
A good place to begin examining the code is with the data structure WIDGET uses to describe a widget object:

 typedef struct tagWIDGETDATA {
     int nType;
     RECT rcItem;
     POINT ptDrag;
 } WIDGETDATA;
The type field holds 0, 1, or 2, identifying the widget as a circle, triangle, or square. rcItem holds the coordinates of the widget's bounding rectangle, and ptDrag holds the coordinates of the drag point—the point at which the widget is grabbed to begin a drag and drop operation. The difference between the drag point and the upper-left corner of the bounding rectangle is used as an offset from the current cursor location whenever WIDGET draws a drag image.
When a widget is clicked, the OnLButtonDown code in the view allocates a global memory block and fills it with a WIDGETDATA structure describing the widget. When a drop occurs, the view's OnDrop handler uses the information in the structure to create a new widget. However, before the drop occurs, the view's OnDragEnter function, which is called when the cursor enters the window during a drag operation, uses the information in the WIDGETDATA structure to create a temporary widget. The temporary widget's address is tucked away in the view's m_pWidget data member, as shown in Figure 4.
Why create a temporary widget when the cursor enters the window? Because widget objects have DrawDragImage functions that draw widget outlines by inverting pixels on the screen. As the cursor is moved over the view during a drag and drop operation, the view's OnDragOver function is called repeatedly. OnDragOver calls the temporary widget's DrawDragImage function twice—once to erase the previous drag image and once to draw the image in the new location. Thus, the drag image follows the cursor around the window, and it's the widget itself—a copy of it, anyway—that does the actual drawing. Calls to DrawDragImage, of course, go through the pointer stored in m_pWidget. The temporary widget is deleted when the view's OnDragLeave function is called, indicating that the cursor left the window.
That's really all there is to it. The key (again) is that the drop target, not the drop source, does the drag imaging. Since the drop target gets called each time the cursor moves over its window, it's a simple matter for it to grab a screen DC and update a drag image. All it needs to know is what to draw and, as WIDGET demonstrates, that information can be provided either directly or indirectly by the drop source. I suspect that, if you could peel the cover off the Microsoft Windows 95 and Windows NT 4.0 shell and look at the source code, you'd find that it does something very similar to what I've done here.

Your Needs, Your Ideas

Are there tough Win32-based programming questions you'd like to see answered in this column? If so, mail them to me at the address listed below. I regret that time doesn't permit me to respond individually to all questions, but rest assured that I'll read each and every one and consider all for inclusion in a future installment of Wicked Code.

Have a tricky issue dealing with Windows? Send your questions via email to Jeff Prosise: jeffpro@msn.com

From the August 1997 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1997 Microsoft Corporation. All rights reserved. Legal Notices.

© 2016 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft