[MFC]DriveTree应用:CTreeView的示例

!!本章将展示一个简单的文件系统树形目录


1. 使用WIN32 API来检索系统的硬盘驱动并遍历文件系统:

    1) 对于涉及硬盘、文件遍历的操作MFC并没有提供封装好的类,只能通过WIN32 API来进行;

    2) 获取计算机的硬盘驱动列表:

         i. DWORD ::GetLogicalDrive(VOID);

         ii. 返回的硬盘驱动列表就保存在一个DWORD中,位0表示驱动器A是否存在,位1表示驱动器B是否存在,以此类推;

    3) 由于驱动器也有类型,比如硬盘、移动设备、网络远程硬盘、光驱等,因此也需要判断驱动器的类型:

         i. UINT ::GetDriveType(LPCTSTR lpRootPathName);

         ii. lpRootPathName是驱动器的路径,形式必须是Windows风格的,例如:C:\,但是在源代码中要转义"C:\\"

         iii. 返回值UINT以DRIVE_打头,分别表示不同类型的驱动:

DRIVE_UNKNOWN:表示类型未知(一般是因为驱动款式太新或者错误发生);

DRIVE_NO_ROOT_DIR:驱动器没有根目录(通常是因为没有格式化);

DRIVE_REMOVABLE:移动设备;

DRIVE_CDROM:光驱;

DRIVE_REMOTE:远程驱动器(网络驱动器);

// 我们平时用的硬盘都符合下面两项

DRIVE_FIXED:驱动器是固定的;

DRIVE_RAMDISK:RAM驱动器;

    4) 遍历文件系统的WIN32 API之前讲过,使用WIN32_FIND_DATA结构和相关函数即可;


2. 从标题栏删除文档名称:

    1) 本程序虽然使用文档/目录视图,但是并没有用到文档的功能,因此想从标题栏去掉默认的文档名称;

    2) 方法:在CMainFrame::PreCreateWindow中包含语句cs.style &= ~FWS_ADDTOTITLE即可;

    3) 该样式本来是默认包含在框架窗口样式中的,现在将其去掉即可;


3. DriveTree程序:

    1) MainFrm的PreCreateWindow:

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
	if( !CFrameWnd::PreCreateWindow(cs) )
		return FALSE;
	// TODO: Modify the Window class or styles here by modifying
	//  the CREATESTRUCT cs

	cs.style &= ~FWS_ADDTOTITLE; // 由于没有用到文档就要去掉文档标题的显示 

	return TRUE;
}
    2) CDriveTreeView:

.h:

// DriveTreeView.h : interface of the CDriveTreeView class
//
/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_DRIVETREEVIEW_H__0618D2ED_8AB2_4A32_8014_6ABAE176EC9A__INCLUDED_)
#define AFX_DRIVETREEVIEW_H__0618D2ED_8AB2_4A32_8014_6ABAE176EC9A__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000


class CDriveTreeView : public CTreeView
{
protected: // create from serialization only
	CDriveTreeView();
	DECLARE_DYNCREATE(CDriveTreeView)

// Attributes
public:
	CDriveTreeDoc* GetDocument();

// Operations
public:

// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CDriveTreeView)
	public:
	virtual void OnDraw(CDC* pDC);  // overridden to draw this view
	virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
	protected:
	virtual void OnInitialUpdate(); // called first time after construct
	//}}AFX_VIRTUAL

// Implementation
public:
	virtual ~CDriveTreeView();
#ifdef _DEBUG
	virtual void AssertValid() const;
	virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
	void DeleteAllChildren(HTREEITEM hItem); // 删除hItem的所有子结点
	// hItem的路径是lpszItemPath,然后为hItem添加所有的子目录项目,返回实际添加的子项目数
	int AddDirectories(HTREEITEM hItem, LPCTSTR lpszItemPath); 
	void DeleteFirstChild(HTREEITEM hItem); // 删除hItem的第一个子结点
	CString GetPathFromItem(HTREEITEM hItem); // 从hItem中获得项目所代表的文件的完整路径
	// hItem的路径是lpszPath,根据其是否有子项目来设定其是否应该有加减号按钮
	BOOL SetButtonState(HTREEITEM hItem, LPCTSTR lpszPath); 
	BOOL AddDriveItem(LPCTSTR lpszDrivePath); // 一次只添加一条根驱动结点,返回值表示成功与否
	int AddDrives(); // 向树形控件中添加所有根驱动器结点(只添加驱动器结点,不递归添加其中的子项目),返回添加了几个驱动器
	CImageList m_ilDrives; // 树形控件需要绑定的图形控件列表
	// 里面包含了硬盘、CD、软盘、远程硬盘、打开和未打开文件夹的图形
	//{{AFX_MSG(CDriveTreeView)
		// NOTE - the ClassWizard will add and remove member functions here.
		//    DO NOT EDIT what you see in these blocks of generated code !
	//}}AFX_MSG
	afx_msg void OnItemExpanding(NMHDR* pNMHDR, LRESULT* pResult); // 处理结点展开通知
	DECLARE_MESSAGE_MAP()
};

