摘要本文利用Windows名空间所提供的IShellFolder接口用Delphi实现了文件夹管理树的生成
关键字文件夹 接口 Delphi
一概述
Windows/视觉感观上区别Windows的一个重要方面就是大量采用了树形视图控件资源管理器左侧的文件夹管理树便是如此它将本地和网络上的文件夹和文件等资源以层次树的方式罗列出来为用户集中管理计算机提供了极大便利同时在外貌上也焕然一新Delphi为我们提供了大量Windows标准控件但遗憾的是在目录浏览方面却只提供了一个Windows样式的DirectoryListBox(Delphi的测试版也是如此)因此在Delphi中实现Windows文件夹管理树对开发更地道的Windows程序有着重大意义
二实现原理
Windows文件夹管理树的实现实质上是对Windows名空间(Namespace)的遍历名空间中每个文件夹都提供了一个IShellFolder接口遍历名空间的方法是
)调用SHGetDesktopFolder函数获得桌面文件夹的IShellFolder接口桌面文件夹是文件夹管理树的根节点
)再调用所获得的IShellFolder接口的EnumObjects成员函数列举出子文件夹
)调用IShellFolder的BindToObject成员函数获得子文件夹的IShellFolder接口 )重复步骤))列举出某文件夹下的所有子文件夹只至所获得的IShellFolder接口为nil为止
下面解释将要用到的几个主要函数它们在ShlObj单元中定义
)function SHGetDesktopFolder(var ppshf: IShellFolder): HResult;
该函数通过ppshf获得桌面文件夹的IShellFolder接口
)function IShellFolderEnumObjects(hwndOwner: HWND; grfFlags: DWORD;out EnumIDList: IEnumIDList): HResult;
该函数获得一个IEnumIDList接口通过调用该接口的Next等函数可以列举出IShellFolder接口所对应的文件夹的内容内容的类型由grfFlags来指定我们需要列举出子文件夹来因此grfFlags的值指定为SHCONTF_FOLDERSHwndOwner是属主窗口的句柄
)function IShellFolderBindToObject(pidl: PItemIDList; pbcReserved: Pointer;const riid: TIID; out ppvOut: Pointer): HResult;
该函数获得某个子文件夹的IShellFolder接口该接口由ppvOut返回pidl是一个指向元素标识符列表的指针Windows/中用元素标识符和元素标识符列表来标识名空间中的对象它们分别类似于文件名和路径需要特别指出的是pidl作为参数传递给Shell API函数时必须是相对于桌面文件夹的绝对路径而传递给IShellFolder接口的成员函数时则应是相对于该接口所对应文件夹的相对路径pbcReserved应指定为nilriid则应指定为IID_IShellFolder
其它函数可以查阅Delphi提供的《Win Programmers Reference》
三程序清单
下面的源代码在Windows中实现并在Windows测试版中测试无误(程序运行结果如图所示)有兴趣的读者可以将其改写成Delphi组件以备常用
unit BrowseTreeView;
interface
uses
Windows Messages SysUtils Classes Graphics Controls Forms Dialogs
ShlObj ComCtrls;
type
PTreeViewItem = ^TTreeViewItem;
TTreeViewItem = record
ParentFolder: IShellFolder; // 接点对应的文件夹的父文件夹的IShellFolder接口
Pidl FullPidl: PItemIDList; // 接点对应的文件夹的相对和绝对项目标识符列表
HasExpanded: Boolean; // 接点是否展开
end;
TForm = class(TForm)
TreeView: TTreeView;
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure TreeViewExpanding(Sender: TObject; Node: TTreeNode;
var AllowExpansion: Boolean);
private
FItemList: TList;
procedure SetTreeViewImageList;
procedure FillTreeView(Folder: IShellFolder; FullPIDL: PItemIDList; ParentNode: TTreeNode);
end;
var
Form: TForm;
implementation
{$R *DFM}
uses
ActiveX ComObj ShellAPI CommCtrl;
// 以下是几个对项目标识符进行操作的函数
procedure DisposePIDL(ID: PItemIDList);
var
Malloc: IMalloc;
begin
if ID = nil then Exit;
OLECheck(SHGetMalloc(Malloc));
MallocFree(ID);
end;
function CopyItemID(ID: PItemIDList): PItemIDList;
var
Malloc: IMalloc;
begin
Result := nil;
OLECheck(SHGetMalloc(Malloc));
if Assigned(ID) then
begin
Result := MallocAlloc(ID^mkidcb + sizeof(ID^mkidcb));
CopyMemory(Result ID ID^mkidcb + sizeof(ID^mkidcb));
end;
end;
function NextPIDL(ID: PItemIDList): PItemIDList;
begin
Result := ID;
Inc(PChar(Result) ID^mkidcb);
end;
function GetPIDLSize(ID: PItemIDList): Integer;
begin
Result := ;
if Assigned(ID) then
begin
Result := sizeof(ID^mkidcb);
while ID^mkidcb <> do
begin
Inc(Result ID^mkidcb);
ID := NextPIDL(ID);
end;
end;
end;
function CreatePIDL(Size: Integer): PItemIDList;
var
Malloc: IMalloc;
HR: HResult;
begin
Result := nil;
HR := SHGetMalloc(Malloc);
if Failed(HR) then Exit;
try
Result := MallocAlloc(Size);
if Assigned(Result) then
FillChar(Result^ Size );
finally
end;
end;
function ConcatPIDLs(ID ID: PItemIDList): PItemIDList;
var
cb cb: Integer;
begin
if Assigned(ID) then
cb := GetPIDLSize(ID) sizeof(ID^mkidcb)
else
cb := ;
cb := GetPIDLSize(ID);
Result := CreatePIDL(cb + cb);
if Assigned(Result) then
begin
if Assigned(ID) then
CopyMemory(Result ID cb);
CopyMemory(PChar(Result) + cb ID cb);
end;
end;
// 将二进制表示的项目标识符列表转换成有可识的项目名
function GetDisplayName(Folder: IShellFolder; PIDL: PItemIDList;
ForParsing: Boolean): String;
var
StrRet: TStrRet;
P: PChar;
Flags: Integer;
begin
Result := ;
if ForParsing then
Flags := SHGDN_FORPARSING
else
Flags := SHGDN_NORMAL;
FolderGetDisplayNameOf(PIDL Flags StrRet);
case StrRetuType of
STRRET_CSTR:
SetString(Result StrRetcStr lStrLen(StrRetcStr));
STRRET_OFFSET:
begin
P := @PIDLmkidabID[StrRetuOffset sizeof(PIDLmkidcb)];
SetString(Result P PIDLmkidcb StrRetuOffset);
end;
STRRET_WSTR:
Result := StrRetpOleStr;
end;
end;
function GetIcon(PIDL: PItemIDList; Open: Boolean): Integer;
const
IconFlag = SHGFI_PIDL or SHGFI_SYSICONINDEX or SHGFI_SMALLICON;
var
FileInfo: TSHFileInfo;
Flags: Integer;
begin
if Open then
Flags := IconFlag or SHGFI_OPENICON
else
Flags := IconFlag;
SHGetFileInfo(PChar(PIDL) FileInfo sizeof(TSHFileInfo) Flags);
Result := FileInfoiIcon;
end;
// 获得每个文件夹在系统中的图标
procedure GetItemIcons(FullPIDL: PItemIDList; TreeNode: TTreeNode);
begin
with TreeNode do
begin
ImageIndex := GetIcon(FullPIDL False);
SelectedIndex := GetIcon(FullPIDL True);
end;
end;
// 获得系统的图标列表
procedure TFormSetTreeViewImageList;
var
ImageList: THandle;
FileInfo: TSHFileInfo;
begin
ImageList := SHGetFileInfo(PChar(C:\) FileInfo
sizeof(TSHFileInfo) SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
if ImageList <> then
TreeView_SetImageList(TreeViewHandle ImageList );
end;
// 生成文件夹管理树
procedure TFormFillTreeView(Folder: IShellFolder;
FullPIDL: PItemIDList; ParentNode: TTreeNode);
var
TreeViewItem: PTreeViewItem;
EnumIDList: IEnumIDList;
PIDLs FullItemPIDL: PItemIDList;
NumID: LongWord;
ChildNode: TTreeNode;
Attr: Cardinal;
begin
try
OLECheck(FolderEnumObjects(Handle SHCONTF_FOLDERS EnumIDList));
while EnumIDListNext( PIDLs NumID) = S_OK do
begin
FullItemPIDL := ConcatPIDLs(FullPIDL PIDLs);
TreeViewItem := New(PTreeViewItem);
TreeViewItemParentFolder := Folder;
TreeViewItemPidl := CopyItemID(PIDLs);
TreeViewItemFullPidl := FullItemPIDL;
TreeViewItemHasExpanded := False;
FItemListAdd(TreeViewItem);
ChildNode := TreeViewItemsAddChildObject(ParentNode
GetDisplayName(Folder PIDLs False) TreeViewItem);
GetItemIcons(FullItemPIDL ChildNode);
Attr := SFGAO_HASSUBFOLDER or SFGAO_FOLDER;
FolderGetAttributesOf( PIDLs Attr);
if Bool(Attr and (SFGAO_HASSUBFOLDER or SFGAO_FOLDER)) then
if Bool(Attr and SFGAO_FOLDER) then
if Bool(Attr and SFGAO_HASSUBFOLDER) then
ChildNodeHasChildren := True;
end;
except
// 你可在此处对异常进行处理
end;
end;
procedure TFormFormDestroy(Sender: TObject);
var
I: Integer;
begin
try
for I := to FItemListCount do
begin
DisposePIDL(PTreeViewItem(FItemList[i])PIDL);
DisposePIDL(PTreeViewItem(FItemList[i])FullPIDL);
end;
FItemListClear;
FItemListFree;
except
end;
end;
procedure TFormFormCreate(Sender: TObject);
var
Folder: IShellFolder;
begin
SetTreeViewImageList;
OLECheck(SHGetDesktopFolder(Folder));
FItemList := TListCreate;
FillTreeView(Folder nil nil);
end;
procedure TFormTreeViewExpanding(Sender: TObject; Node: TTreeNode;
var AllowExpansion: Boolean);
var
TVItem: PTreeViewItem;
SHFolder: IShellFolder;
begin
TVItem := PTreeViewItem(NodeData);
if TVItemHasExpanded then Exit;
OLECheck(TVItemParentFolderBindToObject(TVItem^Pidl
nil IID_IShellFolder Pointer(SHFolder)));
FillTreeView(SHFolder TVItem^FullPidl Node);
NodeAlphaSort;
TVItem^HasExpanded := True;
end;
end