Stop auto-selection of item

I've been playing around with the new common controls that are distributed with IE 4.0 and one of the neat features is the new style TVS_TRACKSELECT. Basically what it does is underline the current item and then select it if you leave it there long enough. Now to me this is annoying. I don't mind it telling me where I am but selecting it automatically for me I don't like ( mainly because most trees I've seen do something when an item is selected ). So I implemented hot tracking in a CTreeCtrl derived class. Hot tracking is basically underlining the item you are on, but not selecting it.

I was going to implement a state and then stick it in the LPARAM, but I decided to use a state flag instead. So I defined this flag...

#define TVIS_UNDERLINE 0x0001

Now you might be wondering why I used 0x0001, believe it or not, this is the only available state flag left to the tree control. It might be used internally, but I have seen no side effects.

The first thing that must be done is setting the state of the item...we do that in OnMouseMove...shown here...
void CTreeCtrlEx::OnMouseMove(UINT nFlags, CPoint point) 
{
        TV_HITTESTINFO hit;
        ::ZeroMemory(&hit,sizeof(hit));
        
        hit.pt = point;
        HitTest( &hit );

        //Did we hit an item??
        if( hit.hItem )
        {
                //Yep...now check to make sure we hit on the item not beside it
                if( hit.flags & TVHT_ONITEM )
                        //Yep...underline it...
                        SetItemState( hit.hItem, TVIS_UNDERLINE, TVIS_UNDERLINE );
                else
                        //Nope we sure didn't...since we might have
			//moved the mouse to left or right after
                        //underlining it previously...go ahead and undo
			//the underlining
                        SetItemState( hit.hItem, 0, TVIS_UNDERLINE );
        }

        //Must unconditionally check the last item or underline will stay around
        // after we move off the last underlined item...
        if( m_htiLast != hit.hItem )
        {
                SetItemState( m_htiLast, 0, TVIS_UNDERLINE );
        }

        //Cache the last item hit...
        m_htiLast = hit.hItem;

        UINT uID = SetTimer( MOUSE_MOVE, 55, NULL );
        ASSERT( uID == MOUSE_MOVE );

        CTreeCtrl::OnMouseMove(nFlags, point);
}

There ya go...item is now ready to be underlined. Now...you might be asking...what happens if the first visible item ( which I define as being right at the top of the control )is underlined and my user moves the mouse out of the client area? Won't the item still be underlined? The answer - yes it will...unless you tell it not to :-)

There are 2 ways you can tell it not to hilight the last item underlined...

1) handle WM_NCMOUSEMOVE and undo the underlining of the the item if you move over NC area.

2) Start a timer in WM_MOUSEMOVE ( I actually did both. You know...just to make sure...) and check the point of the message. If it is in the rect...do nothing. If it isn't in the rect...loop through the visible items and setting the underline state flag to 0. Here is my implementation of both WM_NCMOUSEMOVE and WM_TIMER.

void CTreeCtrlEx::OnTimer(UINT nIDEvent) 
{
        if( nIDEvent == MOUSE_MOVE )
        {
                KillTimer(MOUSE_MOVE);          
                
                CPoint pt(::GetMessagePos());
                CRect rc;
                GetWindowRect(&rc);

                if( rc.PtInRect(pt) == FALSE )
                {
                        HTREEITEM hti = GetFirstVisibleItem();

                        while( hti != NULL )
                        {
                                if( GetItemState( hti, TVIS_UNDERLINE ) & TVIS_UNDERLINE )
                                        SetItemState( hti, 0, TVIS_UNDERLINE );
                                hti = GetNextVisibleItem( hti );
                        }
                }
                return;
        }

        CTreeCtrl::OnTimer(nIDEvent);
}

void CTreeCtrlEx::OnNcMouseMove(UINT nHitTest, CPoint point) 
{
        //Since most tree controls have that nice client edge border.
	//If the user moves the mouse from
        //bottom to top, after passing over the first visible item will
	//leave the underline.  So we have to 
        //handle WM_NCMOUSEMOVE for when it goes over the border of the
	//tree control...
        if( nHitTest == HTBORDER )
        {
                HTREEITEM hti = GetFirstVisibleItem();
                if( GetItemState( hti, TVIS_UNDERLINE) & TVIS_UNDERLINE )
                        SetItemState( hti, 0, TVIS_UNDERLINE );
        }

        CTreeCtrl::OnNcMouseMove(nHitTest, point);
}