#ifndef _DEBUG  // debug version in DriveTreeView.cpp
inline CDriveTreeDoc* CDriveTreeView::GetDocument()
   { return (CDriveTreeDoc*)m_pDocument; }
#endif

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_DRIVETREEVIEW_H__0618D2ED_8AB2_4A32_8014_6ABAE176EC9A__INCLUDED_)

.cpp:

// DriveTreeView.cpp : implementation of the CDriveTreeView class
//

#include "stdafx.h"
#include "DriveTree.h"

#include "DriveTreeDoc.h"
#include "DriveTreeView.h"

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

/////////////////////////////////////////////////////////////////////////////
// CDriveTreeView

IMPLEMENT_DYNCREATE(CDriveTreeView, CTreeView)

BEGIN_MESSAGE_MAP(CDriveTreeView, CTreeView)
	//{{AFX_MSG_MAP(CDriveTreeView)
		// NOTE - the ClassWizard will add and remove mapping macros here.
		//    DO NOT EDIT what you see in these blocks of generated code!
	//}}AFX_MSG_MAP
	ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, OnItemExpanding) // 控件视图的通知必须反射
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDriveTreeView construction/destruction

CDriveTreeView::CDriveTreeView()
{
	// TODO: add construction code here

}

CDriveTreeView::~CDriveTreeView()
{
}

