Scrolling during drag and drop


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().
 



Comments

  • nearly correct but found a small error

    Posted by Legacy on 04/24/2002 12:00am

    Originally posted by: Laz


    The scroll works great but the dragged image appears to be way offline heres a 3 line fix to get that working aswell with your code around it so everyone can see where it should be included:-

    if( nIDEvent != m_nTimerID )
    {
    //CTreeCtrl::OnTimer(nIDEvent);
    CDTLUIXTreeViewEx::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.

    ////////fix I added
    // Basically you need to convert the point co-ordinates to
    // screen ones to line up the dragged image properly,
    // note I used a copy so that I don't have to edit
    // your scroll code.

    POINT pttemp = POINT(pt);
    ClientToScreen( &pttemp );
    CImageList::DragMove(pttemp);

    /////// end of fix

    HTREEITEM hitem = GetTreeCtrl().GetFirstVisibleItem();

    if( pt.y < rect.top + 10 )

    .
    .
    .


    Reply
  • Another Look at scroll during Drag and Drop

    Posted by Legacy on 05/10/2001 12:00am

    Originally posted by: Srinivasan Seetharaman

    
    


    Following code in my program works for the auto-scroll during drag and drop over my TreeView.


    DROPEFFECT CMyTree::OnDragOver(CWnd* pWnd, COleDataObject*
    pDataObject, DWORD dwKeyState, CPoint point )
    {

    UINT flag;
    flag=TVHT_ONITEM;
    HTREEITEM item=GetTreeCtrl().HitTest(point,&flag);
    GetTreeCtrl().SelectDropTarget(item);


    HTREEITEM prev=GetTreeCtrl().GetPrevVisibleItem(item);
    HTREEITEM next=GetTreeCtrl().GetNextVisibleItem(item);;
    HTREEITEM first=GetTreeCtrl().GetFirstVisibleItem();

    GetTreeCtrl().EnsureVisible(prev);

    GetTreeCtrl().EnsureVisible(next);


    if((dwKeyState&MK_CONTROL) == MK_CONTROL)
    return DROPEFFECT_COPY;
    else
    return DROPEFFECT_MOVE;


    }

    Reply
  • the item dragged is blinking

    Posted by Legacy on 03/16/2001 12:00am

    Originally posted by: Oliv'

    When you move an item, the call of CImageList::DragMove in OnTimer make the item blink.
    When You stop it on tree, the item disapper

    So it's better to declare a member POINT pt , updating it with a DragMove(pt) call in OnMouseMove, instead of updating it in the OnTimer event.


    Reply
  • Some improvements

    Posted by Legacy on 11/15/1999 12:00am

    Originally posted by: Geert Delmeiren

    I improved the code slightly:
    
    

    1) For deeply nested trees left/right scrolling might be handy.
    (Only if the cursor is in a small zone IN the tree control)
    2) Don't send scroll messages if not needed
    3) I prefer only scrolling up/down if cursor is right above/beneath the tree control

    This is the changed code:

    with SCROLL_BORDER 10
    and SCROLL_SPEED_ZONE_WIDTH 20

    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 );
    CRect rect;
    GetClientRect( &rect );
    ClientToScreen( &rect );

    // NOTE: Screen coordinate is being used because the call
    // to DragEnter had used the Desktop window.
    CImageList::DragMove(pt);

    int iMaxV = GetScrollLimit(SB_VERT);
    int iPosV = GetScrollPos(SB_VERT);
    // The cursor must not only be SOMEWHERE above/beneath the tree control
    // BUT RIGHT above or beneath it
    // i.e. the x-coordinates must be those of the control (+/- SCROLL_BORDER)
    if ( pt.x < rect.left - SCROLL_BORDER )
    ; // Too much to the left
    else if ( pt.x > rect.right + SCROLL_BORDER )
    ; // Too much to the right
    else if( (pt.y < rect.top + SCROLL_BORDER) && iPosV )
    {
    HTREEITEM hitem = GetFirstVisibleItem();
    // We need to scroll up
    // Scroll slowly if cursor near the treeview control
    int slowscroll = 6 - (rect.top + SCROLL_BORDER - pt.y) / SCROLL_SPEED_ZONE_WIDTH;
    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 - SCROLL_BORDER) && (iPosV!=iMaxV) )
    {
    // We need to scroll down
    // Scroll slowly if cursor near the treeview control
    int slowscroll = 6 - (pt.y - rect.bottom + SCROLL_BORDER ) / SCROLL_SPEED_ZONE_WIDTH;
    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);
    }
    }

    // The cursor must be in a small zone IN the treecontrol at the left/right
    int iPosH = GetScrollPos(SB_HORZ);
    int iMaxH = GetScrollLimit(SB_HORZ);

    if ( !rect.PtInRect(pt) ) return; // not in TreeCtrl
    else if ( (pt.x < rect.left + SCROLL_BORDER) && (iPosH != 0) )
    {
    // We need to scroll to the left
    CImageList::DragShowNolock(FALSE);
    SendMessage(WM_HSCROLL, SB_LINELEFT);
    CImageList::DragShowNolock(TRUE);
    }
    else if ( (pt.x > rect.right - SCROLL_BORDER) && (iPosH != iMaxH) )
    {
    // We need to scroll to the right
    CImageList::DragShowNolock(FALSE);
    SendMessage(WM_HSCROLL, SB_LINERIGHT);
    CImageList::DragShowNolock(TRUE);
    }
    }

    Reply
  • ActiveX Control

    Posted by Legacy on 08/04/1999 12:00am

    Originally posted by: Tim

    Where do I get the ActiveX control to be able to view treeview control on my web pages? Any info would be great.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Today's agile organizations pose operations teams with a tremendous challenge: to deploy new releases to production immediately after development and testing is completed. To ensure that applications are deployed successfully, an automatic and transparent process is required. We refer to this process as Zero Touch Deployment™. This white paper reviews two approaches to Zero Touch Deployment--a script-based solution and a release automation platform. The article discusses how each can solve the key …

  • On-demand Event Event Date: December 18, 2014 The Internet of Things (IoT) incorporates physical devices into business processes using predictive analytics. While it relies heavily on existing Internet technologies, it differs by including physical devices, specialized protocols, physical analytics, and a unique partner network. To capture the real business value of IoT, the industry must move beyond customized projects to general patterns and platforms. Check out this webcast and join industry experts as …

Most Popular Programming Stories

More for Developers

RSS Feeds