Topic : Common Controls
Author : Unknown
Page : << Previous 9  Next >>
Go to page :

the content of pResult, the edit will stop. Otherwise the label editing will go on as usual.

Using the New Class

In the new sample, variable CCCtlDlg::m_treeCtrl is declared by class MCTreeCtrl instead of CTreeCtrl. First the header file that contains class MCTreeCtrl is included in file "CCtlDlg.h", then the declaration of member variable CCCtlDlg::m_treeCtrl is changed:

class CCCtlDlg : public CDialog





MCTreeCtrl m_treeCtrl;

That's all we need to do in order to add new features to the sample.

When editing a label, we can not press RETURN key to end the editing. This is because in a dialog box, RETURN is used to close the dialog box by default. If we want to change this feature, we need to override function CDialog::PreTranslateMessage(...) and intercept RETURN key stroke messages as we did for combo box in sample 9\CCtl.

14 Drag-n-Drop

Another nice feature we can add to tree control is to change the tree structure by dragging and dropping. By implementing this, we can copy or move one node (and all its child nodes) to another place with few mouse clicks.

Sample 14\CCtl demonstrates drag-n-drop implementation. It is base on sample 13\CCtl with new messages handled in class MCTreeCtrl.

Handling New Messages

To implement node dragging and dropping, we need to handle the following three messages: TVN_BEGINDRAG, WM_MOUSEMOVE and WM_LBUTTONUP. The first message is sent when the user starts node dragging. After receiving this message, we need to prepare node dragging. Message WM_MOUSEMOVE should be handled when an item is being dragged around: when the mouse cursor hits a possible target node, we need to highlight it to remind the user that the source node could be dropped here. When we receive message WM_LBUTTONUP, we need to check if the node can be copied to the new place, if so, we need to implement node copy (or move).

In the sample, message handlers of TVN_BEGINDRAG, WM_MOUSEMOVE and WM_LBUTTONUP are added through using Class Wizard. The default TVN_BEGINDRAG message handler has the following format:

void MCTreeCtrl::OnBegindrag(NMHDR *pNMHDR, LRESULT *pResult)





Here, several issues must be considered when a node is being dragged around:

1) To determine which node is being clicked for dragging after receiving message TVB_BEGINDRAG, we can call API function ::GetCursorPos(...) to retrieve the current position of mouse cursor, call function CWnd::ScreenToClient(...) to convert its coordinates, and call CTreeCtrl::HitTest(...) to obtain the handle of the node that is being clicked.

2) We must provide a dragging image that will be drawn under the mouse cursor to give the user an impression that the node is being "dragged". An easiest way of preparing this image is to call function CTreeCtrl::CreateDragImage(...), which will create dragging image using the bitmap associated with this node. This function will return a CImageList type pointer, which could be further used to implement dragging. We can also create our own customized image list for dragging operation, the procedure of creating this type of image list is the same with creating a normal image list.

3) We can call function CImageList::SetDragCursorImage(...) to combine an image contained in the image list with the cursor to begin dragging.

4) We must lock the tree control window when a node is being dragged around to avoid any change happening to the tree structure (When a node is being dragged, the tree should not change). When we want to do a temporary update (For example, when the dragging image enters a node and we want to highlight that node to indicate that the source can be dropped there), we must first unlock the window, then implement the update. If we want the dragging to be continued, we must lock the window again.

5) Function CImageList::EnterDrag(...) can be called to enter dragging mode and lock the tree control window. Before we make any change to the tree control window (For example, before we highlight a node), we need to call function CImageList::LeaveDrag(...) to unlock the tree control window. After the updates, we need to call CImageList::EnterDrag(...) again to lock the window. This will prevent the tree control from being updated when a node is being dragged around.

6) We can show or hide the dragging image by calling function CImageList::DragShowNolock(...) without locking the tree control window. This function is usually called before CImageList::SetDragCursorImage(...) is called.

7) To begin dragging, we need to call CImageList::BeginDrag(...); to move the dragging image to a specified position, we can call CImageList::DragMove(...); to end dragging, we need to call CImageList::EndDrag().

8) We can highlight a node by calling function CTreeCtrl::SelectDropTarget(...).

The following is a list of prototypes of the above-mentioned functions:

BOOL CImageList::DragShowNolock(BOOL bShow);