BOOL CDriveTreeView::PreCreateWindow(CREATESTRUCT& cs)
{
	// TODO: Modify the Window class or styles here by modifying
	//  the CREATESTRUCT cs

	if (!CTreeView::PreCreateWindow(cs)) // 别忘了先调用基类的PreCreateWindow函数试试水
		return FALSE;

	// 指定树形视图的树形是具有连线(包括根结点之间的)
	// 并且具有加减号展开按钮
	// 并一直都高亮被选中的项目
	cs.style |= TVS_HASLINES		|
				TVS_LINESATROOT		|
				TVS_HASBUTTONS		|
				TVS_SHOWSELALWAYS;

	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CDriveTreeView drawing

void CDriveTreeView::OnDraw(CDC* pDC) // 由于树形控件的显示都交给控件本身处理因此这里无需重绘什么东西
{
	CDriveTreeDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	// TODO: add draw code for native data here
}

void CDriveTreeView::OnInitialUpdate()
{
	CTreeView::OnInitialUpdate(); // 别忘了先初始化基类的部分

	// TODO: You may populate your TreeView with items by directly accessing
	//  its tree control through a call to GetTreeCtrl(). // 一定要用GetTreeCtrl来访问树形控件!!
	// 如果直接将GetTreeCtrl的返回值作为树形视图的数据成员的话会非常不安全

	m_ilDrives.Create(IDB_DRIVEIMAGES, 16, 1, RGB(255, 0, 255)); // 将位图资源和对象绑定
	GetTreeCtrl().SetImageList(&m_ilDrives, TVSIL_NORMAL); // 绑定并设置为而状态图标

	// 树形控件准备就绪后就往控件中添加驱动器项目
	AddDrives(); // 并且已经显示出来了

	// 这里程序的画面初始化为折叠所有根驱动
	// 但是只打开当前路径所在盘符的根驱动(展开)并选中

	// 下面四句为了得到当前路径所在的盘符,即当前盘符
	TCHAR szBuffer[MAX_PATH]; // MAX_PATH是MFC预定义宏,它规定了Windows系统中路径的最大长度260
	::GetCurrentDirectory(sizeof(szBuffer) / sizeof(TCHAR), szBuffer);
	CString strCurrentPath(szBuffer); // 将当前路径字符串初始化为CString对象
	CString strCurrentDrivePath(strCurrentPath.Left(3)); // 再获得当前路径下的盘符路径

	// 在树中找到当前盘符所代表的结点
	HTREEITEM hItem = GetTreeCtrl().GetNextItem(NULL, TVGN_ROOT);
	while (hItem) {
		if (GetTreeCtrl().GetItemText(hItem) == strCurrentDrivePath) break;
		hItem = GetTreeCtrl().GetNextSiblingItem(hItem);
	}

	if (hItem) { // 如果能找到当前盘符所在的结点,则展开并选中它
		GetTreeCtrl().Expand(hItem, TVE_EXPAND);
		GetTreeCtrl().Select(hItem, TVGN_CARET);
	}
}

/////////////////////////////////////////////////////////////////////////////
// CDriveTreeView diagnostics

#ifdef _DEBUG
void CDriveTreeView::AssertValid() const
{
	CTreeView::AssertValid();
}

void CDriveTreeView::Dump(CDumpContext& dc) const
{
	CTreeView::Dump(dc);
}

CDriveTreeDoc* CDriveTreeView::GetDocument() // non-debug version is inline
{
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CDriveTreeDoc)));
	return (CDriveTreeDoc*)m_pDocument;
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CDriveTreeView message handlers



int CDriveTreeView::AddDrives()
{
	int nDrivesAdded = 0; // 总共添加了多少个根驱动结点,将作为返回值

	DWORD dwDriveList = ::GetLogicalDrives(); // 得到机器的根驱动里列表
	CString strDrivePath = _T("?:\\"); // 驱动器的路径,具体是什么盘先保留一个占位符
	int nBitPos = 0; // 目前正检测到dwDriveList的第几位
	while (dwDriveList) {
		if (dwDriveList & 1) {
			strDrivePath.SetAt(0, _T('A') + nBitPos); // 检测成功则设定其盘符
			if (AddDriveItem(strDrivePath)) nDrivesAdded++; // 逐条添加	
		}
		dwDriveList >>= 1; // 右移一位继续检测
		nBitPos++;
	}

	return nDrivesAdded;
}

// 一次只添加一条根驱动结点
BOOL CDriveTreeView::AddDriveItem(LPCTSTR lpszDrivePath)
{
	HTREEITEM hItem;

	UINT nDriveType = ::GetDriveType(lpszDrivePath);
	switch (nDriveType) 
	{
#ifndef __IMAGE_LIST_INDEX__
#define __IMAGE_LIST_INDEX__
#define ILI_HARD_DISK		0	// 普通硬盘
#define ILI_FLOPPY			1	// 软盘,移动盘也用软盘表示
#define	ILI_CDROM			2	// 光驱
#define ILI_NET_DRIVE		3	// 网络设备,以上4个(包括网络设备)打开和关闭都用同一个图标
#define ILI_CLOSED_FOLDER	4	// 关闭的文件夹
#define	ILI_OPEN_FOLDER		5	// 打开的文件夹
#endif

// 这里规定所有根驱动器结点必须要具有加减号按钮(展开时再添加子项目,这样减少资源消耗,同时也提高运行效率)
// 因此这里先给它们添加一个空的子结点

	case DRIVE_FIXED: // 普通硬盘
	case DRIVE_RAMDISK:
		hItem = GetTreeCtrl().InsertItem(lpszDrivePath, ILI_HARD_DISK, ILI_HARD_DISK);
		GetTreeCtrl().InsertItem(_T(""), ILI_CLOSED_FOLDER, ILI_CLOSED_FOLDER, hItem);
		break;

	case DRIVE_REMOTE: // 网络硬盘就当做普通硬盘处理,除了图标不一样,其它都一样
		hItem = GetTreeCtrl().InsertItem(lpszDrivePath, ILI_NET_DRIVE, ILI_NET_DRIVE);
		GetTreeCtrl().InsertItem(_T(""), ILI_CLOSED_FOLDER, ILI_CLOSED_FOLDER, hItem);
		break;

	case DRIVE_REMOVABLE:
		hItem = GetTreeCtrl().InsertItem(lpszDrivePath, ILI_FLOPPY, ILI_FLOPPY);
		GetTreeCtrl().InsertItem(_T(""), ILI_CLOSED_FOLDER, ILI_CLOSED_FOLDER, hItem);
		break;

	case DRIVE_CDROM:
		hItem = GetTreeCtrl().InsertItem(lpszDrivePath, ILI_CDROM, ILI_CDROM);
		GetTreeCtrl().InsertItem(_T(""), ILI_CLOSED_FOLDER, ILI_CLOSED_FOLDER, hItem);
		break;

	default: return FALSE; // 这里程序设定为不接受其它类型的驱动
	}

	return TRUE;
}

// 该函数针对非根驱动器结点的文件夹项目(本程序的结点只包含跟驱动器和文件夹(目录))
// 还未展开时调用此函数,根据其是否具有子目录来决定其是否具有按钮
// 返回值也表示是否具有加减号按钮
BOOL CDriveTreeView::SetButtonState(HTREEITEM hItem, LPCTSTR lpszPath)
{ // 对于一个未展开的文件夹结点,如果在真实的文件系统中该结点所代表的文件夹具有子目录
	// 则给该结点添加一个空的子项目让其具有加减号按钮
  // 如果在文件系统中该结点对应的文件夹无子结点则不给它添加任何子项目,让其不具有加减号按钮
// 有没有按钮是由其是否具有子项目决定的

	// lpszPath可能会是由WIN32 API获取的,API获取的路径末尾可能没有路径分割符
	// 现在我们要在该路径后添加其它字符,因此必须要保证最后一位是路径分割符
	CString strPath = lpszPath;
	if (strPath.Right(1) != _T("\\")) strPath += _T("\\");
	strPath += _T("*.*"); // 表示可以搜索任意文件

	HANDLE hFind;
	WIN32_FIND_DATA fd;
	if ((hFind = ::FindFirstFile(strPath, &fd)) == INVALID_HANDLE_VALUE)
		return FALSE; // 没有任何东西,不添加子项目,没有按钮

	do {
		if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			CString strFilePath = (LPCTSTR)&fd.cFileName;
			if (strFilePath != _T(".") && strFilePath != _T("..")) { // 如果在底下找到了至少一个目录
				// 但不能是.和..,这两个任何一个目录都会有
				// 那么就添加一个空项目让其具有按钮
				GetTreeCtrl().InsertItem(_T(""), ILI_CLOSED_FOLDER, ILI_CLOSED_FOLDER, hItem);
				::FindClose(hFind); // 达到目的后立马停止查找,成功退出
				return TRUE;
			}
		}
	} while (::FindNextFile(hFind, &fd)); // 没找到就继续查找

	::FindClose(hFind); // 全部找完还是没找到就失败退出
	return FALSE;
}

void CDriveTreeView::OnItemExpanding(NMHDR* pNMHDR, LRESULT* pResult)
{
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
	HTREEITEM hItem = pNMTreeView->itemNew.hItem;

	CString strItemPath = GetPathFromItem(hItem);

	if (pNMTreeView->action == TVE_EXPAND) {
		DeleteFirstChild(hItem); // 先删除临时添加的空项目
		if (!AddDirectories(hItem, strItemPath)) // 在添加它底下的所有子目录
			*pResult = TRUE; // 如果没有子项目就没必要触发Expand
	}
	else { // action == TVE_COLLAPSE
		*pResult = FALSE; // 折叠时从有到无,因此一定要触发Expand产生折叠效果

		DeleteAllChildren(hItem); // 如果是折叠就先删除其所有子项目
		if (!GetTreeCtrl().GetParentItem(hItem)) // 对于根项目一定要添加一个临时的空子项目
			GetTreeCtrl().InsertItem(_T(""), ILI_CLOSED_FOLDER, ILI_CLOSED_FOLDER, hItem);
		else // 对于其它结点则还要判断一下其是否应该具有加减号按钮
			SetButtonState(hItem, strItemPath);
	}
}

CString CDriveTreeView::GetPathFromItem(HTREEITEM hItem)
{
	CString strResultPath = GetTreeCtrl().GetItemText(hItem);

	HTREEITEM hParentItem; // 不断迭代获取父结点的文件名并连接到最终的路径中
	while (hParentItem = GetTreeCtrl().GetParentItem(hItem)) {
		CString strParentText = GetTreeCtrl().GetItemText(hParentItem);
		if (strParentText.Right(1) != _T("\\")) strParentText += _T("\\");
		strResultPath = strParentText + strResultPath;
		hItem = hParentItem;
	}

	return strResultPath;
}

void CDriveTreeView::DeleteFirstChild(HTREEITEM hItem)
{
	HTREEITEM hChildItem = GetTreeCtrl().GetChildItem(hItem);
	if (hChildItem) GetTreeCtrl().DeleteItem(hChildItem);
}

void CDriveTreeView::DeleteAllChildren(HTREEITEM hItem)
{
	HTREEITEM hChildItem = GetTreeCtrl().GetChildItem(hItem);
	
	if (!hChildItem) return ;
	
	while (hChildItem) {
		HTREEITEM hNextItem = GetTreeCtrl().GetNextSiblingItem(hChildItem);
		GetTreeCtrl().DeleteItem(hChildItem);
		hChildItem = hNextItem;
	}
}

int CDriveTreeView::AddDirectories(HTREEITEM hItem, LPCTSTR lpszItemPath)
{
	CString str = lpszItemPath;
	if (str.Right(1) != _T("\\")) str += _T("\\");
	CString strItemPath = str + _T("*.*");

	HANDLE hFind;
	WIN32_FIND_DATA fd;
	if ((hFind = ::FindFirstFile(strItemPath, &fd)) == INVALID_HANDLE_VALUE) {
		if (!GetTreeCtrl().GetParentItem(hItem))
		// 考虑到用户可能会展开没有子目录的结点
		// 由于展开前会先删除子项目,但是如果该结点是根驱动结点那么必须要保留其按钮
		// 因此必须要考虑到这种情况,要为根驱动结点添加一个空项目让其保持具有按钮
			GetTreeCtrl().InsertItem(_T(""), ILI_CLOSED_FOLDER, ILI_CLOSED_FOLDER, hItem);
		return 0; // 添加了0个项目
	}

	int nDirAdded = 0; // 总共加入的子目录个数
	do {
		if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			CString strChildItemText = (LPCTSTR)&fd.cFileName; // cFileName没有路径
			if (strChildItemText != _T(".") && strChildItemText != _T("..")) {
				// 如果是正常文件夹就添加到树中
				HTREEITEM hNewChildItem = GetTreeCtrl().InsertItem(strChildItemText, ILI_CLOSED_FOLDER, ILI_OPEN_FOLDER, hItem);
				CString strChildItemPath = str + strChildItemText; // 再得到子项目对应的目录的完整路径
				SetButtonState(hNewChildItem, strChildItemPath); // 添加的项目还需要考虑是否具有按钮
				nDirAdded++;
			}
		}
	} while (::FindNextFile(hFind, &fd)); // 接着找下一个子目录

	::FindClose(hFind);
	return nDirAdded;
}

    3) IDB_DRIVEIMAGES位图:


!!注意格式是.bmp,命名为Drives.bmp;

你可能感兴趣的:(mfc,CTreeView)