分享
 
 
 

仿Explorer的目录树视图类

王朝vc·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

边城浪子(QQ:16168666)

在网上找了很长时间也没有关于这方面的VC代码.倒是找到几个控件,用起来却很不爽.很奇怪WINDOWS里为什么没有这样的控件.没办法.自己实现一个呗.

下面这个类从CTreeView拜生.参考了DELPHI的相应控件源代码写的,本人对DELPHI不会,所以看得很勉强,好多地方也没看明白.不过这个类使用起来的效果,嘿嘿.已经和那个控件差不多了:).只是比起Explorer里的来,还差着一些.具体差在什么地方,大家用一用就知道了.

将你的类从此类拜生,就能做你想做的了. 该类只导出两个函数:

1.SetCurPath : 设置选中一个路径.如果需要的话,它会展开相应有文件夹

2.GetSelectedDir : 返回当前选中的文件夹,没有选中的话,返回自然就是空串

如果你有兴趣,自己对这个类进行扩充吧.欢迎和我交流.

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

// ShellFolderView.h ---- 头文件

#if !defined(AFX_SHELLFOLDERVIEW_H__D30D10ED_3D11_4873_9909_146B8E18BEB1__INCLUDED_)

#define AFX_SHELLFOLDERVIEW_H__D30D10ED_3D11_4873_9909_146B8E18BEB1__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

// ShellFolderView.h : header file

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

// 该类根据DELPHI相应控件源代码改编

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

简介:

CShellFolderView从CTreeView拜生,它显示整个WINDOWS的文件目录树.在WINDOWS中

又叫外壳名字空间(Shell Name Space).外壳名字空间是Windows下的标准文件系统,

它大大扩展了Dos文件系统,形成了以“桌面”(Desktop)为根的单一的文件系统

树,原有的C盘、D盘等目录树变成了“我的电脑”这一外壳名字空间子树的下一级

子树,而像“控制面板”、“回收站”、“网上邻居”等应用程序及“打印机”等设

备也被虚拟成了外壳名字空间中的节点。另外,与DOS中物理存储只能和文件系统

项一一对应这一点不同的是,一个实际目录在外壳名字空间中可以表现为不同的项。

例如“我的文档”与“C:\My Documents”其实都指向“C:\My Documents”目录,但

它们在外壳名字空间中是不同的项。

外壳名字空间下的路径: PIDL

PIDL是一个元素类型为ITEMIDLIST结构的数组,数组中元素的个数是未知的,但紧接

着数组末尾的必是一个双字节的零。每个数组元素代表了外壳名字空间树中的一层(

即一个文件夹或文件),数组中的前一元素代表的是后一元素的父文件夹。由此可见,

PIDL实际上就是指向一块由若干个顺序排列的ITEMIDLIST结构组成、并在最后有一个

双字节零的空间的指针。所以PIDL的类型就被Windows定义为ITEMIDLIST结构的指针。

PIDL亦有“绝对路径”与“相对路径”的概念。表示“相对路径”的PIDL只有一个

ITEMIDLIST结构的元素,用于标识相对于父文件夹的“路径”;表示“绝对路径”的

PIDL(简称为“绝对PIDL”)有若干个ITEMIDLIST结构的元素,第一个元素表示外壳

名字空间根文件夹(“桌面”)下的某一子文件夹A,第二个元素则表示文件夹A下的

某一子文件夹B,其余依此类推。这样绝对PIDL就通过保存一条从“桌面”下的直接子

文件夹或文件的绝对PIDL与相对PIDL是相同的,而其他的文件夹或文件的相对PIDL就

只是其绝对PIDL的最后一部分了。由于所有的PIDL都是从桌面下的某一个子文件夹开

始的,所以对于桌面本身来说,它的PIDL数组显然一个元素都没有。这样就只剩下PIDL

数组最后的那个双字节的零了。所以,“桌面”的PIDL就是一个16位的零。

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

// 监视指定的文件夹的变化的类. 该类由CShellFolderView专用,所以定义在源文件中

// 不对外部公开,在这里进行提前声明,因为下面要用到

class CFolderChangeMonitor;

// 外壳名字空间下结点的串表示

typedef struct _FILEPATHLIST

{

char cNormal[MAX_PATH]; // 绝对名称(如,"我的电脑")

char cForParsing[MAX_PATH]; // 用来分析解释的名称, 可以根据它来产生PIDL

char cInFolder[MAX_PATH]; // 相对名称

} FILEPATHLIST, *LPFILEPATHLIST;

// 文件夹的分类

typedef enum emFolderType{

ftFolder, // 文件夹

ftNetworkNeighborhood, // 网上邻居

ftRecycleBin, // 回收站

ftMyComputer, // 我的电脑

ftDesktop, // 桌面

ftNetNode, // 网络结点

ftNone // 空

}FOLDERTYPE;

// 路径信息

typedef struct _PATHINFO{

char cPath[MAX_PATH]; // 路径

char cText[MAX_PATH]; // 名称

UINT uIconIndex; // 图标索引

UINT uSelectedIndex; // 选中时的图标索引

LPITEMIDLIST pidl; // PIDL

}PATHINFO,*LPPATHINFO;

// 结点数据