(Table omitted)

BOOL CImageList::BeginDrag(int nImage, CPoint ptHotSpot);

(Table omitted)

BOOL CImageList::DragMove(CPoint pt);

(Table omitted)

BOOL CImageList::DragEnter(CWnd *pWndLock, CPoint point);

(Table omitted)

BOOL CImageList::DragLeave(CWnd *pWndLock);

(Table omitted)

BOOL CTreeCtrl::SelectDropTarget(HTREEITEM hItem);

(Table omitted)

When the mouse button is released, we need to check if the source node can be copied to the target node. In the sample, we disable copying under the following three conditions: 1) The source node is the same with the target node. 2) The target node is a descendent node of the source node. 3) The target node does not have any child node. By setting these restrictions, a node can only be copied to become the child of its parent node (direct or indirect).

We can use function CTreeCtrl::GetParentItem(...) to decide if one node is the descendent of another node:

HTREEITEM CTreeCtrl::GetParentItem(HTREEITEM hItem);

This function will return an HTREEITEM handle, which specifies the parent of node hItem. By repeatedly calling this function we will finally get a NULL return value (This indicates that the root node was encountered). Using this method, we can easily find out a list of all nodes that are parents of a specific node.

New Member Variables and Functions

To implement drag-n-drop, several new variables and functions are declared in class MCTreeCtrl:

(Code omitted)

Here, Boolean type variable m_bIsDragging is used to indicate if the drag-n-drop activity is undergoing. Pointer m_pilDrag will be used to store the dragging image. Variables m_hTreeDragSrc and m_hTreeDragTgt are used to store the handles of source and target nodes respectively. We can use them to implement copying right after the source node is dropped. Function MCTreeCtrl::IsDescendent(...) is used to judge if one node is the descendent node of another, and MCTreeCtrl::CopyItemTo(...) will copy one node (and all its descendent nodes) to another place.

Node Copy

When copying a node, we want to copy not only the node itself, but also all its descendent nodes. Since we do not know how many descendents a node have beforehand, we need to call function MCTreeCtrl::CopyItemTo(...) recursively until all the descendent nodes are copied. The following is the implementation of this function:

(Code omitted)

This function copies node hTreeDragSrc along with all its descendent nodes, and make them the child nodes of hTreeDragTgt. First we call function CTreeCtrl::GetItem(...) to retrieve source node's information. We must pass a TV_ITEM type pointer to this function, and the corresponding object will be filled with the information of the specified node. Here, we use member item of structure TV_INSERTSTRUCT to receive a node's information (Variable tvInsertStruct is declared by TV_INSERTSTRUCT, it will be used to create new nodes). When calling this function, member mask of TV_ITEM structure specifies which member should be filled with the node's information. In our case, we want to know the handle of this node, the associated images, the text of the label, the current state (expanded, highlighted, etc.), and if the node has any child node. So we need to set the following bits of member mask: TVIF_CHILDREN, TVIF_HANDLE, TVIF_IMAGE, TVIF_SELECTEDIMAGE, TVIF_TEXT and TVIFF_STATE. Note we must provide our own buffer to receive the label text. In the function, szBuf is declared as a char type array and its address is stored in pszText member of TV_ITEM. Then we use tvInsertStruct to create a new node. Since we have already stuffed item member with valid information, here we only need to assign the target handle (stored in hTreeDragTgt) to hParent, and assign TVI_LAST to hInsertAfter. This will make the new node to become the child of the target node, and be added to the end of all child nodes under the target node. Next we check if this node has any child node. If so, we find out all the child nodes and call this function recursively to copy all the child nodes. For this step, we use the newly created node as the target node, this will ensure that the original tree structure will not change after copying.

In the final step, we call function CTreeCtrl::GetChileItem(...) to find out a node's first child node, then call function CTreeCtrl::GetNextitem(...) repeatedly to get the rest child nodes. The two functions will return NULL if no child node is found.


Now we need to implement TVN_BEGINDRAG message handler. First, we need to obtain the node that was clicked by the mouse cursor. To obtain the current position of mouse cursor, we can call API function ::GetCursorPos(...). Since this position is measured in the screen coordinate system, we need to further call function CWnd::ScreenToClient(...) to convert the coordinates to the coordinate system of the tree control window. Then we can set variable

Page : << Previous 9  Next >>