August 1998
Download Aug98Wicked.exe (22KB)
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.
|
Lately I've been traveling a lot.
When I travel, I meet with other
developers of Windows®-based
applications and frequently find myself fielding programming questions. In recent months, one question has popped up so many times that I decided I'd better look into it. The question is: how do I create a tree view control that supports drag and drop?
Now, I confess that I always thought tree view drag and drop was no big deal. After all, a tree view sends its parent a TVN_ BEGINDRAG notification when a dragging operation is begun with the left mouse button, so the control must support drag and drop, right? Well, yes and no. It turns out that there's a lot more to supporting drag and drop in a tree view control than meets the eye. As an experiment, I set out to create a control that mimics the drag and drop behavior of the tree view in Windows Explorer. A job that I figured would take an hour or two ended up requiring the better part of two days. I spent much of that time trying to decipher poorly written SDK documentation and figuring out what functions to call and when. The rest of the time was spent adding needed bells and whistles to make the resulting control as user friendly as possible. |
|
Figure 1 TreeDemo |
CDragDropTreeCtrl's source code is shown in Figure 2. You can use it as is, modify it, or use it as a base class for classes of your own. The remainder of this column discusses key aspects of CDragDropTreeCtrl's source code and operation.
Drag and Drop Basics
CDragDropTreeCtrl's source code is shown in Figure 2. OnBeginDrag handles TVN_BEGINDRAG notifications reflected by the control's parent. It performs all of the steps listed previously. It also caches the handle of the item being dragged in a data member so it can be retrieved when the drop is executed. Here's the pertinent code: |
|
m_pImageList is a CDragDropTreeCtrl member variable that stores a pointer to the temporary CImageList object created by CreateDragImage. It's your responsibility to delete that object when it's no longer needed, so CDragDropTreeCtrl::OnLButtonUp deletes it when the mouse button is released, signifying the end of the drag and drop operation.
Once dragging has begun, the control must respond to WM_MOUSEMOVE messages by erasing the previous drag image, drawing a new drag image at the current cursor location, and highlighting the item under the cursor to provide visual feedback to the user. In an SDK-style application, you'd have to subclass the control in order to see its WM_MOUSEMOVE messages. In an MFC-based application, you can simply write the message handler into the derived control class, attach an object of that class to the control, and allow MFC to do the subclassing. Erasing and redrawing the drag image is easy; CImageList::DragMove will do both. Highlighting the drop target is simple, too. First you call CTreeCtrl::HitTest to find out which item (if any) the cursor is over. Then you call CTreeCtrl::SelectDropTarget to highlight the item. CDragDropTreeCtrl's OnMouseMove function accomplishes all this with two lines of code: |
|
HighlightDropTarget is a helper function that turns the cursor location into an HTREEITEM and passes it to SelectDropTarget. To avoid painting problems, the drag image should be hidden before SelectDropTarget is called. That's why HighlightDropTarget sandwiches calls to SelectDropTarget between calls to CImageList::DragShowNolock.
A drag and drop operation ends when the mouse button is released. Unfortunately, a tree view control doesn't send its parent a notification when this happens. Therefore, you must provide a WM_LBUTTONUP handler that determines which item (if any) the cursor is over, then executes a drop. The message handler should also perform certain housekeeping chores such as erasing the last drag image, releasing the mouse, and deleting the temporary image list. In CDragDropTreeCtrl, these steps are performed by the following statements in OnLButtonUp: |
|
Note the calls to CImageList::DragLeave and CImageList::EndDrag to clean up the drag image, and the call to MoveTree to move the dragged item to its new location. MoveTree is a CDragDropTreeCtrl helper function that moves a specified item and its subitems to another part of the tree. MoveTree calls CopyTree, which in turn relies on a recursive function named CopyChildren to enumerate the item's subitems and copy them to the destination.
Automatic Scrolling
|
|
Initially, the timer is set to fire after 500 milliseconds. If the cursor moves before the timer fires, the timer is reset. If, however, the cursor remains motionless until the timer interval expires, a WM_TIMER message ensues and CDragDropTreeCtrl::OnTimer scrolls the window by sending it a WS_VSCROLL message: |
|
OnTimer also programs the timer to fire again after 200 milliseconds so that subsequent scrolls will happen more quickly. The messages don't stop until the cursor is moved or the tree view is scrolled as far as it possibly can. To adjust the 500 millisecond delay before the first scroll and the 200 millisecond interval between consecutive scrolls, tweak CDragDropTreeCtrl's m_nDelayInterval and m_nScrollInterval data members.
Automatic Branch Expansion
Have a tricky issue dealing with Windows? Send your questions via email to Jeff Prosise: JeffPro@msn.com |
From the August 1998 issue of Microsoft Systems Journal.