GME  13
TreeCtrlEx.cpp
Go to the documentation of this file.
00001 
00002 // 
00003 // CTreeCtrlEx - Multiple selection tree control for MFC
00004 // 
00005 // Copyright © 1997-2003 Bendik Engebretsen
00006 // bendik@techsoft.no
00007 // http://www.techsoft.no/bendik/
00008 //
00009 // Oct 9,  1997 : Fixed problem with notification to parent (TVN_BEGINDRAG)
00010 // Oct 17, 1997 : Fixed bug with deselection when collapsing node with no sibling
00011 // Nov 5,  1997 : Fixed problem with label editing
00012 // Feb 17, 1998 : Fixed another notfication to parent (TVN_KEYDOWN)
00013 // Apr 27, 1998 : Added TVN_SELCHANGED notification to parent in SelectMultiple()
00014 // Dec 21, 1998 : Fixed incorrect use of LVHT_... constants. Now using TVHT_...
00015 // Mar 15, 1999 : Fixed problems when there is no selected item.
00016 //                Fixed new problem with label editing
00017 // Apr 16, 1999 : Fixed problem with double TVN_SELCHANGED notifications
00018 //                                Many thanx to Roel Schroeven for providing the solution!!
00019 // Jul 2, 1999  : Added TVN_SELCHANGING notification when selecting/deselecting 
00020 //                                selected item
00021 // Oct 11, 1999 : Fixed quirks with Shift+Arrow Key selection. Thanx to Yariv Elani !
00022 //                                Also changed behaviour of Ctrl+Mouse click selection: The item is 
00023 //                                unselected, but NOT 'focused' (i.e. same behaviour as a listctrl).
00024 // Nov 16, 1999 : Fixed another quirk with Ctrl+Mouse click selection. Thanks to 
00025 //                                Yariv Ben-Tovim!
00026 // May 21, 2001 : Now supports Shift PgUp and PgDn selection. Thanks to Sergei 
00027 //                                Antonov. Also fixed the too early TVN_SELCHANGED when key-selecting.
00028 // May 31, 2001 : Fixed bug with uninitialized m_ptClick in OnLButtonDown. Thanks to
00029 //                                Cristian Rodriguez!
00030 // Jun 20, 2001 : Fixed quirk with label editing/doubleclick. Once more, thanks 
00031 //                                to Cristian Rodriguez. Also added treectrl wndclass registration.
00032 // Jan 24, 2003 : Fixed bug when treeview has the TVS_DISABLEDRAGDROP style. Thanks to
00033 //                                Fernanda Diniz Tavarez
00034 // Mar 20, 2003 : Fixed 'blinking' problem with TVS_SINGLEEXPAND style. Thanks to 
00035 //                                H.-Joachim Riedel.
00036 // Jun 10, 2003 : Migrated to Visual C++.NET / MFC 7.X
00037 
00038 #include "stdafx.h"
00039 #include "TreeCtrlEx.h"
00040 
00041 #ifdef _DEBUG
00042 #define new DEBUG_NEW
00043 #undef THIS_FILE
00044 static char THIS_FILE[] = __FILE__;
00045 #endif
00046 
00047 #define TCEX_EDITLABEL 1                // Edit label timer event
00048 
00050 // CTreeCtrlEx
00051 
00052 BEGIN_MESSAGE_MAP(CTreeCtrlEx, CTreeCtrl)
00053         //{{AFX_MSG_MAP(CTreeCtrlEx)
00054         ON_WM_LBUTTONDOWN()
00055         ON_WM_LBUTTONUP()
00056         ON_WM_MOUSEMOVE()
00057         ON_WM_KEYDOWN()
00058         ON_NOTIFY_REFLECT_EX(TVN_ITEMEXPANDING, OnItemexpanding)
00059         ON_NOTIFY_REFLECT_EX(NM_SETFOCUS, OnSetfocus)
00060         ON_NOTIFY_REFLECT_EX(NM_KILLFOCUS, OnKillfocus)
00061         ON_NOTIFY_REFLECT_EX(TVN_SELCHANGED, OnSelchanged)
00062         ON_WM_RBUTTONDOWN()
00063         ON_WM_LBUTTONDBLCLK()
00064         ON_WM_TIMER()
00065         ON_WM_NCHITTEST()
00066         //}}AFX_MSG_MAP
00067 END_MESSAGE_MAP()
00068 
00069 IMPLEMENT_DYNAMIC(CTreeCtrlEx, CTreeCtrl)
00070 
00071 BOOL CTreeCtrlEx::Create(DWORD dwStyle, DWORD dwExStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)
00072 {
00073 #if _MFC_VER < 0x0700
00074         return CreateEx( dwExStyle, WC_TREEVIEW, NULL, dwStyle,
00075                 rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, 
00076                 pParentWnd->GetSafeHwnd(), (HMENU)nID );
00077 #else
00078         return CTreeCtrl::CreateEx( dwExStyle, dwStyle, rect, pParentWnd, nID );
00079 #endif
00080 }
00081 
00082 BOOL CTreeCtrlEx::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)
00083 {
00084         return CTreeCtrl::Create(dwStyle, rect, pParentWnd, nID);
00085 }
00086 
00087 
00089 // CTreeCtrlEx message handlers
00090 
00091 
00093 // The tree control dosn't support multiple selection. However we can simulate 
00094 // it by taking control of the left mouse click and arrow key press before the
00095 // control gets them, and setting/clearing the TVIS_SELECTED style on the items
00096 
00097 void CTreeCtrlEx::OnLButtonDown( UINT nFlags, CPoint point )
00098 {
00099 
00100         UINT nHitFlags = 0;
00101         HTREEITEM hClickedItem = HitTest( point, &nHitFlags );
00102 
00103         // Must invoke label editing explicitly. The base class OnLButtonDown would normally
00104         // do this, but we can't call it here because of the multiple selection...
00105         if( !( nFlags&( MK_CONTROL|MK_SHIFT ) ) && ( GetStyle() & TVS_EDITLABELS ) && ( nHitFlags & TVHT_ONITEMLABEL ) )
00106                 if ( hClickedItem == GetSelectedItem() )
00107                 {
00108                         // Clear multple selection before label editing
00109                         ClearSelection();
00110                         SelectItem( hClickedItem );
00111 
00112                         // Invoke label editing
00113                         m_bEditLabelPending = TRUE;
00114                         m_idTimer = SetTimer(TCEX_EDITLABEL, GetDoubleClickTime(), NULL);
00115 
00116                         return;
00117                 }
00118 
00119         m_bEditLabelPending = FALSE;
00120 
00121         if( nHitFlags & TVHT_ONITEM )
00122         {
00123                 SetFocus();
00124 
00125                 m_hClickedItem = hClickedItem;
00126 
00127                 // Is the clicked item already selected ?
00128                 BOOL bIsClickedItemSelected = GetItemState( hClickedItem, TVIS_SELECTED ) & TVIS_SELECTED;
00129 
00130                 if ( bIsClickedItemSelected )
00131                 {
00132                         // Maybe user wants to drag/drop multiple items!
00133                         // So, wait until OnLButtonUp() to do the selection stuff. 
00134                         m_bSelectPending=TRUE;
00135                 }
00136                 else
00137                 {
00138                         SelectMultiple( hClickedItem, nFlags, point );
00139                         m_bSelectPending=FALSE;
00140                 }
00141 
00142                 m_ptClick=point;
00143         }
00144         else
00145         {
00146                 // Modified: 01/29/2002 Tihamer Levendovszky////////
00147                 
00148                 if(nHitFlags&TVHT_NOWHERE || nHitFlags&TVHT_ONITEMRIGHT )
00149                 {
00150                         ClearSelection();
00151                 }
00153                 CTreeCtrl::OnLButtonDown( nFlags, point );
00154         }
00155 }
00156 
00157 void CTreeCtrlEx::OnLButtonUp( UINT nFlags, CPoint point )
00158 {
00159         if ( m_bSelectPending )
00160         {
00161                 // A select has been waiting to be performed here
00162                 SelectMultiple( m_hClickedItem, nFlags, point );
00163                 m_bSelectPending=FALSE;
00164         }
00165 
00166         m_hClickedItem=NULL;
00167 
00168         CTreeCtrl::OnLButtonUp( nFlags, point );
00169 }
00170 
00171 
00172 void CTreeCtrlEx::OnMouseMove( UINT nFlags, CPoint point )
00173 {
00174         // If there is a select pending, check if cursor has moved so much away from the 
00175         // down-click point that we should cancel the pending select and initiate
00176         // a drag/drop operation instead!
00177 
00178         if ( m_hClickedItem )
00179         {
00180                 CSize sizeMoved = m_ptClick-point;
00181 
00182                 if ( abs(sizeMoved.cx) > GetSystemMetrics( SM_CXDRAG ) || abs(sizeMoved.cy) > GetSystemMetrics( SM_CYDRAG ) )
00183                 {
00184                         m_bSelectPending=FALSE;
00185 
00186                         // Notify parent that he may begin drag operation
00187                         // Since we have taken over OnLButtonDown(), the default handler doesn't
00188                         // do the normal work when clicking an item, so we must provide our own
00189                         // TVN_BEGINDRAG notification for the parent!
00190 
00191                         CWnd* pWnd = GetParent();
00192                         if ( pWnd && !( GetStyle() & TVS_DISABLEDRAGDROP ) )
00193                         {
00194                                 NM_TREEVIEW tv;
00195 
00196                                 tv.hdr.hwndFrom = GetSafeHwnd();
00197                                 tv.hdr.idFrom = GetWindowLong( GetSafeHwnd(), GWL_ID );
00198                                 tv.hdr.code = TVN_BEGINDRAG;
00199 
00200                                 tv.itemNew.hItem = m_hClickedItem;
00201                                 tv.itemNew.state = GetItemState( m_hClickedItem, 0xffffffff );
00202                                 tv.itemNew.lParam = GetItemData( m_hClickedItem );
00203 
00204                                 tv.ptDrag.x = point.x;
00205                                 tv.ptDrag.y = point.y;
00206 
00207                                 pWnd->SendMessage( WM_NOTIFY, tv.hdr.idFrom, (LPARAM)&tv );
00208                         }
00209 
00210                         m_hClickedItem=NULL;
00211                 }
00212         }
00213 
00214         CTreeCtrl::OnMouseMove( nFlags, point );
00215 }
00216 
00217 
00218 void CTreeCtrlEx::SelectMultiple( HTREEITEM hClickedItem, UINT nFlags, CPoint point )
00219 {
00220         // Start preparing an NM_TREEVIEW struct to send a notification after selection is done
00221         NM_TREEVIEW tv;
00222         memset(&tv.itemOld, 0, sizeof(tv.itemOld));
00223 
00224         CWnd* pWnd = GetParent();
00225 
00226         HTREEITEM hOldItem = GetSelectedItem();
00227 
00228         if ( hOldItem )
00229         {
00230                 tv.itemOld.hItem = hOldItem;
00231                 tv.itemOld.state = GetItemState( hOldItem, 0xffffffff );
00232                 tv.itemOld.lParam = GetItemData( hOldItem );
00233                 tv.itemOld.mask = TVIF_HANDLE|TVIF_STATE|TVIF_PARAM;
00234         }
00235 
00236         // Flag signaling that selection process is NOT complete.
00237         // (Will prohibit TVN_SELCHANGED from being sent to parent)
00238         m_bSelectionComplete = FALSE;
00239 
00240         // Action depends on whether the user holds down the Shift or Ctrl key
00241         if ( nFlags & MK_SHIFT )
00242         {
00243                 // Select from first selected item to the clicked item
00244                 if ( !m_hFirstSelectedItem )
00245                         m_hFirstSelectedItem = GetSelectedItem();
00246 
00247                 SelectItems( m_hFirstSelectedItem, hClickedItem );
00248         }
00249         else if ( nFlags & MK_CONTROL )
00250         {
00251                 // Find which item is currently selected
00252                 HTREEITEM hSelectedItem = GetSelectedItem();
00253 
00254                 // Is the clicked item already selected ?
00255                 BOOL bIsClickedItemSelected = GetItemState( hClickedItem, TVIS_SELECTED ) & TVIS_SELECTED;
00256                 BOOL bIsSelectedItemSelected = FALSE;
00257                 if ( hSelectedItem )
00258                         bIsSelectedItemSelected = GetItemState( hSelectedItem, TVIS_SELECTED ) & TVIS_SELECTED;
00259 
00260                 // Must synthesize a TVN_SELCHANGING notification
00261                 if ( pWnd )
00262                 {
00263                         tv.hdr.hwndFrom = GetSafeHwnd();
00264                         tv.hdr.idFrom = GetWindowLong( GetSafeHwnd(), GWL_ID );
00265                         tv.hdr.code = TVN_SELCHANGING;
00266 
00267                         tv.itemNew.hItem = hClickedItem;
00268                         tv.itemNew.state = GetItemState( hClickedItem, 0xffffffff );
00269                         tv.itemNew.lParam = GetItemData( hClickedItem );
00270 
00271                         tv.itemOld.hItem = NULL;
00272                         tv.itemOld.mask = 0;
00273 
00274                         tv.action = TVC_BYMOUSE;
00275 
00276                         tv.ptDrag.x = point.x;
00277                         tv.ptDrag.y = point.y;
00278 
00279                         pWnd->SendMessage( WM_NOTIFY, tv.hdr.idFrom, (LPARAM)&tv );
00280                 }
00281 
00282                 // If the previously selected item was selected, re-select it
00283                 if ( bIsSelectedItemSelected )
00284                         SetItemState( hSelectedItem, TVIS_SELECTED, TVIS_SELECTED );
00285 
00286                 // We want the newly selected item to toggle its selected state,
00287                 // so unselect now if it was already selected before
00288                 if ( bIsClickedItemSelected )
00289                         SetItemState( hClickedItem, 0, TVIS_SELECTED );
00290                 else
00291                 {
00292                         SelectItem(hClickedItem);
00293                         SetItemState( hClickedItem, TVIS_SELECTED, TVIS_SELECTED );
00294                 }
00295 
00296                 // If the previously selected item was selected, re-select it
00297                 if ( bIsSelectedItemSelected && hSelectedItem != hClickedItem )
00298                         SetItemState( hSelectedItem, TVIS_SELECTED, TVIS_SELECTED );
00299 
00300                 // Store as first selected item (if not already stored)
00301                 if ( m_hFirstSelectedItem==NULL )
00302                         m_hFirstSelectedItem = hClickedItem;
00303         }
00304         else
00305         {
00306                 // Clear selection of all "multiple selected" items first
00307                 ClearSelection();
00308 
00309                 // Then select the clicked item
00310                 SelectItem( hClickedItem );
00311                 SetItemState( hClickedItem, TVIS_SELECTED, TVIS_SELECTED );
00312 
00313                 // Store as first selected item
00314                 m_hFirstSelectedItem = hClickedItem;
00315         }
00316 
00317         // Selection process is now complete. Since we have 'eaten' the TVN_SELCHANGED 
00318         // notification provided by Windows' treectrl, we must now produce one ourselves,
00319         // so that our parent gets to know about the change of selection.
00320         m_bSelectionComplete = TRUE;
00321 
00322         if ( pWnd )
00323         {
00324                 tv.hdr.hwndFrom = GetSafeHwnd();
00325                 tv.hdr.idFrom = GetWindowLong( GetSafeHwnd(), GWL_ID );
00326                 tv.hdr.code = TVN_SELCHANGED;
00327 
00328                 tv.itemNew.hItem = m_hClickedItem;
00329                 tv.itemNew.state = GetItemState( m_hClickedItem, 0xffffffff );
00330                 tv.itemNew.lParam = GetItemData( m_hClickedItem );
00331                 tv.itemNew.mask = TVIF_HANDLE|TVIF_STATE|TVIF_PARAM;
00332 
00333                 tv.action = TVC_UNKNOWN;
00334 
00335                 pWnd->SendMessage( WM_NOTIFY, tv.hdr.idFrom, (LPARAM)&tv );
00336         }
00337 }
00338 
00339 void CTreeCtrlEx::OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags ) 
00340 {
00341         CWnd* pWnd = GetParent();
00342 
00343         if ( nChar==VK_NEXT || nChar==VK_PRIOR )
00344         {
00345                 if ( !( GetKeyState( VK_SHIFT )&0x8000 ) )
00346                 {
00347                         // User pressed Pg key without holding 'Shift':
00348                         // Clear multiple selection (if multiple) and let base class do 
00349                         // normal selection work!
00350                         if ( GetSelectedCount()>1 )
00351                                 ClearSelection( TRUE );
00352 
00353                         CTreeCtrl::OnKeyDown( nChar, nRepCnt, nFlags );
00354                         m_hFirstSelectedItem = GetSelectedItem();
00355                         return;
00356                 }
00357 
00358                 // Flag signaling that selection process is NOT complete.
00359                 // (Will prohibit TVN_SELCHANGED from being sent to parent)
00360                 m_bSelectionComplete = FALSE;
00361 
00362                 // Let base class select the item
00363                 CTreeCtrl::OnKeyDown( nChar, nRepCnt, nFlags );
00364                 HTREEITEM hSelectedItem = GetSelectedItem();
00365 
00366                 // Then select items in between
00367                 SelectItems( m_hFirstSelectedItem, hSelectedItem );
00368 
00369                 // Selection process is now complete. Since we have 'eaten' the TVN_SELCHANGED 
00370                 // notification provided by Windows' treectrl, we must now produce one ourselves,
00371                 // so that our parent gets to know about the change of selection.
00372                 m_bSelectionComplete = TRUE;
00373 
00374                 if (pWnd)
00375                 {
00376                         NM_TREEVIEW tv;
00377                         memset(&tv.itemOld, 0, sizeof(tv.itemOld));
00378 
00379                         tv.hdr.hwndFrom = GetSafeHwnd();
00380                         tv.hdr.idFrom = GetWindowLong(GetSafeHwnd(), GWL_ID);
00381                         tv.hdr.code = TVN_SELCHANGED;
00382 
00383                         tv.itemNew.hItem = hSelectedItem;
00384                         tv.itemNew.state = GetItemState(hSelectedItem, 0xffffffff);
00385                         tv.itemNew.lParam = GetItemData(hSelectedItem);
00386                         tv.itemNew.mask = TVIF_HANDLE|TVIF_STATE|TVIF_PARAM;
00387 
00388                         tv.action = TVC_UNKNOWN;
00389 
00390                         pWnd->SendMessage(WM_NOTIFY, tv.hdr.idFrom, (LPARAM)&tv);
00391                 }
00392         }
00393         else if ( nChar==VK_UP || nChar==VK_DOWN )
00394         {
00395                 // Find which item is currently selected
00396                 HTREEITEM hSelectedItem = GetSelectedItem();
00397 
00398                 HTREEITEM hNextItem;
00399                 if ( nChar==VK_UP )
00400                         hNextItem = GetPrevVisibleItem( hSelectedItem );
00401                 else
00402                         hNextItem = GetNextVisibleItem( hSelectedItem );
00403 
00404                 if ( !( GetKeyState( VK_SHIFT )&0x8000 ) )
00405                 {
00406                         // User pressed arrow key without holding 'Shift':
00407                         // Clear multiple selection (if multiple) and let base class do 
00408                         // normal selection work!
00409                         if ( GetSelectedCount()>1 )
00410                                 ClearSelection( TRUE );
00411 
00412                         if ( hNextItem )
00413                                 CTreeCtrl::OnKeyDown( nChar, nRepCnt, nFlags );
00414                         m_hFirstSelectedItem = GetSelectedItem();
00415                         return;
00416                 }
00417 
00418                 if ( hNextItem )
00419                 {
00420                         // Flag signaling that selection process is NOT complete.
00421                         // (Will prohibit TVN_SELCHANGED from being sent to parent)
00422                         m_bSelectionComplete = FALSE;
00423 
00424                         // If the next item is already selected, we assume user is
00425                         // "moving back" in the selection, and thus we should clear 
00426                         // selection on the previous one
00427                         BOOL bSelect = !( GetItemState( hNextItem, TVIS_SELECTED ) & TVIS_SELECTED );
00428 
00429                         // Select the next item (this will also deselect the previous one!)
00430                         SelectItem( hNextItem );
00431 
00432                         // Now, re-select the previously selected item
00433                         if ( bSelect || ( !( GetItemState( hSelectedItem, TVIS_SELECTED ) & TVIS_SELECTED ) ) )
00434                                 SelectItems( m_hFirstSelectedItem, hNextItem );
00435 
00436                         // Selection process is now complete. Since we have 'eaten' the TVN_SELCHANGED 
00437                         // notification provided by Windows' treectrl, we must now produce one ourselves,
00438                         // so that our parent gets to know about the change of selection.
00439                         m_bSelectionComplete = TRUE;
00440 
00441                         if (pWnd)
00442                         {
00443                                 NM_TREEVIEW tv;
00444                                 memset(&tv.itemOld, 0, sizeof(tv.itemOld));
00445 
00446                                 tv.hdr.hwndFrom = GetSafeHwnd();
00447                                 tv.hdr.idFrom = GetWindowLong(GetSafeHwnd(), GWL_ID);
00448                                 tv.hdr.code = TVN_SELCHANGED;
00449 
00450                                 tv.itemNew.hItem = hNextItem;
00451                                 tv.itemNew.state = GetItemState(hNextItem, 0xffffffff);
00452                                 tv.itemNew.lParam = GetItemData(hNextItem);
00453                                 tv.itemNew.mask = TVIF_HANDLE|TVIF_STATE|TVIF_PARAM;
00454 
00455                                 tv.action = TVC_UNKNOWN;
00456 
00457                                 pWnd->SendMessage(WM_NOTIFY, tv.hdr.idFrom, (LPARAM)&tv);
00458                         }
00459                 }
00460 
00461                 // Since the base class' OnKeyDown() isn't called in this case,
00462                 // we must provide our own TVN_KEYDOWN notification to the parent
00463 
00464                 CWnd* pWnd = GetParent();
00465                 if ( pWnd )
00466                 {
00467                         NMTVKEYDOWN tvk;
00468 
00469                         tvk.hdr.hwndFrom = GetSafeHwnd();
00470                         tvk.hdr.idFrom = GetWindowLong( GetSafeHwnd(), GWL_ID );
00471                         tvk.hdr.code = TVN_KEYDOWN;
00472 
00473                         tvk.wVKey = nChar;
00474                         tvk.flags = 0;
00475 
00476                         pWnd->SendMessage( WM_NOTIFY, tvk.hdr.idFrom, (LPARAM)&tvk );
00477                 }
00478         }
00479         else
00480                 // Behave normally
00481                 CTreeCtrl::OnKeyDown( nChar, nRepCnt, nFlags );
00482 }
00483 
00484 
00486 // I want clicking on an item with the right mouse button to select the item,
00487 // but not if there is currently a multiple selection
00488 
00489 void CTreeCtrlEx::OnRButtonDown( UINT nFlags, CPoint point )
00490 {
00491         UINT nHitFlags = 0;
00492         HTREEITEM hClickedItem = HitTest( point, &nHitFlags );
00493 
00494         if( nHitFlags&TVHT_ONITEM )
00495         {
00496                 if ( GetSelectedCount()<2 )
00497                 {
00498                         SelectItem( hClickedItem );
00499                 }
00500                 // Modified: 01/26/2002 Tihamer Levendovszky
00501                 else
00502                 {
00503                         if(!( GetItemState( hClickedItem, TVIS_SELECTED ) & TVIS_SELECTED ))
00504                         {
00505                                 ClearSelection();
00506                                 SelectItem( hClickedItem );
00507                         }
00508                 }
00509         }
00510         // Modified: 01/26/2002 Tihamer Levendovszky
00511         else
00512         {
00513                 ClearSelection();
00514         }
00515 
00516 // Modified: 01/26/2002 Tihamer Levendovszky
00517 /*      CTreeCtrl::OnRButtonDown( nFlags, point ); */
00518 }
00519 
00520 
00522 // Get number of selected items
00523 
00524 UINT CTreeCtrlEx::GetSelectedCount() const
00525 {
00526         // Only visible items should be selected!
00527         UINT uCount=0;
00528         for ( HTREEITEM hItem = GetRootItem(); hItem!=NULL; hItem = GetNextVisibleItem( hItem ) )
00529                 if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
00530                         uCount++;
00531 
00532         return uCount;
00533 }
00534 
00535 
00537 // Overloaded to catch our own special code
00538 
00539 HTREEITEM CTreeCtrlEx::GetNextItem(HTREEITEM hItem, UINT nCode)
00540 {
00541         if (nCode==TVGN_EX_ALL)
00542         {
00543                 // This special code lets us iterate through ALL tree items regardless 
00544                 // of their parent/child relationship (very handy)
00545                 HTREEITEM hNextItem;
00546 
00547                 // If it has a child node, this will be the next item
00548                 hNextItem = GetChildItem( hItem );
00549                 if (hNextItem)
00550                         return hNextItem;
00551 
00552                 // Otherwise, see if it has a next sibling item
00553                 hNextItem = GetNextSiblingItem(hItem);
00554                 if (hNextItem)
00555                         return hNextItem;
00556 
00557                 // Finally, look for next sibling to the parent item
00558                 HTREEITEM hParentItem=hItem;
00559                 while (!hNextItem && hParentItem)
00560                 {
00561                         // No more children: Get next sibling to parent
00562                         hParentItem = GetParentItem(hParentItem);
00563                         hNextItem = GetNextSiblingItem(hParentItem);
00564                 }
00565 
00566                 return hNextItem; // will return NULL if no more parents
00567         }
00568         else
00569                 return CTreeCtrl::GetNextItem(hItem, nCode);    // standard processing
00570 }
00571 
00573 // Helpers to list out selected items. (Use similar to GetFirstVisibleItem(), 
00574 // GetNextVisibleItem() and GetPrevVisibleItem()!)
00575 
00576 HTREEITEM CTreeCtrlEx::GetFirstSelectedItem()
00577 {
00578         for ( HTREEITEM hItem = GetRootItem(); hItem!=NULL; hItem = GetNextVisibleItem( hItem ) )
00579                 if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
00580                         return hItem;
00581 
00582         return NULL;
00583 }
00584 
00585 HTREEITEM CTreeCtrlEx::GetNextSelectedItem( HTREEITEM hItem )
00586 {
00587         for ( hItem = GetNextVisibleItem( hItem ); hItem!=NULL; hItem = GetNextVisibleItem( hItem ) )
00588                 if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
00589                         return hItem;
00590 
00591         return NULL;
00592 }
00593 
00594 HTREEITEM CTreeCtrlEx::GetPrevSelectedItem( HTREEITEM hItem )
00595 {
00596         for ( hItem = GetPrevVisibleItem( hItem ); hItem!=NULL; hItem = GetPrevVisibleItem( hItem ) )
00597                 if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
00598                         return hItem;
00599 
00600         return NULL;
00601 }
00602 
00603 
00605 // Select/unselect item without unselecting other items
00606 
00607 BOOL CTreeCtrlEx::SelectItemEx(HTREEITEM hItem, BOOL bSelect/*=TRUE*/)
00608 {
00609         HTREEITEM hSelItem = GetSelectedItem();
00610 
00611         if ( hItem==hSelItem )
00612         {
00613                 if ( !bSelect )
00614                 {
00615                         SelectItem( NULL );
00616                         return TRUE;
00617                 }
00618 
00619                 return FALSE;
00620         }
00621 
00622         SelectItem( hItem );
00623         m_hFirstSelectedItem=hItem;
00624 
00625         // Reselect previous "real" selected item which was unselected byt SelectItem()
00626         if ( hSelItem )
00627                 SetItemState( hSelItem, TVIS_SELECTED, TVIS_SELECTED );
00628 
00629         return TRUE;
00630 }
00631 
00633 // Select visible items between specified 'from' and 'to' item (including these!)
00634 // If the 'to' item is above the 'from' item, it traverses the tree in reverse 
00635 // direction. Selection on other items is cleared!
00636 
00637 BOOL CTreeCtrlEx::SelectItems( HTREEITEM hFromItem, HTREEITEM hToItem )
00638 {
00639         // Determine direction of selection 
00640         // (see what item comes first in the tree)
00641         HTREEITEM hItem = GetRootItem();
00642 
00643         while ( hItem && hItem!=hFromItem && hItem!=hToItem )
00644                 hItem = GetNextVisibleItem( hItem );
00645 
00646         if ( !hItem )
00647                 return FALSE;   // Items not visible in tree
00648 
00649         BOOL bReverse = hItem==hToItem;
00650 
00651         // "Really" select the 'to' item (which will deselect 
00652         // the previously selected item)
00653 
00654         SelectItem( hToItem );
00655 
00656         // Go through all visible items again and select/unselect
00657 
00658         hItem = GetRootItem();
00659         BOOL bSelect = FALSE;
00660 
00661         while ( hItem )
00662         {
00663                 if ( hItem == ( bReverse ? hToItem : hFromItem ) )
00664                         bSelect = TRUE;
00665 
00666                 if ( bSelect )
00667                 {
00668                         if ( !( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED ) )
00669                                 SetItemState( hItem, TVIS_SELECTED, TVIS_SELECTED );
00670                 }
00671                 else
00672                 {
00673                         if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
00674                                 SetItemState( hItem, 0, TVIS_SELECTED );
00675                 }
00676 
00677                 if ( hItem == ( bReverse ? hFromItem : hToItem ) )
00678                         bSelect = FALSE;
00679 
00680                 hItem = GetNextVisibleItem( hItem );
00681         }
00682 
00683         return TRUE;
00684 }
00685 
00686 
00688 // Clear selected state on all visible items
00689 
00690 void CTreeCtrlEx::ClearSelection(BOOL bMultiOnly/*=FALSE*/)
00691 {
00692 //      if ( !bMultiOnly )
00693 //              SelectItem( NULL );
00694 
00695         for ( HTREEITEM hItem=GetRootItem(); hItem!=NULL; hItem=GetNextVisibleItem( hItem ) )
00696                 if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
00697                         SetItemState( hItem, 0, TVIS_SELECTED );
00698 }
00699 
00700 
00702 // If a node is collapsed, we should clear selections of its child items 
00703 
00704 BOOL CTreeCtrlEx::OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult) 
00705 {
00706         NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
00707 
00708         if ( pNMTreeView->action == TVE_COLLAPSE )
00709         {
00710                 HTREEITEM hItem = GetChildItem( pNMTreeView->itemNew.hItem );
00711 
00712                 while ( hItem )
00713                 {
00714                         if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
00715                                 SetItemState( hItem, 0, TVIS_SELECTED );
00716 
00717                         // Get the next node: First see if current node has a child
00718                         HTREEITEM hNextItem = GetChildItem( hItem );
00719                         if ( !hNextItem )
00720                         {
00721                                 // No child: Get next sibling item
00722                                 if ( !( hNextItem = GetNextSiblingItem( hItem ) ) )
00723                                 {
00724                                         HTREEITEM hParentItem = hItem;
00725                                         while ( !hNextItem )
00726                                         {
00727                                                 // No more children: Get parent
00728                                                 if ( !( hParentItem = GetParentItem( hParentItem ) ) )
00729                                                         break;
00730 
00731                                                 // Quit when parent is the collapsed node
00732                                                 // (Don't do anything to siblings of this)
00733                                                 if ( hParentItem == pNMTreeView->itemNew.hItem )
00734                                                         break;
00735 
00736                                                 // Get next sibling to parent
00737                                                 hNextItem = GetNextSiblingItem( hParentItem );
00738                                         }
00739 
00740                                         // Quit when parent is the collapsed node
00741                                         if ( hParentItem == pNMTreeView->itemNew.hItem )
00742                                                 break;
00743                                 }
00744                         }
00745 
00746                         hItem = hNextItem;
00747                 }
00748         }
00749         
00750         *pResult = 0;
00751         return FALSE;   // Allow parent to handle this notification as well
00752 }
00753 
00754 
00756 // Intercept TVN_SELCHANGED and pass it only to the parent window of the
00757 // selection process is finished
00758 
00759 BOOL CTreeCtrlEx::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult)
00760 {
00761         // Return TRUE if selection is not complete. This will prevent the 
00762         // notification from being sent to parent.
00763         return !m_bSelectionComplete;   
00764 }
00765 
00766 
00768 // Ensure the multiple selected items are drawn correctly when loosing/getting
00769 // the focus
00770 
00771 BOOL CTreeCtrlEx::OnSetfocus(NMHDR* pNMHDR, LRESULT* pResult) 
00772 {
00773         Invalidate();
00774         *pResult = 0;
00775         return FALSE;
00776 }
00777 
00778 BOOL CTreeCtrlEx::OnKillfocus(NMHDR* pNMHDR, LRESULT* pResult) 
00779 {
00780         Invalidate();
00781         *pResult = 0;
00782         return FALSE;
00783 }
00784 
00785 void CTreeCtrlEx::OnLButtonDblClk(UINT nFlags, CPoint point)
00786 {
00787         // We stop label editing.
00788         m_bEditLabelPending = FALSE;
00789         CTreeCtrl::OnLButtonDblClk(nFlags, point);
00790 }
00791 
00792 void CTreeCtrlEx::OnTimer(UINT_PTR nIDEvent)
00793 {
00794         if (nIDEvent == TCEX_EDITLABEL)
00795         {
00796                 // Stop the timer.
00797                 KillTimer(m_idTimer);
00798 
00799                 // Invoke label editing.
00800                 if (m_bEditLabelPending)
00801                         EditLabel(GetSelectedItem());
00802 
00803                 m_bEditLabelPending = FALSE;
00804                 return;
00805         }
00806 
00807         CTreeCtrl::OnTimer(nIDEvent);
00808 }
00809 
00811 // Retrieves a sorted list of the item text and item handles
00812 // Returns the number of matched items
00813 // Tihamer Levendovszky 12/07/2001
00814 /*int CTreeCtrlEx::FindTextInItems(CString &strText, HTREEITEM hStartAtItem,
00815                                         CStringArrayEx &strResults, CArray<HTREEITEM, HTREEITEM>& hResults)
00816 {
00817         strResults.RemoveAll();
00818         hResults.RemoveAll();
00819         // Traverse all items in tree control
00820         HTREEITEM hItem;
00821         if ( hStartAtItem )
00822                 hItem = hStartAtItem;
00823         else
00824                 hItem = GetRootItem();
00825 
00826         while ( hItem )
00827         {
00828                 CString strItemText = GetItemText( hItem );
00829                 if ( strItemText.Find(strText) == 0 )   // Item text starts with strText
00830                 {
00831                         // Insert at the same position the item handle and the string
00832                         hResults.InsertAt(strResults.InsertAtOrder(strItemText), hItem);
00833                 }                       
00834 
00835                 // Get first child node
00836                 HTREEITEM hNextItem = GetChildItem( hItem );
00837 
00838                 if ( !hNextItem )
00839                 {
00840                         // Get next sibling child
00841                         hNextItem = GetNextSiblingItem( hItem );
00842 
00843                         if ( !hNextItem )
00844                         {
00845                                 HTREEITEM hParentItem=hItem;
00846                                 while ( !hNextItem && hParentItem )
00847                                 {
00848                                         // No more children: Get next sibling to parent
00849                                         hParentItem = GetParentItem( hParentItem );
00850                                         hNextItem = GetNextSiblingItem( hParentItem );
00851                                 }
00852                         }
00853                 }
00854 
00855                 hItem = hNextItem;
00856         }
00857 
00858         return strResults.GetSize();
00859 
00860 }
00861 
00862 */
00864 // Retreives a tree ctrl item given the item's data
00865 
00866 HTREEITEM CTreeCtrlEx::ItemFromData(DWORD dwData, HTREEITEM hStartAtItem/*=NULL*/) const
00867 {
00868         // Traverse all items in tree control
00869         HTREEITEM hItem;
00870         if ( hStartAtItem )
00871                 hItem = hStartAtItem;
00872         else
00873                 hItem = GetRootItem();
00874 
00875         while ( hItem )
00876         {
00877                 if ( dwData == (DWORD)GetItemData( hItem ) )
00878                         return hItem;
00879 
00880                 // Get first child node
00881                 HTREEITEM hNextItem = GetChildItem( hItem );
00882 
00883                 if ( !hNextItem )
00884                 {
00885                         // Get next sibling child
00886                         hNextItem = GetNextSiblingItem( hItem );
00887 
00888                         if ( !hNextItem )
00889                         {
00890                                 HTREEITEM hParentItem=hItem;
00891                                 while ( !hNextItem && hParentItem )
00892                                 {
00893                                         // No more children: Get next sibling to parent
00894                                         hParentItem = GetParentItem( hParentItem );
00895                                         hNextItem = GetNextSiblingItem( hParentItem );
00896                                 }
00897                         }
00898                 }
00899 
00900                 hItem = hNextItem;
00901         }
00902 
00903         return NULL;
00904 }
00905 
00906 
00908 // Global function to retreive a HTREEITEM from a tree control, given the 
00909 // item's itemdata.
00910 
00911 HTREEITEM GetTreeItemFromData(CTreeCtrl& treeCtrl, DWORD dwData, HTREEITEM hStartAtItem /*=NULL*/)
00912 {
00913         // Traverse from given item (or all items if hFromItem is NULL)
00914         HTREEITEM hItem;
00915         if ( hStartAtItem )
00916                 hItem=hStartAtItem;
00917         else
00918                 hItem = treeCtrl.GetRootItem();
00919 
00920         while ( hItem )
00921         {
00922                 if ( dwData == (DWORD)treeCtrl.GetItemData( hItem ) )
00923                         return hItem;
00924 
00925                 // Get first child node
00926                 HTREEITEM hNextItem = treeCtrl.GetChildItem( hItem );
00927 
00928                 if ( !hNextItem )
00929                 {
00930                         // Get next sibling child
00931                         hNextItem = treeCtrl.GetNextSiblingItem( hItem );
00932 
00933                         if ( !hNextItem )
00934                         {
00935                                 HTREEITEM hParentItem=hItem;
00936                                 while ( !hNextItem && hParentItem )
00937                                 {
00938                                         // No more children: Get next sibling to parent
00939                                         hParentItem = treeCtrl.GetParentItem( hParentItem );
00940                                         hNextItem = treeCtrl.GetNextSiblingItem( hParentItem );
00941                                 }
00942                         }
00943                 }
00944                 hItem = hNextItem;
00945         }
00946         return NULL;
00947 }
00948 
00949 
00950 // Tihamer Levendovszky 02/25/02
00951 BOOL CTreeCtrlEx::CreateDragImageEx(CPoint ptDragPoint)
00952 {
00953          if (GetSelectedCount() <= 0)
00954           return FALSE; // no row selected
00955 
00956          CRect rectSingle;
00957          CRect rectText;
00958          CRect rectComplete(0,0,0,0);
00959 
00960          HTREEITEM hItem;
00961 
00962          // Putting together the bounding rectangle
00963          for(hItem=GetFirstSelectedItem();hItem;hItem=GetNextSelectedItem(hItem))
00964          {
00965                 CImageList* pSingleImageList = CreateDragImage(hItem);
00966                 IMAGEINFO ImageInfo;
00967                 
00968                 // The image bounding rectangle is zero based - but has the correct size
00969                 // GetItemRect correct in offset, but does not contain icon size
00970                 pSingleImageList->GetImageInfo(0,&ImageInfo);                           
00971                 GetItemRect(hItem,rectSingle,TRUE);     
00972                 rectSingle.left=rectSingle.right-ImageInfo.rcImage.right;
00973 
00974                 pSingleImageList->DeleteImageList();
00975                 delete pSingleImageList;
00976                 
00977                 rectComplete.UnionRect(rectComplete, rectSingle);
00978          }
00979 
00980          CClientDC dcClient(this);
00981          CDC dcMem;
00982          CBitmap Bitmap;
00983 
00984          if (!dcMem.CreateCompatibleDC(&dcClient))
00985           return FALSE;
00986 
00987          if (!Bitmap.CreateCompatibleBitmap(&dcClient, 
00988                  rectComplete.Width(), 
00989                  rectComplete.Height()))
00990           return FALSE;
00991 
00992          CBitmap *pOldMemDCBitmap = dcMem.SelectObject(&Bitmap);
00993 
00994          COLORREF cMaskColor=RGB(0,255,0);
00995          dcMem.FillSolidRect(0, 0, 
00996                                                  rectComplete.Width(), 
00997                                                  rectComplete.Height(), 
00998                                                  cMaskColor);
00999 
01000          // Paint each DragImage in the DC
01001          for(hItem=GetFirstSelectedItem();hItem;hItem=GetNextSelectedItem(hItem))
01002          {                
01003                   CImageList* pSingleImageList = CreateDragImage(hItem);
01004 
01005                   if (pSingleImageList)
01006                   {
01007                                                 
01008                         // The image bounding rectangle is zero based - but has the correct size
01009                         // GetItemRect correct in offset, but does not contain icon size
01010                         IMAGEINFO ImageInfo;
01011                         pSingleImageList->GetImageInfo(0,&ImageInfo);                           
01012                         GetItemRect(hItem,rectSingle,TRUE);     
01013                         rectSingle.left=rectSingle.right-ImageInfo.rcImage.right;
01014 
01015                     pSingleImageList->Draw(&dcMem, 
01016                         0, 
01017                         CPoint(rectSingle.left - rectComplete.left,
01018                         rectSingle.top - rectComplete.top), 
01019                         ILD_TRANSPARENT);
01020 
01021 /*                      #ifdef _DEBUG
01022                                    CDC *pWndDC=GetDC();
01023                                    pSingleImageList->Draw(pWndDC,
01024                                         0, 
01025                                         CPoint(rectSingle.left - rectComplete.left,
01026                                         rectSingle.top - rectComplete.top), 
01027                                         ILD_TRANSPARENT);       
01028                                    ReleaseDC(pWndDC);
01029                         #endif
01030 */
01031                    pSingleImageList->DeleteImageList();
01032                    delete pSingleImageList;
01033                   }
01034          }
01035 /*
01036                         #ifdef _DEBUG
01037                                    CDC *pWndDC=GetDC();
01038                                    BitBlt(pWndDC->GetSafeHdc(),0,0,rectComplete.Width(),rectComplete.Height(),dcMem.GetSafeHdc(),0,0,SRCCOPY);
01039                                    ReleaseDC(pWndDC);
01040                         #endif
01041 */
01042          dcMem.SelectObject(pOldMemDCBitmap);
01043 
01044 
01045          if(m_CurrentDragImage.GetSafeHandle()!=NULL)
01046          {
01047                  m_CurrentDragImage.DeleteImageList();
01048          }
01049 
01050          m_CurrentDragImage.Create(rectComplete.Width(), 
01051                                                                 rectComplete.Height(), 
01052                                                                 ILC_COLOR | ILC_MASK, 
01053                                                                 0, 1);
01054 
01055          // Green is used as mask color
01056          m_CurrentDragImage.Add(&Bitmap, cMaskColor); 
01057 
01058 
01059 
01060          Bitmap.DeleteObject();
01061 
01062          m_ptHotSpot.x = ptDragPoint.x-rectComplete.left;
01063          m_ptHotSpot.y = ptDragPoint.y-rectComplete.top;
01064 
01065 
01066          return TRUE;
01067 
01068 }
01069 
01070 // Tihamer Levendovszky 02/25/02
01071 void CTreeCtrlEx::DeleteDragImageEx()
01072 {
01073         m_CurrentDragImage.DeleteImageList();
01074 }
01075 
01076 // Tihamer Levendovszky 02/25/02
01077 void CTreeCtrlEx::ScrollUp()
01078 {
01079 
01080         HTREEITEM hItem=GetFirstVisibleItem();
01081         HTREEITEM hPrevSibling=GetPrevSiblingItem(hItem);
01082         HTREEITEM hParent=GetParentItem(hItem);
01083 
01084         if(hItem)
01085         {
01086                 if(hPrevSibling)
01087                 {
01088                         SelectSetFirstVisible(hPrevSibling);
01089                 }
01090                 else if(hParent)
01091                 {
01092                         SelectSetFirstVisible(hParent);
01093                 }
01094         }
01095                          
01096 }