typedef struct _FOLDERNODE{

char cPath[MAX_PATH]; // 路径

BOOL bHasParent; // 是否有父结点

LONG Vol_Ser; // 卷标

ULONG dwAttributes; // 属性

FOLDERTYPE fType; // 类型

IShellFolder* pShellFolder; // 指向该文件夹的IShellFolder接口

LPITEMIDLIST pidl; // 文件夹的PIDL

WORD widlen; // PIDL的长度

CFolderChangeMonitor* pMonitorThread; // 监视文件夹变化的线程

}FOLDERNODE, *LPFOLDERNODE;

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

// CShellFolderView view

class CShellFolderView : public CTreeView

{

protected:

CShellFolderView(); // protected constructor used by dynamic creation

DECLARE_DYNCREATE(CShellFolderView)

// Attributes

public:

// Operations

public:

BOOL SetCurPath(LPCTSTR lpszPath); // 设置当前选中的文件夹, 成功返回TRUE

CString GetSelectedDir(); // 取得当前选中的文件夹,没有则返回一个空串

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CShellFolderView)

protected:

//virtual void OnDraw(CDC* pDC); // overridden to draw this view

//}}AFX_VIRTUAL

// Implementation

protected:

virtual ~CShellFolderView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

// Generated message map functions

protected:

//{{AFX_MSG(CShellFolderView)

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

afx_msg void OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult);

afx_msg void OnDestroy();

//}}AFX_MSG

afx_msg LRESULT OnFolderChanged(WPARAM wParam, LPARAM); // 处理文件夹发生变化的消息

DECLARE_MESSAGE_MAP()

private:

IMalloc* m_pMalloc; // COM内存分配接口用来分配和回收PIDL使用的空间

protected:

void GetSpecialFolder(int nFolder, LPPATHINFO lppi); // 取得指定的特殊文件夹的信息

void GetNetHood(LPPATHINFO lpPI); // 取得网上邻居结点

// 创建一个结点

HTREEITEM CreateFolderNode(LPITEMIDLIST lpidl, HTREEITEM hParent, BOOL bVirtual);

CString GetPath(HTREEITEM hNode); // 取得结点的对应的路径

BOOL AttachFolders(HTREEITEM hNode); // 展开指定的结点下的子结点

LONG GetVol_Ser(LPCTSTR strDriver); // 获取卷标

void ShowDesktop(); // 显示桌面

void RetrieveSysImageList(); // 获取系统图像列表

void FreeNode(HTREEITEM hNode); // 释放结点

void FreeNodeData(LPFOLDERNODE lpfn); // 释放结点数据

int Expand(HTREEITEM hNode, UINT nCode); // 展开结点

};

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

//{{AFX_INSERT_LOCATION}}

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

#endif // !defined(AFX_SHELLFOLDERVIEW_H__D30D10ED_3D11_4873_9909_146B8E18BEB1__INCLUDED_)

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

// ShellFolderView.cpp : implementation file

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

#include "stdafx.h"

#include "ShellFolderView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

#define WM_U_FOLDERCHANGED (WM_USER+215) // 消息

// 一些固定的名字:

static const char c_cMyComputer[] = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"; // Normal = 我的电脑

static const char c_cNetworkNeighborhood[] = "::{208D2C60-3AEA-1069-A2D7-08002B30309D}"; // Normal = 网上邻居

static const char c_cRecycleBin[] = "::{645FF040-5081-101B-9F08-00AA002F954E}"; // Normal = 回收站

static const char c_cController[] = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}"; // Normal = 控制面板

static const char c_cPrinter[] = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\\::{2227A280-3AEA-1069-A2DE-08002B30309D}"; // Normal = 打印机

static const char c_cDial[] = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\\::{7007ACC7-3202-11D1-AAD2-00805FC1270E}"; // Normal = 网络和拨号连接

static const char c_cFonts[] = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\\::{D20EA4E1-3957-11D2-A40B-0C5020524152}"; // Normal = 字体

static const char c_cManager[] = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\\::{D20EA4E1-3957-11D2-A40B-0C5020524153}"; // Normal = 管理工具

static const char c_cPlans[] = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\\::{D6277990-4C6A-11CF-8D87-00AA0060F5BF}"; // Normal = 任务计划

// 文件夹监视线程类

class CFolderChangeMonitor

{

public:

CFolderChangeMonitor(LPCTSTR lpszPath, HWND hWnd, WPARAM wParam);

~CFolderChangeMonitor();

private:

CWinThread* m_pThread;

CString m_strPath;

HWND m_hWnd;

WPARAM m_wParam;

BOOL m_bStop;

friend UINT MonitorProc(void* lp);

void Run();

};

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

// CFolderChangeMonitor class

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

// 文件夹监视线程类的构造函数

// lpszPath : 被监视的文件夹

// hWnd : 文件夹已经改变的消息将发往该窗口

// wParam : 消息的参数,在这里是树上的一结点句柄

CFolderChangeMonitor::CFolderChangeMonitor(LPCTSTR lpszPath, HWND hWnd, WPARAM wParam)

:m_hWnd(hWnd), m_wParam(wParam), m_strPath(lpszPath)

{

m_bStop = FALSE;

// 开始监视线程

m_pThread = AfxBeginThread(MonitorProc, (void*)this);

}

CFolderChangeMonitor::~CFolderChangeMonitor()