In the above code, I could unconditionally set the item state, but SetItem ( which is called internally by SetItemState ) triggers an invalidate, and there is no reason to redraw if we don't have to.

Now that we are setting the state properly...how do we go about drawing the underline? Simple...do it the same way that another sample does it. That sample that I based my drawing code on is - Setting Color and Font Attributes for individual items.

Here is my WM_PAINT handler...

void CTreeCtrlEx::OnPaint() 
{
        CPaintDC paintDC(this);

        CRect clientRect(-1,-1,-1,-1);
        GetClientRect(&clientRect);

        CDC memDC;
        memDC.CreateCompatibleDC( &paintDC );

        CBitmap canvas, *pOldCanvas = NULL;
        canvas.CreateCompatibleBitmap( &paintDC, clientRect.Width(), clientRect.Height() );
        pOldCanvas = memDC.SelectObject( &canvas );

        CRect clipBox(-1,-1,-1,-1);
        paintDC.GetClipBox(&clipBox);

        CRgn clipRgn;
        clipRgn.CreateRectRgnIndirect(&clipBox);
        memDC.SelectClipRgn( &clipRgn );

        CWnd::DefWindowProc( WM_PAINT, (WPARAM)memDC.m_hDC, 0 );

        HTREEITEM hti = GetFirstVisibleItem();
        
        int iVizCount = GetVisibleCount() + 1;
        while( hti && iVizCount-- )
        {
                BOOL bUnderline = !!(GetItemState( hti, TVIS_UNDERLINE ) & TVIS_UNDERLINE);

                UINT flags = TVIS_SELECTED|TVIS_DROPHILITED;
                if( (GetItemState( hti, flags ) & flags) == 0 )
                {
                        CFont* pTreeFont = GetFont();
                        if( pTreeFont == NULL )
                                pTreeFont = CFont::FromHandle( (HFONT)::GetStockObject(DEFAULT_GUI_FONT) );
                        ASSERT_VALID(pTreeFont);

                        LOGFONT lfTree;
                        ::ZeroMemory(&lfTree, sizeof(lfTree));

                        pTreeFont->GetLogFont(&lfTree);

                        if( bUnderline )
                                lfTree.lfUnderline = TRUE;

                        CFont font;
                        font.CreateFontIndirect(&lfTree);
                        CFont* pOldFont = memDC.SelectObject(&font);

                        CString sItemText = GetItemText( hti );

                        CRect itemRect(-1,-1,-1,-1);
                        GetItemRect( hti, itemRect, TRUE );

                        COLORREF clrOld = 0;
                        if( bUnderline )
                                clrOld = memDC.SetTextColor( ::GetSysColor(COLOR_GRAYTEXT) );
                        
                        memDC.TextOut( itemRect.left + 2, itemRect.top + 1, sItemText );
                        
                        if( bUnderline )
                                clrOld = memDC.SetTextColor( clrOld );

                        memDC.SelectObject( pOldFont );
                }

                hti = GetNextVisibleItem( hti );
        }

        paintDC.BitBlt( clipBox.left, clipBox.top, clipBox.Width(),
			clipBox.Height(), &memDC, clipBox.left, clipBox.top, SRCCOPY );
        
        memDC.SelectObject( pOldCanvas );
}



Comments

  • Good

    Posted by Legacy on 02/02/2002 12:00am

    Originally posted by: ajiva

    Good

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

Top White Papers and Webcasts

  • The first phase of API management was about realizing the business value of APIs. This next wave of API management enables the hyper-connected enterprise to drive and scale their businesses as API models become more complex and sophisticated. Today, real world product launches begin with an API program and strategy in mind. This API-first approach to development will only continue to increase, driven by an increasingly interconnected web of devices, organizations, and people. To support this rapid growth, …

  • As everyone scrambles to protect customers and consumers from the Heartbleed virus, there will be a variety of mitigating solutions offered up to address this pesky bug. There are a variety of points within the data path where solutions could be put into place to mitigate this (and similar) vulnerabilities and customers must choose the most strategic point in the network at which to deploy their selected mitigation. Read this white paper to learn the ins and outs of mitigating the risk of Heartbleed and the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds