




已阅读5页,还剩10页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
MFC树控件实现目录树浏览由一个全封闭的派生目录树类实现这个例程是由VC+ CTreeCtrl控件派生的完整目录树类,可以浏览本地PC的目录树。VS2008下经作者试验,结果准确,反应迅速。 本例程由一个CDirTreeCtrl类(DirTreeCtrl.h, DirTreeCtrl.cpp)和一个对话框(作为test bench)构成。 本例使用网上下载的源码,消除原有的bugs,使程序可以正确流畅地运行;简化并明确功能和目的,删除冗余代码,改进结构,使代码结构简练清晰易懂,并且在代码中作者做了详细的注解,主要目的是为本作者自己以后可以迅速查阅,顺便也就适应不同level的编程朋友阅读交流;提升了反应速度和改进结构和消息机制。本例程较源码增加和改进的方面如下:1 以往的CTreeCtrl控件构造的目录树,都是把鼠标消息和树消息的处理放在父窗口中,这样破坏了封闭性,不利于代码的重复利用, 本例程构造目录树派生类,最后的实现目录树类的完全封闭。( 这个主意最先来自源码例程,那个作者已经构造了派生类CDirTreeCtrl,但是很遗憾,他没有实现目录树的类的封闭,他只是假期作业,没时间了,他最后还是在父窗口类中处理消息。 本例程非常自然地利用了MFC反射功能,完美实现了这一个构想。 )2 网上的例程都以鼠标左键的OnClick处理来定位鼠标位置,根据鼠标位置由HitTest来确定鼠标点击项。这样做每当鼠标点击控件,即使没有点击目录项,都会调用处理函数,效率很受影响; 而且,目录树上每一个item有button,label, icon等多个子项,HitTest后的uFlag的值不相同,点击时位置需要限定在button,label, icon这三个子项上才能有反应,其余位置要忽略。 (这种处理方法本例程有试验,被注释掉,恢复一下即可试验。) 试验很多可以利用的消息的结果表明,最好是利用 =TVN_ITEMEXPANDING 反射消息,添加OnTvnItemexpanding(NMHDR *pNMHDR, LRESULT *pResult),从pNMHDR中得到被选中的item的句柄(HTREEITEM hItem = pNMTreeView-itemNew;),操作时系统已经肯定知道选中的目录项,处理只需只针对这个句柄,形式及其简单明了,并且效率提高。3如果初始化利用递归时把全部硬盘的目录全部建好并加入目录树,耗时很大。本人200G硬盘,安装程序较多,测试结果运行时间约30分钟。因此这种把全部目录加入的方法行不通,必须在目录树展开的瞬间,仅仅把该目录内下一级子目录的项全部添加到树上,这个见解也是网上很多朋友认同的。但是在使用中,会发现,下一级的子目录中即使有子项,前面的button上也没有+号,但是你展开后再合上,再展开会有+号的。为避免这个问题, 我的方法是在鼠标选中任意一个子项时,首先删除该子项下所有的子目录项,然后不仅仅加入该子项的所有子目录,还要把子目录中有子目录的项子目录也加入。这样就会避免有子目录项+号没有的问题。2013/10/2于国庆长假 DirTreeCtrl.h 头文件#pragma once/ CDirTreeCtrlclass CDirTreeCtrl : public CTreeCtrlDECLARE_DYNAMIC(CDirTreeCtrl)/dataprivate: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 functionsprotected:/AFX_MSG(CDirTreeCtrl)/ NOTE - the ClassWizard will add and remove member functions here./AFX_MSGDECLARE_MESSAGE_MAP()public:/afx_msg void OnNMClick(NMHDR *pNMHDR, LRESULT *pResult); / 用这个作为鼠标消息响应也可以,但是下面的一个更好afx_msg void OnTvnItemexpanding(NMHDR *pNMHDR, LRESULT *pResult);DirTreeCtrl.cpp#include stdafx.h#include test_dir_tree.h#include DirTreeCtrl.h/ CDirTreeCtrlIMPLEMENT_DYNAMIC(CDirTreeCtrl, CTreeCtrl)/ CDirTreeCtrl message handlersCDirTreeCtrl:CDirTreeCtrl()CDirTreeCtrl:CDirTreeCtrl() m_imgList.Detach();BEGIN_MESSAGE_MAP(CDirTreeCtrl, CTreeCtrl) /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() SHFILEINFO shFinfo; HIMAGELIST hImgList = NULL; if ( GetImageList( TVSIL_NORMAL ) ) m_imgList.Detach(); hImgList = (HIMAGELIST)SHGetFileInfoW(_T(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 szDrives260; 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; nstrDrives.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 = _tcslen(pDrive);pDrivelen-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 listDestroyIcon( 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 listDestroyIcon( 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 );/*pResult = 0;/经本人测试,用这个消息作为鼠标点击响应的入口最好/用上面Click消息也可以/这里也是利用反射,好处是可以把消息的处理完全封闭到类内,实现封闭void CDirTreeCtrl:OnTvnItemexpanding(NMHDR *pNMHDR, LRESULT *pResult)LPNMTREEVIEW pNMTreeView = reinterpret_cast(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;Test Bench 测试平台搭建 构建一个MFC基于对话框的应用程序,把上面CDirTreeCtrl类(DirTreeCtrl.h, DirTreeCtrl.cpp)两个文件,拷入工程文件中。在对话框资源中加入一个树控件,如下图:然后点击树控件为它加入一个变量m_treeDir,类名手工改为CDirTreeCtrl。 如下图:主对话框对象中就会增加一个CDirTreeCtrl的变量对象m_treeDir。然后在主对话框对象的OnInitDialog()中增加一个调用 m_treeDir.DisplayTree(NULL)即可,见下面: BOOL Ctestbench_dirtreeDlg:OnInitDialog()CDialog:OnInitDialog();/ 将“关于.”菜单项添加到系统菜单中。/ IDM_ABOUTBOX 必须在系统命令范围内。ASSERT(IDM_ABOUTBOX & 0xFFF0) = IDM_ABOUTBOX);ASSERT(IDM_ABOUTBOX AppendMenu(MF_SEPARATOR);pSysMenu-AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);/ 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动/ 执行此操作SetIcon(m_hIcon, TRUE);/ 设置大图标SetIcon(m_hIcon, FALSE);/ 设置小图标/ TODO: 在此添加额外的初始化代码m_treeDir.DisplayTree(NULL); / 就这里,加一句话即可。return TRUE; / 除非将焦点设置到控件,否则返回TRUE到此,就可以开始调试。 问题1:应用MFC CTreeCtrl 树控件,如果从中间删除几个子项,那么它带的的子项是不是就会自动删掉?是否需要我们先删掉它的子项才然后可以删掉它本身? 可以同过试验证明吗?BOOLDeleteItem(HTREEITEMhItem ); MSDN上说:hItem Handle of the tree item to be deleted. If hitem has the TVI_ROOT value, all items are deleted from the tree view control. 由此估计只要删掉一项,其子项自然会删除,不必我们自己再去删除。网上有个例子:在实现删除功能时,应对存在子项的树项进行提示,以警告用户是否连同其子项一起删除。void CVCTREEDlg:OnDel() /删除子项功能函数HTREEITEM hSel=m_TreeCtrl.GetSelectedItem();/取得选项句柄if(hSel=NULL) return;/无任何选项则返回if(m_
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
评论
0/150
提交评论