{

// 停止监视线程

m_bStop = TRUE;

// 原来可以等待线程结束后才返回,后来不知道怎么搞的,居然会死锁,只好先不等待了,嘻嘻

// ::WaitForSingleObject(m_pThread->m_hThread, INFINITE);

}

// 监视线程函数

UINT MonitorProc(void*lp)

{

if(lp)

{

// 将线程反调回类中

((CFolderChangeMonitor*)lp)->Run();

}

return 0;

}

// 监视函数

void CFolderChangeMonitor::Run()

{

// 检测结束标志, 没有就一直循环

while(!m_bStop)

{

// 创建事件句柄,当文件夹发生了指定的变化时,该事件被触发

HANDLE handle = ::FindFirstChangeNotification(m_strPath, FALSE,

FILE_NOTIFY_CHANGE_DIR_NAME);

// 如果事件创建不成功则结束

if(handle == INVALID_HANDLE_VALUE) break;

// 开始监视

if(::FindNextChangeNotification(handle))

{

int i = WAIT_TIMEOUT;

// 除非指明结束监视或者发生了一次事件,否则一直循环等待

while(i == WAIT_TIMEOUT && !m_bStop)

{

// 等待事件发生(每次等待20mS)因为线程需要不断检测m_bStop

// 标志,以便及时退出,所以不能无限制地等待下去

i = ::WaitForSingleObject(handle, 20);

}

// 循环结束,如果不是指定结束,则一定是发事件被触发了

if(!m_bStop)

{

// 发送消息通知窗口来更新目录树

::SendMessage(m_hWnd, WM_U_FOLDERCHANGED, m_wParam,0);

}

}

else

{

// 监视不能开始,则退出

::FindCloseChangeNotification(handle);

break;

}

// 关闭事件

::FindCloseChangeNotification(handle);

}

}

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

FUNCTION : GetName

描述:lpio lpsf所指的IshellFolder接口代表的文件夹下的相对PIDL,

本函数获得lpi所指项的显示名称,dwFlags表明欲得到的显示名称类型,

lpFriendlyName为存放显示名称的缓冲区。

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

BOOL GetName(LPSHELLFOLDER lpsf,LPITEMIDLIST lpi,DWORD dwFlags,LPSTR lpFriendlyName)

{

STRRET str;

AGAIN:

//得到显示名称

if(NOERROR!=lpsf->GetDisplayNameOf(lpi,dwFlags,&str))

return FALSE;

//根据返回值进行转换

switch(str.uType)

{

case STRRET_WSTR: //如为Unicode字符串,则转成Ansi字符集的字符串case STRRET_WSTR:

if(str.pOleStr != NULL)

WideCharToMultiByte(CP_ACP,0,str.pOleStr,-1,lpFriendlyName,MAX_PATH,NULL,NULL);

else if(dwFlags == SHGDN_NORMAL)

{

dwFlags = SHGDN_FORPARSING;

goto AGAIN;

}

break;

case STRRET_OFFSET: //如为偏移量,则去除偏移量

lstrcpy(lpFriendlyName,(LPSTR)lpi+str.uOffset);

break;

case STRRET_CSTR: // 如为Ansi字符串,则直接拷贝

lstrcpy(lpFriendlyName,(LPSTR)str.cStr);

break;

default: //非法情况

return FALSE;

}

return TRUE;

}

// 获取路径

void GetLongFilePath(IShellFolder* pShellFolder, LPITEMIDLIST lpidl, LPFILEPATHLIST lpfpl)

{

BOOL bRelease = FALSE;

// 如果IShellFolder接口为空,则使用桌面的IShellFolder接口

if(pShellFolder == NULL)

{

if(SHGetDesktopFolder(&pShellFolder) != NOERROR)

return;

bRelease = TRUE;

}

// 取得显示, PIDL及相对名称

GetName(pShellFolder, lpidl, SHGDN_NORMAL, lpfpl->cNormal);

GetName(pShellFolder, lpidl, SHGDN_FORPARSING, lpfpl->cForParsing);

GetName(pShellFolder, lpidl, SHGDN_INFOLDER, lpfpl->cInFolder);

// 取得的COM接口一定要释放

if(bRelease) pShellFolder->Release();

}

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

// CShellFolderView

IMPLEMENT_DYNCREATE(CShellFolderView, CTreeView)

CShellFolderView::CShellFolderView()

{

// 取得内存分配的COM接口, 这里一定要成功,否则接下来的程序中无法分配空间

VERIFY(SHGetMalloc(&m_pMalloc)==NOERROR);

}

CShellFolderView::~CShellFolderView()

{

// 释放IMalloc接口

m_pMalloc->Release();

}

BEGIN_MESSAGE_MAP(CShellFolderView, CTreeView)

//{{AFX_MSG_MAP(CShellFolderView)

ON_WM_CREATE()

ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, OnItemexpanding)

ON_WM_DESTROY()

//}}AFX_MSG_MAP

ON_MESSAGE(WM_U_FOLDERCHANGED, OnFolderChanged)

END_MESSAGE_MAP()

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

// CShellFolderView drawing

//void CShellFolderView::OnDraw(CDC* pDC)

//{

// CDocument* pDoc = GetDocument();

// TODO: add draw code here

//}

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

// CShellFolderView diagnostics

#ifdef _DEBUG

void CShellFolderView::AssertValid() const

{

CTreeView::AssertValid();

}

void CShellFolderView::Dump(CDumpContext& dc) const

{

CTreeView::Dump(dc);

}

#endif //_DEBUG

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

// CShellFolderView message handlers

int CShellFolderView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CTreeView::OnCreate(lpCreateStruct) == -1)

return -1;

// TODO: Add your specialized creation code here

// 设置树控件的风格:带有展开按钮,结点间有连线

GetTreeCtrl().ModifyStyle(0, TVS_HASLINES|TVS_HASBUTTONS|TVS_LINESATROOT);

RetrieveSysImageList(); // 取得系统图标列表

// 显示桌面虚拟文件夹

ShowDesktop();

return 0;

}

// 当某个结点将要被展开前

void CShellFolderView::OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult)

{

NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

// TODO: Add your control notification handler code here

// 如果是展开操作

if(pNMTreeView->action == TVE_EXPAND)

{

// 如果被展开的结点还没有子结点则为该结点附加它的子结点

// 我们不能一开始就将所有的结点添加上去,这将是一个漫长的过程,且浪费

// 大量的系统资源,只有那些被展开的结点才需要显示其子结点

if(GetTreeCtrl().GetChildItem(pNMTreeView->itemNew.hItem) == NULL)

{

AttachFolders(pNMTreeView->itemNew.hItem);

}

}

*pResult = 0;

*pResult = 0;

}

void CShellFolderView::OnDestroy()

{

// 释放所有的结点

FreeNode(NULL);

CTreeView::OnDestroy();

}

// 当被监视的文件夹发生变化时,该消息被监视线程发送过来

LRESULT CShellFolderView::OnFolderChanged(WPARAM wParam, LPARAM)

{

// 变化对应的结点句柄

HTREEITEM hNode = (HTREEITEM)wParam;

CTreeCtrl& tree = GetTreeCtrl(); // 视图上的树控件

// 保存该结点的状态(是否是已经展开的)

BOOL bExpanded = tree.GetItemState(hNode, TVIS_EXPANDED) == TVIS_EXPANDED;

// 它的子结点

HTREEITEM hChild = tree.GetChildItem(hNode);

// 删除该结点下的所有子结点

while(hChild != NULL)

{

HTREEITEM hCur = hChild;

hChild = tree.GetNextSiblingItem(hChild); // 下一个子结点

FreeNode(hCur);

tree.DeleteItem(hCur);

}

// 重新生成子结点.

AttachFolders(hNode);

// 恢复展开状态(如果以前是展开的)

if(bExpanded) tree.Expand(hNode, TVE_EXPAND);

return 0;

}

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

// protected member function

// 获取系统图标列表

void CShellFolderView::RetrieveSysImageList()

{

PATHINFO DesktopInfo;

SHFILEINFO FileInfo;

CString strDesktop;

GetSpecialFolder(CSIDL_DESKTOPDIRECTORY, &DesktopInfo);

// SHGetFileInfo函数的返回值就是系统图标列表的句柄(可参考MSDN)

HIMAGELIST hSysImageList = (HIMAGELIST)::SHGetFileInfo(

(LPCTSTR)DesktopInfo.pidl, 0, &FileInfo, sizeof(FileInfo),

SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON);

// 设置重叠图标

// 设置1号重叠图标对应图标列表中索引为0的图标,这是一个手托,象征被共享的文件夹

// 设置2号重叠图标对应图标列表中索引为1的图标,这是一个箭头,象征快捷方式

// 设置3号重叠图标对应图标列表中索引为2的图标,这个图标我研究了好长时间也没看出来是个什么东东

ImageList_SetOverlayImage(hSysImageList, 0,1);

ImageList_SetOverlayImage(hSysImageList, 1,2);

ImageList_SetOverlayImage(hSysImageList, 2,3);

// 树控件就使用这个图标列表

GetTreeCtrl().SetImageList(CImageList::FromHandle(hSysImageList),TVSIL_NORMAL);

}

// 获取特殊的文件夹信息

void CShellFolderView::GetSpecialFolder(int nFolder, LPPATHINFO lppi)

{

LPITEMIDLIST lpidl = NULL;

// 取得该文件夹的PIDL

if(SUCCEEDED(SHGetSpecialFolderLocation(m_hWnd, nFolder, &lpidl)))

{

lppi->pidl = lpidl;

// Get the actual path of the directory from the PItemIDList

SHGetPathFromIDList(lpidl, lppi->cPath); // Do it

// Get the Normal Image index

SHFILEINFO shfi;

SHGetFileInfo((LPCTSTR)lpidl, SFGAO_SHARE, &shfi, sizeof(shfi),

SHGFI_PIDL | SHGFI_SYSICONINDEX);

lppi->uIconIndex = shfi.iIcon;

// Get the selected image index

SHGetFileInfo((LPCTSTR)lpidl, SFGAO_SHARE, &shfi, sizeof(shfi),

SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_OPENICON);

lppi->uSelectedIndex = shfi.iIcon;

}

}

void CShellFolderView::GetNetHood(LPPATHINFO lpPI)

