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

  • Live Event Date: August 20, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT When you look at natural user interfaces as a developer, it isn't just fun and games. There are some very serious, real-world usage models of how things can help make the world a better place – things like Intel® RealSense™ technology. Check out this upcoming eSeminar and join the panel of experts, both from inside and outside of Intel, as they discuss how natural user interfaces will likely be getting adopted in a wide variety …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds