一个支持多选的树类

来源:互联网 发布:电动机绕组技术数据 编辑:程序博客网 时间:2024/05/16 07:49

 

//头文件

///////////////////////////////////////////////////////////////////////////////
//
// CTreeCtrlEx - Multiple selection tree control for MFC
//
// Bendik Engebretsen (c) 1997-1999
// bendik@techsoft.no
// http://www.techsoft.no/bendik/
//
//

#ifndef __TREECTRLEX_H
#define __TREECTRLEX_H

#define TVGN_EX_ALL   0x000F

/////////////////////////////////////////////////////////////////////////////
// CTreeCtrlEx window

class CTreeCtrlEx : public CTreeCtrl
{
 DECLARE_DYNAMIC(CTreeCtrlEx)

// Construction
public:
 CTreeCtrlEx() : m_bSelectPending(FALSE), m_hClickedItem(NULL), m_hFirstSelectedItem(NULL), m_bSelectionComplete(TRUE), m_bEditLabelPending(FALSE) {}
 BOOL Create(DWORD dwStyle, DWORD dwExStyle, const RECT& rect, CWnd* pParentWnd, UINT nID);
 BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID);

// Attributes
public:
 UINT GetSelectedCount() const;
 HTREEITEM GetNextItem(HTREEITEM hItem, UINT nCode);
 HTREEITEM GetFirstSelectedItem();
 HTREEITEM GetNextSelectedItem(HTREEITEM hItem);
 HTREEITEM GetPrevSelectedItem(HTREEITEM hItem);
 HTREEITEM ItemFromData(DWORD dwData, HTREEITEM hStartAtItem=NULL) const;

 BOOL SelectItemEx(HTREEITEM hItem, BOOL bSelect=TRUE);

 BOOL SelectItems(HTREEITEM hFromItem, HTREEITEM hToItem);
 void ClearSelection(BOOL bMultiOnly=FALSE);

protected:
 void SelectMultiple( HTREEITEM hClickedItem, UINT nFlags, CPoint point );

private:
 BOOL  m_bSelectPending;
 CPoint  m_ptClick;
 HTREEITEM m_hClickedItem;
 HTREEITEM m_hFirstSelectedItem;
 BOOL  m_bSelectionComplete;
 BOOL  m_bEditLabelPending;
 UINT  m_idTimer;

// Operations
public:

// Overrides
 // ClassWizard generated virtual function overrides
 //{{AFX_VIRTUAL(CTreeCtrlEx)
 //}}AFX_VIRTUAL

// Implementation
public:
 virtual ~CTreeCtrlEx() {}

 // Generated message map functions
protected:
 //{{AFX_MSG(CTreeCtrlEx)
 afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
 afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
 afx_msg void OnMouseMove(UINT nFlags, CPoint point);
 afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
 afx_msg BOOL OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult);
 afx_msg BOOL OnSetfocus(NMHDR* pNMHDR, LRESULT* pResult);
 afx_msg BOOL OnKillfocus(NMHDR* pNMHDR, LRESULT* pResult);
 afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
 afx_msg BOOL OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult);
 afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
 afx_msg void OnTimer(UINT nIDEvent);
 //}}AFX_MSG

 DECLARE_MESSAGE_MAP()
};


HTREEITEM GetTreeItemFromData(CTreeCtrl& treeCtrl, DWORD dwData, HTREEITEM hStartAtItem=NULL);

#endif
 

 

//实现文件

///////////////////////////////////////////////////////////////////////////////
//
// CTreeCtrlEx - Multiple selection tree control for MFC
//
// Copyright ?1997-2003 Bendik Engebretsen
// bendik@techsoft.no
// http://www.techsoft.no/bendik/
//
// Oct 9,  1997 : Fixed problem with notification to parent (TVN_BEGINDRAG)
// Oct 17, 1997 : Fixed bug with deselection when collapsing node with no sibling
// Nov 5,  1997 : Fixed problem with label editing
// Feb 17, 1998 : Fixed another notfication to parent (TVN_KEYDOWN)
// Apr 27, 1998 : Added TVN_SELCHANGED notification to parent in SelectMultiple()
// Dec 21, 1998 : Fixed incorrect use of LVHT_... constants. Now using TVHT_...
// Mar 15, 1999 : Fixed problems when there is no selected item.
//                Fixed new problem with label editing
// Apr 16, 1999 : Fixed problem with double TVN_SELCHANGED notifications
//      Many thanx to Roel Schroeven for providing the solution!!
// Jul 2, 1999 : Added TVN_SELCHANGING notification when selecting/deselecting
//      selected item
// Oct 11, 1999 : Fixed quirks with Shift+Arrow Key selection. Thanx to Yariv Elani !
//      Also changed behaviour of Ctrl+Mouse click selection: The item is
//      unselected, but NOT 'focused' (i.e. same behaviour as a listctrl).
// Nov 16, 1999 : Fixed another quirk with Ctrl+Mouse click selection. Thanks to
//      Yariv Ben-Tovim!
// May 21, 2001 : Now supports Shift PgUp and PgDn selection. Thanks to Sergei
//      Antonov. Also fixed the too early TVN_SELCHANGED when key-selecting.
// May 31, 2001 : Fixed bug with uninitialized m_ptClick in OnLButtonDown. Thanks to
//      Cristian Rodriguez!
// Jun 20, 2001 : Fixed quirk with label editing/doubleclick. Once more, thanks
//      to Cristian Rodriguez. Also added treectrl wndclass registration.
// Jan 24, 2003 : Fixed bug when treeview has the TVS_DISABLEDRAGDROP style. Thanks to
//      Fernanda Diniz Tavarez
// Mar 20, 2003 : Fixed 'blinking' problem with TVS_SINGLEEXPAND style. Thanks to
//      H.-Joachim Riedel.
// Jun 10, 2003 : Migrated to Visual C++.NET / MFC 7.X

#include "stdafx.h"
#include "TreeCtrlEx.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define TCEX_EDITLABEL 1  // Edit label timer event

/////////////////////////////////////////////////////////////////////////////
// CTreeCtrlEx

BEGIN_MESSAGE_MAP(CTreeCtrlEx, CTreeCtrl)
 //{{AFX_MSG_MAP(CTreeCtrlEx)
 ON_WM_LBUTTONDOWN()
 ON_WM_LBUTTONUP()
 ON_WM_MOUSEMOVE()
 ON_WM_KEYDOWN()
 ON_NOTIFY_REFLECT_EX(TVN_ITEMEXPANDING, OnItemexpanding)
 ON_NOTIFY_REFLECT_EX(NM_SETFOCUS, OnSetfocus)
 ON_NOTIFY_REFLECT_EX(NM_KILLFOCUS, OnKillfocus)
 ON_NOTIFY_REFLECT_EX(TVN_SELCHANGED, OnSelchanged)
 ON_WM_RBUTTONDOWN()
 ON_WM_LBUTTONDBLCLK()
 ON_WM_TIMER() 
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

IMPLEMENT_DYNAMIC(CTreeCtrlEx, CTreeCtrl)

BOOL CTreeCtrlEx::Create(DWORD dwStyle, DWORD dwExStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)
{
#if _MFC_VER < 0x0700
 return CreateEx( dwExStyle, WC_TREEVIEW, NULL, dwStyle,
  rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top,
  pParentWnd->GetSafeHwnd(), (HMENU)nID );
#else
 return CTreeCtrl::CreateEx( dwExStyle, dwStyle, rect, pParentWnd, nID );
#endif
}

BOOL CTreeCtrlEx::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)
{
 return CTreeCtrl::Create(dwStyle, rect, pParentWnd, nID);
}


/////////////////////////////////////////////////////////////////////////////
// CTreeCtrlEx message handlers


///////////////////////////////////////////////////////////////////////////////
// The tree control dosn't support multiple selection. However we can simulate
// it by taking control of the left mouse click and arrow key press before the
// control gets them, and setting/clearing the TVIS_SELECTED style on the items

void CTreeCtrlEx::OnLButtonDown( UINT nFlags, CPoint point )
{

 UINT nHitFlags = 0;
 HTREEITEM hClickedItem = HitTest( point, &nHitFlags );

 // Must invoke label editing explicitly. The base class OnLButtonDown would normally
 // do this, but we can't call it here because of the multiple selection...
 if( !( nFlags&( MK_CONTROL|MK_SHIFT ) ) && ( GetStyle() & TVS_EDITLABELS ) && ( nHitFlags & TVHT_ONITEMLABEL ) )
  if ( hClickedItem == GetSelectedItem() )
  {
   // Clear multple selection before label editing
   ClearSelection();
   SelectItem( hClickedItem );

   // Invoke label editing
   m_bEditLabelPending = TRUE;
   m_idTimer = SetTimer(TCEX_EDITLABEL, GetDoubleClickTime(), NULL);

   return;
  }

 m_bEditLabelPending = FALSE;

 if( nHitFlags & TVHT_ONITEM )
 {
  SetFocus();

  m_hClickedItem = hClickedItem;

  // Is the clicked item already selected ?
  BOOL bIsClickedItemSelected = GetItemState( hClickedItem, TVIS_SELECTED ) & TVIS_SELECTED;

  if ( bIsClickedItemSelected )
  {
   // Maybe user wants to drag/drop multiple items!
   // So, wait until OnLButtonUp() to do the selection stuff.
   m_bSelectPending=TRUE;
  }
  else
  {
   SelectMultiple( hClickedItem, nFlags, point );
   m_bSelectPending=FALSE;
  }

  m_ptClick=point;
 }
 else
  CTreeCtrl::OnLButtonDown( nFlags, point );
}

void CTreeCtrlEx::OnLButtonUp( UINT nFlags, CPoint point )
{
 if ( m_bSelectPending )
 {
  // A select has been waiting to be performed here
  SelectMultiple( m_hClickedItem, nFlags, point );
  m_bSelectPending=FALSE;
 }

 m_hClickedItem=NULL;

 CTreeCtrl::OnLButtonUp( nFlags, point );
}


void CTreeCtrlEx::OnMouseMove( UINT nFlags, CPoint point )
{
 // If there is a select pending, check if cursor has moved so much away from the
 // down-click point that we should cancel the pending select and initiate
 // a drag/drop operation instead!

 if ( m_hClickedItem )
 {
  CSize sizeMoved = m_ptClick-point;

  if ( abs(sizeMoved.cx) > GetSystemMetrics( SM_CXDRAG ) || abs(sizeMoved.cy) > GetSystemMetrics( SM_CYDRAG ) )
  {
   m_bSelectPending=FALSE;

   // Notify parent that he may begin drag operation
   // Since we have taken over OnLButtonDown(), the default handler doesn't
   // do the normal work when clicking an item, so we must provide our own
   // TVN_BEGINDRAG notification for the parent!

   CWnd* pWnd = GetParent();
   if ( pWnd && !( GetStyle() & TVS_DISABLEDRAGDROP ) )
   {
    NM_TREEVIEW tv;

    tv.hdr.hwndFrom = GetSafeHwnd();
    tv.hdr.idFrom = GetWindowLong( GetSafeHwnd(), GWL_ID );
    tv.hdr.code = TVN_BEGINDRAG;

    tv.itemNew.hItem = m_hClickedItem;
    tv.itemNew.state = GetItemState( m_hClickedItem, 0xffffffff );
    tv.itemNew.lParam = GetItemData( m_hClickedItem );

    tv.ptDrag.x = point.x;
    tv.ptDrag.y = point.y;

    pWnd->SendMessage( WM_NOTIFY, tv.hdr.idFrom, (LPARAM)&tv );
   }

   m_hClickedItem=NULL;
  }
 }

 CTreeCtrl::OnMouseMove( nFlags, point );
}


void CTreeCtrlEx::SelectMultiple( HTREEITEM hClickedItem, UINT nFlags, CPoint point )
{
 // Start preparing an NM_TREEVIEW struct to send a notification after selection is done
 NM_TREEVIEW tv;
 memset(&tv.itemOld, 0, sizeof(tv.itemOld));

 CWnd* pWnd = GetParent();

 HTREEITEM hOldItem = GetSelectedItem();

 if ( hOldItem )
 {
  tv.itemOld.hItem = hOldItem;
  tv.itemOld.state = GetItemState( hOldItem, 0xffffffff );
  tv.itemOld.lParam = GetItemData( hOldItem );
  tv.itemOld.mask = TVIF_HANDLE|TVIF_STATE|TVIF_PARAM;
 }

 // Flag signaling that selection process is NOT complete.
 // (Will prohibit TVN_SELCHANGED from being sent to parent)
 m_bSelectionComplete = FALSE;

 // Action depends on whether the user holds down the Shift or Ctrl key
 if ( nFlags & MK_SHIFT )
 {
  // Select from first selected item to the clicked item
  if ( !m_hFirstSelectedItem )
   m_hFirstSelectedItem = GetSelectedItem();

  SelectItems( m_hFirstSelectedItem, hClickedItem );
 }
 else if ( nFlags & MK_CONTROL )
 {
  // Find which item is currently selected
  HTREEITEM hSelectedItem = GetSelectedItem();

  // Is the clicked item already selected ?
  BOOL bIsClickedItemSelected = GetItemState( hClickedItem, TVIS_SELECTED ) & TVIS_SELECTED;
  BOOL bIsSelectedItemSelected = FALSE;
  if ( hSelectedItem )
   bIsSelectedItemSelected = GetItemState( hSelectedItem, TVIS_SELECTED ) & TVIS_SELECTED;

  // Must synthesize a TVN_SELCHANGING notification
  if ( pWnd )
  {
   tv.hdr.hwndFrom = GetSafeHwnd();
   tv.hdr.idFrom = GetWindowLong( GetSafeHwnd(), GWL_ID );
   tv.hdr.code = TVN_SELCHANGING;

   tv.itemNew.hItem = hClickedItem;
   tv.itemNew.state = GetItemState( hClickedItem, 0xffffffff );
   tv.itemNew.lParam = GetItemData( hClickedItem );

   tv.itemOld.hItem = NULL;
   tv.itemOld.mask = 0;

   tv.action = TVC_BYMOUSE;

   tv.ptDrag.x = point.x;
   tv.ptDrag.y = point.y;

   pWnd->SendMessage( WM_NOTIFY, tv.hdr.idFrom, (LPARAM)&tv );
  }

  // If the previously selected item was selected, re-select it
  if ( bIsSelectedItemSelected )
   SetItemState( hSelectedItem, TVIS_SELECTED, TVIS_SELECTED );

  // We want the newly selected item to toggle its selected state,
  // so unselect now if it was already selected before
  if ( bIsClickedItemSelected )
   SetItemState( hClickedItem, 0, TVIS_SELECTED );
  else
  {
   SelectItem(hClickedItem);
   SetItemState( hClickedItem, TVIS_SELECTED, TVIS_SELECTED );
  }

  // If the previously selected item was selected, re-select it
  if ( bIsSelectedItemSelected && hSelectedItem != hClickedItem )
   SetItemState( hSelectedItem, TVIS_SELECTED, TVIS_SELECTED );

  // Store as first selected item (if not already stored)
  if ( m_hFirstSelectedItem==NULL )
   m_hFirstSelectedItem = hClickedItem;
 }
 else
 {
  // Clear selection of all "multiple selected" items first
  ClearSelection();

  // Then select the clicked item
  SelectItem( hClickedItem );
  SetItemState( hClickedItem, TVIS_SELECTED, TVIS_SELECTED );

  // Store as first selected item
  m_hFirstSelectedItem = hClickedItem;
 }

 // Selection process is now complete. Since we have 'eaten' the TVN_SELCHANGED
 // notification provided by Windows' treectrl, we must now produce one ourselves,
 // so that our parent gets to know about the change of selection.
 m_bSelectionComplete = TRUE;

 if ( pWnd )
 {
  tv.hdr.hwndFrom = GetSafeHwnd();
  tv.hdr.idFrom = GetWindowLong( GetSafeHwnd(), GWL_ID );
  tv.hdr.code = TVN_SELCHANGED;

  tv.itemNew.hItem = m_hClickedItem;
  tv.itemNew.state = GetItemState( m_hClickedItem, 0xffffffff );
  tv.itemNew.lParam = GetItemData( m_hClickedItem );
  tv.itemNew.mask = TVIF_HANDLE|TVIF_STATE|TVIF_PARAM;

  tv.action = TVC_UNKNOWN;

  pWnd->SendMessage( WM_NOTIFY, tv.hdr.idFrom, (LPARAM)&tv );
 }
}

void CTreeCtrlEx::OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags )
{
 CWnd* pWnd = GetParent();

 if ( nChar==VK_NEXT || nChar==VK_PRIOR )
 {
  if ( !( GetKeyState( VK_SHIFT )&0x8000 ) )
  {
   // User pressed Pg key without holding 'Shift':
   // Clear multiple selection (if multiple) and let base class do
   // normal selection work!
   if ( GetSelectedCount()>1 )
    ClearSelection( TRUE );

   CTreeCtrl::OnKeyDown( nChar, nRepCnt, nFlags );
   m_hFirstSelectedItem = GetSelectedItem();
   return;
  }

  // Flag signaling that selection process is NOT complete.
  // (Will prohibit TVN_SELCHANGED from being sent to parent)
  m_bSelectionComplete = FALSE;

  // Let base class select the item
  CTreeCtrl::OnKeyDown( nChar, nRepCnt, nFlags );
  HTREEITEM hSelectedItem = GetSelectedItem();

  // Then select items in between
  SelectItems( m_hFirstSelectedItem, hSelectedItem );

  // Selection process is now complete. Since we have 'eaten' the TVN_SELCHANGED
  // notification provided by Windows' treectrl, we must now produce one ourselves,
  // so that our parent gets to know about the change of selection.
  m_bSelectionComplete = TRUE;

  if (pWnd)
  {
   NM_TREEVIEW tv;
   memset(&tv.itemOld, 0, sizeof(tv.itemOld));

   tv.hdr.hwndFrom = GetSafeHwnd();
   tv.hdr.idFrom = GetWindowLong(GetSafeHwnd(), GWL_ID);
   tv.hdr.code = TVN_SELCHANGED;

   tv.itemNew.hItem = hSelectedItem;
   tv.itemNew.state = GetItemState(hSelectedItem, 0xffffffff);
   tv.itemNew.lParam = GetItemData(hSelectedItem);
   tv.itemNew.mask = TVIF_HANDLE|TVIF_STATE|TVIF_PARAM;

   tv.action = TVC_UNKNOWN;

   pWnd->SendMessage(WM_NOTIFY, tv.hdr.idFrom, (LPARAM)&tv);
  }
 }
 else if ( nChar==VK_UP || nChar==VK_DOWN )
 {
  // Find which item is currently selected
  HTREEITEM hSelectedItem = GetSelectedItem();

  HTREEITEM hNextItem;
  if ( nChar==VK_UP )
   hNextItem = GetPrevVisibleItem( hSelectedItem );
  else
   hNextItem = GetNextVisibleItem( hSelectedItem );

  if ( !( GetKeyState( VK_SHIFT )&0x8000 ) )
  {
   // User pressed arrow key without holding 'Shift':
   // Clear multiple selection (if multiple) and let base class do
   // normal selection work!
   if ( GetSelectedCount()>1 )
    ClearSelection( TRUE );

   if ( hNextItem )
    CTreeCtrl::OnKeyDown( nChar, nRepCnt, nFlags );
   m_hFirstSelectedItem = GetSelectedItem();
   return;
  }

  if ( hNextItem )
  {
   // Flag signaling that selection process is NOT complete.
   // (Will prohibit TVN_SELCHANGED from being sent to parent)
   m_bSelectionComplete = FALSE;

   // If the next item is already selected, we assume user is
   // "moving back" in the selection, and thus we should clear
   // selection on the previous one
   BOOL bSelect = !( GetItemState( hNextItem, TVIS_SELECTED ) & TVIS_SELECTED );

   // Select the next item (this will also deselect the previous one!)
   SelectItem( hNextItem );

   // Now, re-select the previously selected item
   if ( bSelect || ( !( GetItemState( hSelectedItem, TVIS_SELECTED ) & TVIS_SELECTED ) ) )
    SelectItems( m_hFirstSelectedItem, hNextItem );

   // Selection process is now complete. Since we have 'eaten' the TVN_SELCHANGED
   // notification provided by Windows' treectrl, we must now produce one ourselves,
   // so that our parent gets to know about the change of selection.
   m_bSelectionComplete = TRUE;

   if (pWnd)
   {
    NM_TREEVIEW tv;
    memset(&tv.itemOld, 0, sizeof(tv.itemOld));

    tv.hdr.hwndFrom = GetSafeHwnd();
    tv.hdr.idFrom = GetWindowLong(GetSafeHwnd(), GWL_ID);
    tv.hdr.code = TVN_SELCHANGED;

    tv.itemNew.hItem = hNextItem;
    tv.itemNew.state = GetItemState(hNextItem, 0xffffffff);
    tv.itemNew.lParam = GetItemData(hNextItem);
    tv.itemNew.mask = TVIF_HANDLE|TVIF_STATE|TVIF_PARAM;

    tv.action = TVC_UNKNOWN;

    pWnd->SendMessage(WM_NOTIFY, tv.hdr.idFrom, (LPARAM)&tv);
   }
  }

  // Since the base class' OnKeyDown() isn't called in this case,
  // we must provide our own TVN_KEYDOWN notification to the parent

  CWnd* pWnd = GetParent();
  if ( pWnd )
  {
   NMTVKEYDOWN tvk;

   tvk.hdr.hwndFrom = GetSafeHwnd();
   tvk.hdr.idFrom = GetWindowLong( GetSafeHwnd(), GWL_ID );
   tvk.hdr.code = TVN_KEYDOWN;

   tvk.wVKey = nChar;
   tvk.flags = 0;

   pWnd->SendMessage( WM_NOTIFY, tvk.hdr.idFrom, (LPARAM)&tvk );
  }
 }
 else
  // Behave normally
  CTreeCtrl::OnKeyDown( nChar, nRepCnt, nFlags );
}


///////////////////////////////////////////////////////////////////////////////
// I want clicking on an item with the right mouse button to select the item,
// but not if there is currently a multiple selection

void CTreeCtrlEx::OnRButtonDown( UINT nFlags, CPoint point )
{
 UINT nHitFlags = 0;
 HTREEITEM hClickedItem = HitTest( point, &nHitFlags );

 if( nHitFlags&TVHT_ONITEM )
  if ( GetSelectedCount()<2 )
   SelectItem( hClickedItem );

 CTreeCtrl::OnRButtonDown( nFlags, point );
}


///////////////////////////////////////////////////////////////////////////////
// Get number of selected items

UINT CTreeCtrlEx::GetSelectedCount() const
{
 // Only visible items should be selected!
 UINT uCount=0;
 for ( HTREEITEM hItem = GetRootItem(); hItem!=NULL; hItem = GetNextVisibleItem( hItem ) )
  if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
   uCount++;

 return uCount;
}


///////////////////////////////////////////////////////////////////////////////
// Overloaded to catch our own special code

HTREEITEM CTreeCtrlEx::GetNextItem(HTREEITEM hItem, UINT nCode)
{
 if (nCode==TVGN_EX_ALL)
 {
  // This special code lets us iterate through ALL tree items regardless
  // of their parent/child relationship (very handy)
  HTREEITEM hNextItem;

  // If it has a child node, this will be the next item
  hNextItem = GetChildItem( hItem );
  if (hNextItem)
   return hNextItem;

  // Otherwise, see if it has a next sibling item
  hNextItem = GetNextSiblingItem(hItem);
  if (hNextItem)
   return hNextItem;

  // Finally, look for next sibling to the parent item
  HTREEITEM hParentItem=hItem;
  while (!hNextItem && hParentItem)
  {
   // No more children: Get next sibling to parent
   hParentItem = GetParentItem(hParentItem);
   hNextItem = GetNextSiblingItem(hParentItem);
  }

  return hNextItem; // will return NULL if no more parents
 }
 else
  return CTreeCtrl::GetNextItem(hItem, nCode); // standard processing
}

///////////////////////////////////////////////////////////////////////////////
// Helpers to list out selected items. (Use similar to GetFirstVisibleItem(),
// GetNextVisibleItem() and GetPrevVisibleItem()!)

HTREEITEM CTreeCtrlEx::GetFirstSelectedItem()
{
 for ( HTREEITEM hItem = GetRootItem(); hItem!=NULL; hItem = GetNextVisibleItem( hItem ) )
  if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
   return hItem;

 return NULL;
}

HTREEITEM CTreeCtrlEx::GetNextSelectedItem( HTREEITEM hItem )
{
 for ( hItem = GetNextVisibleItem( hItem ); hItem!=NULL; hItem = GetNextVisibleItem( hItem ) )
  if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
   return hItem;

 return NULL;
}

HTREEITEM CTreeCtrlEx::GetPrevSelectedItem( HTREEITEM hItem )
{
 for ( hItem = GetPrevVisibleItem( hItem ); hItem!=NULL; hItem = GetPrevVisibleItem( hItem ) )
  if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
   return hItem;

 return NULL;
}


///////////////////////////////////////////////////////////////////////////////
// Select/unselect item without unselecting other items

BOOL CTreeCtrlEx::SelectItemEx(HTREEITEM hItem, BOOL bSelect/*=TRUE*/)
{
 HTREEITEM hSelItem = GetSelectedItem();

 if ( hItem==hSelItem )
 {
  if ( !bSelect )
  {
   SelectItem( NULL );
   return TRUE;
  }

  return FALSE;
 }

 SelectItem( hItem );
 m_hFirstSelectedItem=hItem;

 // Reselect previous "real" selected item which was unselected byt SelectItem()
 if ( hSelItem )
  SetItemState( hSelItem, TVIS_SELECTED, TVIS_SELECTED );

 return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// Select visible items between specified 'from' and 'to' item (including these!)
// If the 'to' item is above the 'from' item, it traverses the tree in reverse
// direction. Selection on other items is cleared!

BOOL CTreeCtrlEx::SelectItems( HTREEITEM hFromItem, HTREEITEM hToItem )
{
 // Determine direction of selection
 // (see what item comes first in the tree)
 HTREEITEM hItem = GetRootItem();

 while ( hItem && hItem!=hFromItem && hItem!=hToItem )
  hItem = GetNextVisibleItem( hItem );

 if ( !hItem )
  return FALSE; // Items not visible in tree

 BOOL bReverse = hItem==hToItem;

 // "Really" select the 'to' item (which will deselect
 // the previously selected item)

 SelectItem( hToItem );

 // Go through all visible items again and select/unselect

 hItem = GetRootItem();
 BOOL bSelect = FALSE;

 while ( hItem )
 {
  if ( hItem == ( bReverse ? hToItem : hFromItem ) )
   bSelect = TRUE;

  if ( bSelect )
  {
   if ( !( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED ) )
    SetItemState( hItem, TVIS_SELECTED, TVIS_SELECTED );
  }
  else
  {
   if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
    SetItemState( hItem, 0, TVIS_SELECTED );
  }

  if ( hItem == ( bReverse ? hFromItem : hToItem ) )
   bSelect = FALSE;

  hItem = GetNextVisibleItem( hItem );
 }

 return TRUE;
}


///////////////////////////////////////////////////////////////////////////////
// Clear selected state on all visible items

void CTreeCtrlEx::ClearSelection(BOOL bMultiOnly/*=FALSE*/)
{
// if ( !bMultiOnly )
//  SelectItem( NULL );

 for ( HTREEITEM hItem=GetRootItem(); hItem!=NULL; hItem=GetNextVisibleItem( hItem ) )
  if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
   SetItemState( hItem, 0, TVIS_SELECTED );
}


///////////////////////////////////////////////////////////////////////////////
// If a node is collapsed, we should clear selections of its child items

BOOL CTreeCtrlEx::OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult)
{
 NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

 if ( pNMTreeView->action == TVE_COLLAPSE )
 {
  HTREEITEM hItem = GetChildItem( pNMTreeView->itemNew.hItem );

  while ( hItem )
  {
   if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
    SetItemState( hItem, 0, TVIS_SELECTED );

   // Get the next node: First see if current node has a child
   HTREEITEM hNextItem = GetChildItem( hItem );
   if ( !hNextItem )
   {
    // No child: Get next sibling item
    if ( !( hNextItem = GetNextSiblingItem( hItem ) ) )
    {
     HTREEITEM hParentItem = hItem;
     while ( !hNextItem )
     {
      // No more children: Get parent
      if ( !( hParentItem = GetParentItem( hParentItem ) ) )
       break;

      // Quit when parent is the collapsed node
      // (Don't do anything to siblings of this)
      if ( hParentItem == pNMTreeView->itemNew.hItem )
       break;

      // Get next sibling to parent
      hNextItem = GetNextSiblingItem( hParentItem );
     }

     // Quit when parent is the collapsed node
     if ( hParentItem == pNMTreeView->itemNew.hItem )
      break;
    }
   }

   hItem = hNextItem;
  }
 }
 
 *pResult = 0;
 return FALSE; // Allow parent to handle this notification as well
}


///////////////////////////////////////////////////////////////////////////////
// Intercept TVN_SELCHANGED and pass it only to the parent window of the
// selection process is finished

BOOL CTreeCtrlEx::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult)
{
 // Return TRUE if selection is not complete. This will prevent the
 // notification from being sent to parent.
 return !m_bSelectionComplete; 
}


///////////////////////////////////////////////////////////////////////////////
// Ensure the multiple selected items are drawn correctly when loosing/getting
// the focus

BOOL CTreeCtrlEx::OnSetfocus(NMHDR* pNMHDR, LRESULT* pResult)
{
 Invalidate();
 *pResult = 0;
 return FALSE;
}

BOOL CTreeCtrlEx::OnKillfocus(NMHDR* pNMHDR, LRESULT* pResult)
{
 Invalidate();
 *pResult = 0;
 return FALSE;
}

void CTreeCtrlEx::OnLButtonDblClk(UINT nFlags, CPoint point)
{
 // We stop label editing.
 m_bEditLabelPending = FALSE;
 CTreeCtrl::OnLButtonDblClk(nFlags, point);
}

void CTreeCtrlEx::OnTimer(UINT nIDEvent)
{
 if (nIDEvent == TCEX_EDITLABEL)
 {
  // Stop the timer.
  KillTimer(m_idTimer);

  // Invoke label editing.
  if (m_bEditLabelPending)
   EditLabel(GetSelectedItem());

  m_bEditLabelPending = FALSE;
  return;
 }

 CTreeCtrl::OnTimer(nIDEvent);
}

///////////////////////////////////////////////////////////////////////////////
// Retreives a tree ctrl item given the item's data

HTREEITEM CTreeCtrlEx::ItemFromData(DWORD dwData, HTREEITEM hStartAtItem/*=NULL*/) const
{
 // Traverse all items in tree control
 HTREEITEM hItem;
 if ( hStartAtItem )
  hItem = hStartAtItem;
 else
  hItem = GetRootItem();

 while ( hItem )
 {
  if ( dwData == (DWORD)GetItemData( hItem ) )
   return hItem;

  // Get first child node
  HTREEITEM hNextItem = GetChildItem( hItem );

  if ( !hNextItem )
  {
   // Get next sibling child
   hNextItem = GetNextSiblingItem( hItem );

   if ( !hNextItem )
   {
    HTREEITEM hParentItem=hItem;
    while ( !hNextItem && hParentItem )
    {
     // No more children: Get next sibling to parent
     hParentItem = GetParentItem( hParentItem );
     hNextItem = GetNextSiblingItem( hParentItem );
    }
   }
  }

  hItem = hNextItem;
 }

 return NULL;
}


/////////////////////////////////////////////////////////////////////////////
// Global function to retreive a HTREEITEM from a tree control, given the
// item's itemdata.

HTREEITEM GetTreeItemFromData(CTreeCtrl& treeCtrl, DWORD dwData, HTREEITEM hStartAtItem /*=NULL*/)
{
 // Traverse from given item (or all items if hFromItem is NULL)
 HTREEITEM hItem;
 if ( hStartAtItem )
  hItem=hStartAtItem;
 else
  hItem = treeCtrl.GetRootItem();

 while ( hItem )
 {
  if ( dwData == (DWORD)treeCtrl.GetItemData( hItem ) )
   return hItem;

  // Get first child node
  HTREEITEM hNextItem = treeCtrl.GetChildItem( hItem );

  if ( !hNextItem )
  {
   // Get next sibling child
   hNextItem = treeCtrl.GetNextSiblingItem( hItem );

   if ( !hNextItem )
   {
    HTREEITEM hParentItem=hItem;
    while ( !hNextItem && hParentItem )
    {
     // No more children: Get next sibling to parent
     hParentItem = treeCtrl.GetParentItem( hParentItem );
     hNextItem = treeCtrl.GetNextSiblingItem( hParentItem );
    }
   }
  }
  hItem = hNextItem;
 }
 return NULL;
}