{

FILEPATHLIST fpl;

GetSpecialFolder(CSIDL_NETWORK, lpPI);

GetLongFilePath(NULL, lpPI->pidl, &fpl);

strcpy(lpPI->cText, fpl.cNormal);

}

// CreateFolderNode creates a Folder_Node and inserts it under

// the "parent" node (if any), using the last of the path

// string as the Name, and setting the new node's text

// property to match.

HTREEITEM CShellFolderView::CreateFolderNode(

LPITEMIDLIST lpidl, // 文件夹的PIDL

HTREEITEM hParent, // 父结点

BOOL bVirtual) // 是否包含虚拟文件夹

{

CTreeCtrl& tree = GetTreeCtrl();

LPFOLDERNODE lpfnParent = NULL;

FILEPATHLIST fpl;

// 父结点的信息

if(hParent != NULL)

{

lpfnParent = (LPFOLDERNODE)tree.GetItemData(hParent);

GetLongFilePath(lpfnParent->pShellFolder, lpidl, &fpl);

}

else

{

GetLongFilePath(NULL, lpidl, &fpl);

}

CString strFileName(fpl.cInFolder); // 相对名字

CString strPath(fpl.cForParsing); // 法定名字

// 以"::"开头的,是一些特殊的文件夹,普通的目录或文件的名称中是不能包含":"字符的

// 打印出来看看都是些什么东东

if(strncmp(fpl.cForParsing, "::", 2) ==0)

{

TRACE3("<%s> = \"%s\"; // Normal = %s\n", fpl.cInFolder, fpl.cForParsing, fpl.cNormal);

}

// 取得属性

ULONG Attributes = SFGAO_SHARE | SFGAO_FILESYSTEM |

SFGAO_LINK | SFGAO_HASSUBFOLDER;

if(lpfnParent == NULL ||

lpfnParent->pShellFolder->GetAttributesOf(

1, (LPCITEMIDLIST*)&lpidl, &Attributes) != NOERROR)

{

Attributes = 0;

//lpfnParent = NULL;

}

// 是否是一个虚拟文件夹

BOOL bVirFolder = ((Attributes & SFGAO_FILESYSTEM) == 0);

if(lpfnParent != NULL)

{

switch(lpfnParent->fType)

{

case ftNetworkNeighborhood:

case ftNetNode:

bVirFolder = TRUE;

}

}

if(!bVirtual && bVirFolder)

return NULL;

// 生成该文件夹的绝对PIDL,方法就是将相对PIDL附加到父PIDL后

WORD wParentPIDLLen = 0;

LPITEMIDLIST lpidlParent = NULL;

if(lpfnParent != NULL)

{

wParentPIDLLen = lpfnParent->widlen;

lpidlParent = lpfnParent->pidl;

}

WORD pidlen = lpidl->mkid.cb;

// 使用IMalloc接口分配新PIDL需要的空间.因为从系统API返回的PIDL

// 都是通过IMalloc接口分配的空间,所以为了统一,这里也使用IMalloc来分配空间

LPITEMIDLIST lpidlNew = (LPITEMIDLIST)m_pMalloc->Alloc(pidlen + wParentPIDLLen + 2);

if(wParentPIDLLen != 0)

{

memcpy(lpidlNew, lpidlParent, wParentPIDLLen);

memcpy((char*)lpidlNew+wParentPIDLLen, lpidl,pidlen);

}

else

{

memcpy(lpidlNew, lpidl, pidlen);

}

*(WORD*)((char*)lpidlNew + wParentPIDLLen + pidlen) = 0;

LPITEMIDLIST lpidlTemp = /*bVirFolder ? lpidl : */lpidlNew;

// 获取图标索引

SHFILEINFO shfi;

UINT uIcon, uSelectedIcon;

::SHGetFileInfo((LPCTSTR)lpidlTemp, SFGAO_SHARE, &shfi, sizeof(shfi),

SHGFI_PIDL | SHGFI_SYSICONINDEX);

uIcon = uSelectedIcon = shfi.iIcon;

//if((Attributes & SFGAO_FOLDER) == SFGAO_FOLDER)

{

::SHGetFileInfo((LPCTSTR)lpidlTemp, SFGAO_SHARE, &shfi, sizeof(shfi),

SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_OPENICON);

uSelectedIcon = shfi.iIcon;

}

// 一个新的结点

LPFOLDERNODE lpNewNode = new FOLDERNODE;

lpNewNode->widlen = wParentPIDLLen + pidlen;

lpNewNode->pidl = lpidlNew;

strcpy(lpNewNode->cPath, (LPCTSTR)strPath);

lpNewNode->bHasParent = (hParent != NULL);

//lpNewNode->pChangeThread = NULL;

//lpNewNode->VolSer = 1;

// 结点的类型(只要和几个固定的名字比较一下啦)

if(strPath.Compare(c_cMyComputer) == 0)

lpNewNode->fType = ftMyComputer;

else if(strPath.Compare(c_cNetworkNeighborhood) == 0)

lpNewNode->fType = ftNetworkNeighborhood;

else if(strPath.Compare(c_cRecycleBin) == 0)

lpNewNode->fType = ftRecycleBin;

else

lpNewNode->fType = ftFolder;

lpNewNode->dwAttributes = Attributes;

lpNewNode->pMonitorThread = NULL;

// 获取该文件夹对应的IShellFolder接口

// If this Node has no parent, it mut be the Desktop.

if(lpfnParent != NULL)

{

lpfnParent->pShellFolder->BindToObject(lpidl, NULL, IID_IShellFolder,

(void**)&lpNewNode->pShellFolder);

//if(lpfnParent->fType == ftNetworkNeighborhood)

//{

// lpNewNode->fType = ftNetNode;

// sprintf(lpNewNode->cPath, "\\\\%s", (LPCTSTR)strFileName);

//}

}

else

{

// 桌面的IShellFolder

::SHGetDesktopFolder(&lpNewNode->pShellFolder);

}

// 插入树结点

TVINSERTSTRUCT tvis;

tvis.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE |

TVIF_STATE | TVIF_PARAM;

// 如果该文件夹包含子文件夹,则对应的树结点也应该有子结点

if(Attributes & SFGAO_HASSUBFOLDER)

{

tvis.item.mask |= TVIF_CHILDREN;

// 使用 I_CHILDRENCALLBACK 值告诉控件,该结点有子结点,但具体的结点还没给出

// 当该结点被展开时就会通知父窗口.这时你应该为该结点添加子结点

tvis.item.cChildren = I_CHILDRENCALLBACK;

}

// 设置覆盖图标

if(Attributes & SFGAO_SHARE) // 共享的

tvis.item.state = INDEXTOOVERLAYMASK(1);

else if(Attributes & SFGAO_LINK) // 快捷方式

tvis.item.state = INDEXTOOVERLAYMASK(2);

else // 其它的

tvis.item.state = INDEXTOOVERLAYMASK(0);

tvis.item.stateMask = TVIS_OVERLAYMASK; // 指明状太标志包含覆盖图标

tvis.item.iImage = uIcon; // 正常图标

tvis.item.iSelectedImage = uSelectedIcon;// 选中时的图标

//CString strTemp = strFileName + "(" + strPath + ")"; // 看看它的完整路径

tvis.item.pszText = (char*)fpl.cNormal; // 标题

tvis.hParent = hParent; // 父结点

tvis.hInsertAfter = TVI_LAST; // 插入到其它兄弟结点的后面

tvis.item.lParam = (DWORD)lpNewNode; // 自定义的结点数据

HTREEITEM hIns = tree.InsertItem(&tvis); // 插入该结点

if(hIns == NULL) // 插入失败??不大可能的事

FreeNodeData(lpNewNode);

return hIns; // 返回新结点句柄

}

// GetPath returns the path name for true folders, and a null string

// for virtual folders.

CString CShellFolderView::GetPath(HTREEITEM hNode)

{

CString strPath("");

if(hNode == NULL) return strPath;

// 对于虚拟文件夹返回空串

switch(((LPFOLDERNODE)(GetTreeCtrl().GetItemData(hNode)))->fType)

{

case ftNetworkNeighborhood:

case ftNetNode:

case ftRecycleBin:

case ftMyComputer:

return strPath;

}

// 对于真正的文件夹返回绝对路径

strPath = ((LPFOLDERNODE)(GetTreeCtrl().GetItemData(hNode)))->cPath;

if(strPath.GetLength() > 0) // If path is null, it's a virtual folder

{

if(strPath.GetLength() < 3) // If the result is less than 3 bytes,

strPath += "\\"; // It's the root -- add a "\".

}

return strPath;

}

// 对目录进行排序的回调函数

// The lParam1 and lParam2 parameters correspond to the lParam member

// of the TVITEM structure for the two items being compared.

// The lParamSort parameter corresponds to the lParam member of this structure.

static int CALLBACK ShortItemProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)

{

CTreeCtrl* pCtrl = (CTreeCtrl*) lParamSort;

LPFOLDERNODE lpfn1 = (LPFOLDERNODE)lParam1;

LPFOLDERNODE lpfn2 = (LPFOLDERNODE)lParam2;

if(lpfn1 == NULL || lpfn2 == NULL) return 0;

// 虚拟文件夹排在前面,并且不改变原顺序

if((lpfn1->dwAttributes & SFGAO_FILESYSTEM) == 0 &&

(lpfn2->dwAttributes & SFGAO_FILESYSTEM) == 0)

return 0;

else if((lpfn1->dwAttributes & SFGAO_FILESYSTEM) == 0)

return -1;

else if((lpfn2->dwAttributes & SFGAO_FILESYSTEM)==0)

return 1;

// 真正的文件夹排在虚拟文件夹之后,且按字母顺序(不区分大小写)

else if((lpfn1->dwAttributes & SFGAO_FOLDER) &&

(lpfn1->dwAttributes & SFGAO_FOLDER))

return stricmp(lpfn1->cPath, lpfn2->cPath);

else if(lpfn1->dwAttributes & SFGAO_FOLDER)

return -1;

else if(lpfn2->dwAttributes & SFGAO_FOLDER)

return 1;

else

// 其它的排在最后面,按字母序(不区分大小写)

return stricmp(lpfn1->cPath, lpfn2->cPath);

}

// AttachFolders unconditionally attaches child folders to the

// node passed in Node. If there are any, it also starts a thread

// to watch for changes in the tree structure.

BOOL CShellFolderView::AttachFolders(HTREEITEM hNode)

{

CWaitCursor cur; // 显示等待光标

BOOL bRet = FALSE;

BOOL bChildren = TRUE;

CTreeCtrl& tree = GetTreeCtrl();

tree.SetRedraw(FALSE); // 禁止控件更新窗口,以免插入时闪烁

//tree.EnableWindow(FALSE);

CString strPath = GetPath(hNode); // The full path for the node.

LPFOLDERNODE lpfn = (LPFOLDERNODE)tree.GetItemData(hNode);

// If the node being expanded is a root, get the volume serial number

// and lop off the backslash.

if(strPath.GetLength() <= 3)

{

lpfn->Vol_Ser = GetVol_Ser(strPath);

}

// Enumerate the folder's contents.

IEnumIDList* pEnum = NULL;

if(lpfn->pShellFolder->EnumObjects(m_hWnd, SHCONTF_FOLDERS | SHCONTF_INCLUDEHIDDEN,

&pEnum) == NOERROR)

{

bChildren = FALSE;

pEnum->Reset(); // Reset the enumerate interface

ULONG u = 1; // Enumerate context.

LPITEMIDLIST lpidlChild = NULL;

while(pEnum->Next(1, &lpidlChild, &u) == NOERROR)

{

// 为每个PIDL创建对应的树结点(包含其中的虚拟文件夹)

HTREEITEM hChild = CreateFolderNode(lpidlChild, hNode, TRUE);

// 释放PIDL

m_pMalloc->Free(lpidlChild);

if(hChild != NULL)

{

// 调整结点的类型

bChildren = TRUE;

LPFOLDERNODE lpfnChild = (LPFOLDERNODE)tree.GetItemData(hChild);

switch(lpfnChild->fType)

{

case ftMyComputer:

case ftNetNode:

lpfnChild->Vol_Ser = GetVol_Ser(lpfnChild->cPath);

break;

default:

lpfnChild->Vol_Ser = lpfn->Vol_Ser;

bRet = TRUE;

break;

}

}

}

// 释放枚举接口

pEnum->Release();

}

// 调整父结点的属性

TVITEM tvi;

tvi.mask = TVIF_CHILDREN;

tvi.cChildren = bChildren?1:0;

tvi.hItem = hNode;

tree.SetItem(&tvi);

// Alphabetize the nodes, unless the parent is MyComputer or

// Network Neighborhood.

switch(lpfn->fType)

{

case ftMyComputer:

case ftNetworkNeighborhood:

break;

default:{ // 排序所有子结点(根据给定的回调函数)

TVSORTCB tvs;

tvs.hParent = hNode;

tvs.lpfnCompare = ShortItemProc;

tvs.lParam = (LPARAM)&tree;

tree.SortChildrenCB(&tvs);

}break;

}

if(bRet)

{

// 如果还没有的话,则启动一个线程,开始监视该文件夹的变化

if(lpfn->pMonitorThread == NULL)

{

lpfn->pMonitorThread = new CFolderChangeMonitor(strPath, m_hWnd, (WPARAM)hNode);

}

}

// 可以更新窗口了

tree.SetRedraw(TRUE);

// tree.EnableWindow(TRUE);

return bRet;

}

// This routine is used only for diskettes. It returns the volume serial

// number of a disk in the drive--if there is no disk there, it returns

// -1. It's used whenever the user clicks on a node, to determine whether

// he has swapped diskettes, necessitating a discard of all nodes for the

// affected drive.

// For volumes other than diskettes, it returns a dummy value of 1,

// as -1 is used to signify a diskette drive that isn't loaded.

LONG CShellFolderView::GetVol_Ser(LPCTSTR strDriver)

{

LONG result = -1;

// If there is no path it's My Computer -- exit now

if(strlen(strDriver) == 0)

return result;

// If the driver isn't removeable, we dont have to do this

CString strPath = strDriver[0] + ":\\";

strPath.MakeLower();

if(::GetDriveType(strPath) != DRIVE_REMOVABLE)

{

result = 1;

return result;

}

// I f there's no disk in the drive, bail out with a result = -1.

DWORD dwVSN, dwMCL, dwFSF;

if(GetVolumeInformation(strPath, NULL, 0, &dwVSN, &dwMCL, &dwFSF, NULL, 0))

{

result = (LONG) dwVSN;

}

else

result = -1;

return result;

}

// ShowDesktop is performed when Create posts a WM_SHOWDESKTOP

// message, so we see the correct value of fFolderOptions.

void CShellFolderView::ShowDesktop()

{

CTreeCtrl& tree = GetTreeCtrl();

PATHINFO PathInfo;

GetSpecialFolder(CSIDL_DESKTOP, &PathInfo);

//strDesktopPath = PathInfo.cPath;

// 创建桌面这个根结点

HTREEITEM hDesktop = CreateFolderNode(PathInfo.pidl, NULL, TRUE);

LPFOLDERNODE lpfn = (LPFOLDERNODE)tree.GetItemData(hDesktop);

FILEPATHLIST fpl;

GetLongFilePath(lpfn->pShellFolder, lpfn->pidl, &fpl);

tree.SetItemText(hDesktop, fpl.cNormal);

strcpy(lpfn->cPath , PathInfo.cPath);

lpfn->fType = ftDesktop;

// 展开桌面

AttachFolders(hDesktop);

tree.Expand(hDesktop, TVE_EXPAND);

}

// 展开指定的结点,如果需要的话,附加创建其下子结点

int CShellFolderView::Expand(HTREEITEM hNode, UINT nCode)

{

if(GetTreeCtrl().GetChildItem(hNode) == NULL)

{

AttachFolders(hNode);

}

return GetTreeCtrl().Expand(hNode, nCode);

}

// 将路径转化为PIDL,如果失败,返回NULL

LPITEMIDLIST PathToIDList(LPCTSTR lpszPath)

{

OLECHAR szOleStr[MAX_PATH];

IShellFolder* pDesktop = NULL;

LPITEMIDLIST lpidl = NULL;

// 使用桌面的IShellFolder来转换,这将生成一个绝对的PIDL

if(SHGetDesktopFolder(&pDesktop) != NOERROR)

return NULL;

// 将路径转换成宽字符格式

MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED, lpszPath, -1,

szOleStr, sizeof(szOleStr));

// 转换路径

pDesktop->ParseDisplayName(NULL, NULL, szOleStr, NULL, &lpidl, NULL);

// 释放接口

pDesktop->Release();

return lpidl;

}

// PIDL中的下一个结构

inline LPITEMIDLIST PIDL_Next(LPITEMIDLIST p)

{

return (LPITEMIDLIST)((DWORD)p + p->mkid.cb);

}

// 求指定的PIDL的长度,不包括结尾的两个字节的0

inline int PIDL_Len(LPITEMIDLIST p)

{

WORD wLen = 0;

// 累加所有的单元结构尺寸

while(p && p->mkid.cb != 0)

{

wLen += p->mkid.cb;

p = PIDL_Next(p);

}

return wLen;

}

// 设置当前目录

BOOL CShellFolderView::SetCurPath(LPCTSTR lpszPath)

{

// 解释路径为PIDL

LPITEMIDLIST lpidl = PathToIDList(lpszPath);

if(lpidl == NULL) return FALSE;

CTreeCtrl& tree = GetTreeCtrl();

// 从桌面结点开始搜索

HTREEITEM hDesktop = tree.GetRootItem();

if(hDesktop == NULL){

m_pMalloc->Free(lpidl);

return FALSE;

}

HTREEITEM hCur = hDesktop;

WORD wPart = 0;

LPITEMIDLIST lpidlPart = lpidl;

// 对于每一个存在的结点,搜索其下的子结点

while(hCur != NULL && lpidlPart->mkid.cb != 0)

{

// 展开该结点

Expand(hCur,TVE_EXPAND);

wPart += lpidlPart->mkid.cb;

HTREEITEM hChild = tree.GetChildItem(hCur);

// 如果子结点存在,则:

while (hChild != NULL)

{

// 取得结点数据

LPFOLDERNODE lpfn = (LPFOLDERNODE)tree.GetItemData(hChild);

// 如果PIDL的长度匹配的话,则比较这两个PIDL

// PIDL的匹配长度按搜索深度层次而增加,即,第一次搜索匹配一个结构的长度,第二次

// 则匹配两个结构的长...

if(lpfn && PIDL_Len(lpfn->pidl) == wPart)

{

if(memcmp(lpidl, lpfn->pidl, wPart) == 0)

{

// 匹配成功

break;

}

}

hChild = tree.GetNextSiblingItem(hChild); // 下一个结点

}

hCur = hChild;

lpidlPart = PIDL_Next(lpidlPart); // PIDL的下一个结构

}

// 如果子结点存在,则匹配成功. 选中该结点

if(hCur != NULL)

{

tree.SelectItem(hCur);

tree.EnsureVisible(hCur);

}

m_pMalloc->Free(lpidl);

return hCur != NULL;

}

// 释放结点数据

void CShellFolderView::FreeNodeData(LPFOLDERNODE lpfn)

{

if(lpfn)

{

m_pMalloc->Free(lpfn->pidl); // 释放PIDL

if(lpfn->pShellFolder) // 释放IShellFolder接口

lpfn->pShellFolder->Release();

if(lpfn->pMonitorThread != NULL) // 删除监视线程

delete lpfn->pMonitorThread;

delete lpfn; // 删除该结点

}

}

void CShellFolderView::FreeNode(HTREEITEM hNode)

{

CTreeCtrl& tree = GetTreeCtrl();

if(hNode == NULL)

{

// 空的参数指明,将删除所有结点的数据

hNode = tree.GetRootItem();

}

else

{

// 删除本结点数据

LPFOLDERNODE lpfn = (LPFOLDERNODE)tree.GetItemData(hNode);

FreeNodeData(lpfn);

// 结点指向子结点

hNode = tree.GetChildItem(hNode);

}

// 删除所有子结点的数据

while(hNode != NULL)

{

FreeNode(hNode);

hNode = tree.GetNextSiblingItem(hNode);

}

}

// 返回当前选中的路径(没有则返回空串)

CString CShellFolderView::GetSelectedDir()

{

HTREEITEM hSel = GetTreeCtrl().GetSelectedItem();

if(hSel == NULL) return "";

return ((LPFOLDERNODE)GetTreeCtrl().GetItemData(hSel))->cPath;

}

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有