When dragging an item it is quite possible that the drop target is not
visible. Sometimes the user can collapse some items so that both the item
to be dragged and the item that is to be the drop target are both visible.
However, the user doesnt always have this recourse. Besides, most professional
apps provide some form of scrolling while the item is being dragged.
In the implementation discussed below, we use a timer to enable us to
drag the outline. We also scroll at varying speed depending on how far
the cursor is from the border of the control.
Step 1: Add member variables to class
We declare two variables. One to hold the timer id and the other to hold
a counter for the timer ticks. You may also choose to declare two more
variable – for the event id that will be generated by the timer and the
timeout value used for the timer. I have used hard coded values of 1 and
75 respectively.
protected: UINT m_nTimerID; UINT m_timerticks;
Step 2: Set timer in TVN_BEGINDRAG handler
We install the timer in the TVN_BEGINDRAG handler. Place the following
statement at the end of you handler function ( I have used OnBeginDrag()
) in previous sections.
// Set up the timer m_nTimerID = SetTimer(1, 75, NULL);
Step 3: Add a WM_TIMER handler
You can use the class wizard to add the WM_TIMER handler. By default this
function is given the name OnTimer(). We first make sure that we are handling
the right timer event. The next task is to update the drag image position.
We then determine whether scrolling is required. The criteria we use
for this is that the cursor should be close to the top edge of the control
or above the control. On the other end, if the cursor is near the bottom
edge of the control or below the control, then we scroll in the other direction.
We then compute a number to control the speed of scrolling. We have used
a hard coded value of six different speed which should be enough. Each
scroll speed range is 20 pixels tall. Ill leave the rest of the logic
to control the scroll speed as an exercise for the reader – you.
You may notice that the call to DragMove() uses screen coordinates.
The DragMove() function needs the screen coordinates if the call to DragEnter()
when you began the drag operation had associtated the drag image to
the desktop window. Use the client coordinates if the call to DragEnter()
had specified the treeview control.
Before we go on the next step, Id like to discuss the behaviour of
GetVisibleCount(). This function is supposed to return the number of visible
items but instead it returns the maximum number of items that can be visible
at one time. We therefore make a check for NULL before calling SelectDropTarget().
void CTreeCtrlX::OnTimer(UINT nIDEvent) { if( nIDEvent != m_nTimerID ) { CTreeCtrl::OnTimer(nIDEvent); return; } // Doesn't matter that we didn't initialize m_timerticks m_timerticks++; POINT pt; GetCursorPos( &pt ); RECT rect; GetClientRect( &rect ); ClientToScreen( &rect ); // NOTE: Screen coordinate is being used because the call // to DragEnter had used the Desktop window. CImageList::DragMove(pt); HTREEITEM hitem = GetFirstVisibleItem(); if( pt.y < rect.top + 10 ) { // We need to scroll up // Scroll slowly if cursor near the treeview control int slowscroll = 6 - (rect.top + 10 - pt.y) / 20; if( 0 == ( m_timerticks % (slowscroll > 0? slowscroll : 1) ) ) { CImageList::DragShowNolock(FALSE); SendMessage( WM_VSCROLL, SB_LINEUP); SelectDropTarget(hitem); m_hitemDrop = hitem; CImageList::DragShowNolock(TRUE); } } else if( pt.y > rect.bottom - 10 ) { // We need to scroll down // Scroll slowly if cursor near the treeview control int slowscroll = 6 - (pt.y - rect.bottom + 10 ) / 20; if( 0 == ( m_timerticks % (slowscroll > 0? slowscroll : 1) ) ) { CImageList::DragShowNolock(FALSE); SendMessage( WM_VSCROLL, SB_LINEDOWN); int nCount = GetVisibleCount(); for ( int i=0; i<nCount-1; ++i ) hitem = GetNextVisibleItem(hitem); if( hitem ) SelectDropTarget(hitem); m_hitemDrop = hitem; CImageList::DragShowNolock(TRUE); } } }
Step 4: Kill the timer when dragging finishes
Call KillTimer( m_nTimerID ) whenever the drag operation finishes. This
would include the PreTranslateMessage() if you are cancelling the drag
on an escape key and OnLButtonUp().