CTreeCtrlEx树控件完整多选

定义CDirTreeCtrl m_tree;作为树控件变量

TreeCtrlEx.h

#pragma once

/************************************************************************/
/*                                                                      */
/************************************************************************/

#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*/UINT_PTR nIDEvent);
	//}}AFX_MSG

	DECLARE_MESSAGE_MAP()
};

/************************************************************************/
/*                                                                      */
/************************************************************************/

class CDirTreeCtrl : public CTreeCtrlEx
{
	DECLARE_DYNAMIC(CDirTreeCtrl)

	//data
private:
	BOOL AddSubDirAsItem(HTREEITEM hParent);
	BOOL AddSubDirAsItem1(HTREEITEM hParent);

	BOOL FindSubDir(LPCTSTR strPath);
	CString GetFullPath(HTREEITEM hItem);
	BOOL DisplayDrives();
	BOOL AttachImgList();
	HTREEITEM AddItem( HTREEITEM hParent, LPCTSTR strName );

	CString        m_strError;   
	CImageList     m_imgList;
	HTREEITEM m_hDirTreeRoot;
	DWORD m_treeStyle;

public:
	CDirTreeCtrl();
	void SetDirTreeStyle();
	BOOL DisplayTree(LPCTSTR strRoot);
	virtual ~CDirTreeCtrl();

	// Generated message map functions
protected:
	//{{AFX_MSG(CDirTreeCtrl)
	// NOTE - the ClassWizard will add and remove member functions here.
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()

public:
	//afx_msg void OnNMClick(NMHDR *pNMHDR, LRESULT *pResult);   // 用这个作为鼠标消息响应也可以,但是下面的一个更好
	afx_msg void OnTvnItemexpanding(NMHDR *pNMHDR, LRESULT *pResult);
};

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

#endif

TreeCtrlEx.cpp

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

IMPLEMENT_DYNAMIC(CTreeCtrlEx, CTreeCtrl)
IMPLEMENT_DYNAMIC(CDirTreeCtrl, CTreeCtrlEx)
// CDirTreeCtrl message handlers
CDirTreeCtrl::CDirTreeCtrl()
{
}

CDirTreeCtrl::~CDirTreeCtrl()
{
	m_imgList.Detach();
}

/************************************************************************/
/*                                                                      */
/************************************************************************/

#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 = (UINT)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*/UINT_PTR 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;
}


/

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;
}

/************************************************************************/
/*                                                                      */
/************************************************************************/

BEGIN_MESSAGE_MAP(CDirTreeCtrl, CTreeCtrlEx)
	//{{AFX_MSG_MAP(CDirTreeCtrl)
	// NOTE - the ClassWizard will add and remove mapping macros here.
	//}}AFX_MSG_MAP
	//ON_NOTIFY_REFLECT(NM_CLICK, &CDirTreeCtrl::OnNMClick)
	ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, &CDirTreeCtrl::OnTvnItemexpanding)
END_MESSAGE_MAP()

/
// CDirTreeCtrl message handlers
//程序调用的接口,显示一棵目录树,
//strRoot为根目录路径
BOOL CDirTreeCtrl::DisplayTree(LPCTSTR strRoot)
{
	SetItemHeight(20);
	SetTextColor(RGB(0X0,0X0,0XFF));
	DeleteAllItems();
	SetDirTreeStyle();
	if ( !AttachImgList() ) return FALSE;

	DisplayDrives();

	return TRUE;
}
//设置目录树属性
void CDirTreeCtrl::SetDirTreeStyle()
{
	DWORD dwStyle = GetWindowLong(m_hWnd, GWL_STYLE );
	dwStyle |= TVS_HASBUTTONS |
		TVS_HASLINES | TVS_LINESATROOT |
		/*TVS_CHECKBOXES |*/
		WS_BORDER | WS_TABSTOP ;
	m_treeStyle = dwStyle;
	SetWindowLong(m_hWnd, GWL_STYLE, dwStyle );
}
//获取系统图标
BOOL CDirTreeCtrl::AttachImgList()
{
	SHFILEINFOW shFinfo;
	HIMAGELIST hImgList = NULL;

	if ( GetImageList( TVSIL_NORMAL ) ) m_imgList.Detach();

	//char *szName="C:\\";
	//USES_CONVERSION; // 这个宏一定要加上,否则会出一堆错误  
	//LPCWSTR pName=T2W(szName); // tchar ---> wchar 
	hImgList = (HIMAGELIST)SHGetFileInfoW(
		/*pName*/L"C:\\",
		0,
		&shFinfo,
		sizeof( shFinfo ),
		SHGFI_SYSICONINDEX |  SHGFI_SMALLICON );
	if ( !hImgList )
	{
		m_strError = _T("无法得到系统图标文件!");
		return FALSE;
	}
	m_imgList.m_hImageList = hImgList;   

	SetImageList( &m_imgList, TVSIL_NORMAL );
	return TRUE; 
}
//显示系统盘符
BOOL CDirTreeCtrl::DisplayDrives()
{
	DeleteAllItems();

	TCHAR  szDrives[260];
	TCHAR* pDrive=NULL;
	if ( !GetLogicalDriveStrings( sizeof(szDrives), szDrives ) )
	{
		m_strError =_T("驱动信息获取失败!");
		return FALSE;
	}

	pDrive = szDrives;    //szDrives 中的字符串格式:_T("C:\\0D:\\0D:\\0E:\\0") 

	m_hDirTreeRoot = InsertItem(_T("计算机"),15,25);//15 和是计算机的两个图标,前一个是没选中时的,后一个是选中时的
	//CStringArray strDrives;
	//CString strDrive;
	//while( *pDrive!=0 )
	//{
	//	strDrives.Add(pDrive);
	//	pDrive += _tcslen( pDrive ) + 1;
	//}
	//for (int n=0; n<strDrives.GetCount(); n++)
	//{
	//	strDrive=strDrives.GetAt(n);
	//	strDrive.SetAt(strDrive.GetLength()-1,_T('\0'));  //试验表明:uincode 的"\0"字符同样适用!!!
	//	HTREEITEM hParent = AddItem( m_hDirTreeRoot,strDrive  );
	//	if ( FindSubDir( strDrive ))  InsertItem(_T("dummy"),0,0,hParent);
	//	
	//}

	// 去掉盘符后的"\",下面的思路经验证也可行,而且更加简练
	int len;
	while( *pDrive!=0 )
	{
		len = (int)_tcslen(pDrive);
		pDrive[len-1] = _T('\0');
		HTREEITEM hParent = AddItem( m_hDirTreeRoot, pDrive );
		if ( FindSubDir( pDrive ))  AddSubDirAsItem(hParent); 
		// 一个技巧先加入下一级子目录项,
		// 然后再点击该项后,首先去掉所有子项,然后再加入,
		// 这样的好处是在前面路径的方框中会有一个+号,
		// 因为如果把全部的目录一次加入,大约需要半个小时时间,所以采取点击时先去掉所有项,然后再增加子目录
		pDrive += len + 1;
	}

	Expand( m_hDirTreeRoot, TVE_EXPAND );
	return TRUE;
}


//是否有子目录可展开
BOOL CDirTreeCtrl::FindSubDir(LPCTSTR strPath)
{
	CFileFind find;
	CString   strTemp = strPath;
	BOOL      bFind;

	if ( strTemp.Right(1) == _T('\\') )        strTemp += _T("*.*");
	else        strTemp += _T("\\*.*");   

	bFind = find.FindFile( strTemp );   
	while ( bFind )
	{
		bFind = find.FindNextFile();
		if ( find.IsDirectory() && !find.IsDots() )
		{
			return TRUE;
		}
		if ( !find.IsDirectory()/* && m_bShowFiles*/ )
			return TRUE;       
	}
	return FALSE;
}

//获取全目录
CString CDirTreeCtrl::GetFullPath(HTREEITEM hItem)
{
	CString strReturn;
	CString strTemp;
	HTREEITEM hParent = hItem;

	strReturn = "";

	while ( hParent )
	{

		strTemp  = GetItemText( hParent );
		if(strTemp != _T("计算机"))
		{
			if ( strTemp.Right(1) !=  _T("\\") )     strTemp += _T("\\");
			strReturn = strTemp + strReturn;
		}
		hParent = GetParentItem( hParent );
	}

	return strReturn;
}




// 需要在此节点上,加入它的整个子目录和下一级的子目录,作为迭代加入
BOOL CDirTreeCtrl::AddSubDirAsItem(HTREEITEM hParent)
{
	CString strPath,strFileName;
	HTREEITEM hChild;

	//---------------------去除该父项下所有的子项------------
	// 因为有dummy项,并且有时子目录再次打开,或子目录会刷新等,因此必须去除。
	while ( ItemHasChildren(hParent))
	{
		hChild = GetChildItem(hParent); 
		DeleteItem( hChild );

	}

	//-----------------------装入该父项下所有子项--------------
	strPath = GetFullPath(hParent);  // 从本节点开始到根的路径
	CString strSearchCmd = strPath;
	if( strSearchCmd.Right( 1 ) != _T( "\\" )) strSearchCmd += _T( "\\" );
	strSearchCmd += _T( "*.*" );
	CFileFind find;
	BOOL bContinue = find.FindFile( strSearchCmd );
	while ( bContinue )
	{
		bContinue = find.FindNextFile();
		strFileName = find.GetFileName();

		if ( !find.IsHidden() && ! find.IsDots() && find.IsDirectory() )
		{
			hChild = AddItem( hParent, strFileName );
			if ( FindSubDir( GetFullPath(hChild) ))  AddSubDirAsItem1(hChild); 
			// 一个技巧:先加入下一级子目录项,
			// 然后再点击该项后,再先去掉所有子项,然后再加入,
			// 这样的好处是在前面路径的方框中会有一个+号,
			// 因为如果把全部的目录一次加入,大约需要半个小时时间,所以采取点击时先去掉所有项,然后再增加子目录

		}
		if ( !find.IsHidden() && ! find.IsDots() && !find.IsDirectory() )
		{
			InsertItem( strFileName, 0, 0, hParent );
		}

	}

	//


	return TRUE;

}


//仅仅装入下一级子目录
BOOL CDirTreeCtrl::AddSubDirAsItem1(HTREEITEM hParent)
{
	CString strPath,strFileName;
	HTREEITEM hChild;

	//---------------------去除该父项下所有的子项------------
	// 因为有dummy项,并且有时子目录再次打开,或子目录会刷新等,因此必须去除。
	while ( ItemHasChildren(hParent))
	{
		hChild = GetChildItem(hParent); 
		DeleteItem( hChild );
	}

	//-----------------------装入该父项下所有子项--------------
	strPath = GetFullPath(hParent);  // 从本节点开始到根的路径
	CString strSearchCmd = strPath;
	if( strSearchCmd.Right( 1 ) != _T( "\\" )) strSearchCmd += _T( "\\" );
	strSearchCmd += _T( "*.*" );
	CFileFind find;
	BOOL bContinue = find.FindFile( strSearchCmd );
	while ( bContinue )
	{
		bContinue = find.FindNextFile();
		strFileName = find.GetFileName();

		if ( !find.IsHidden() && ! find.IsDots() && find.IsDirectory() )
		{
			hChild = AddItem( hParent, strFileName );
		}
		if ( !find.IsHidden() && ! find.IsDots() && !find.IsDirectory() )
		{
			InsertItem( strFileName, 0, 0, hParent );
		}

	}

	//


	return TRUE;

}

//添加项
HTREEITEM CDirTreeCtrl::AddItem(HTREEITEM hParent, LPCTSTR strName)
{
	// 获取路径
	CString strPath = GetFullPath(hParent);
	CString strTemp = strPath + CString(strName);

	if ( strTemp.Right(1) !=  _T("\\") )     strTemp += _T("\\");

	SHFILEINFO shFinfo;
	int iIcon, iIconSel;


	if ( !SHGetFileInfo( strTemp,
		0,
		&shFinfo,
		sizeof( shFinfo ),
		SHGFI_ICON |
		SHGFI_SMALLICON ) )
	{
		m_strError = _T("系统图表获取失败!");
		return NULL;
	}

	iIcon = shFinfo.iIcon;

	// we only need the index from the system image list
	DestroyIcon( shFinfo.hIcon );

	if ( !SHGetFileInfo( strTemp,
		0,
		&shFinfo,
		sizeof( shFinfo ),
		SHGFI_ICON | SHGFI_OPENICON |
		SHGFI_SMALLICON ) )
	{
		m_strError =  _T("系统图表获取失败!");
		return NULL;
	}

	iIconSel = shFinfo.iIcon;

	// we only need the index of the system image list
	DestroyIcon( shFinfo.hIcon );

	return InsertItem( strName, iIcon, iIconSel, hParent );
}


//这里利用反射实现此函数,好处是可以把消息的处理完全封闭到类内,有利于使用
//父窗口去掉对此消息的处理即可,没有处理,消息自然返回
//void CDirTreeCtrl::OnNMClick(NMHDR *pNMHDR, LRESULT *pResult)
//{
//	// TODO: Add your control notification handler code here
//
//
//	CPoint myPoint;
//	UINT uFlag;
//
//	GetCursorPos(&myPoint);
//	ScreenToClient(&myPoint);
//	HTREEITEM hItem = HitTest(myPoint, &uFlag);
//	if(NULL == hItem ) return;
//
//	if (_T("计算机")==GetItemText(hItem))  return;
//
//	// 用户点选了一个目录项
//	// (TVHT_ONITEM & uFlag)的原因:如果不添加,即使在离图标或标识的较远的地方点一下左键(没在正上面),也会有有树的展开和收缩动作
//	// (0x0010&uFlag) 如果不加此项,则点击+号时,AddSubDirAsItem不会被调用,下一级的有子目录的文件夹前面就没有+号
//	// #define TVHT_ONITEMBUTTON   0x0010  我是通过跟踪试验找到这个值的 
//	if ((TVHT_ONITEM & uFlag)||(0x0010&uFlag))
//	{
//		AddSubDirAsItem(hItem);
//	}
//	else return;
//	Expand( hItem, TVE_EXPAND );
//
//
//
//	BOOL bCheck;
//	if(hItem && (TVHT_ONITEMSTATEICON & uFlag))
//	{
//	       bCheck = GetCheck(hItem);
//	       SetChildCheck(hItem, !bCheck);
//	}
//
//
//
//	*pResult = 0;
//}

//这里也是利用反射,好处是可以把消息的处理完全封闭到类内,实现封闭
void CDirTreeCtrl::OnTvnItemexpanding(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
	// TODO: Add your control notification handler code here
	//HTREEITEM hItem = GetSelectedItem();  // 注意:用这个函数不行,在+号打开时,子目录总是没有+号,尽管子目录中有子项
	TV_ITEM tvi= pNMTreeView->itemNew;
	HTREEITEM hItem = tvi.hItem;

	if(NULL == hItem ) return;

	if (_T("计算机")==GetItemText(hItem))  return;

	AddSubDirAsItem(hItem);



	*pResult = 0;
}


©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页