(本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢~)
Windows Shell 编程,即 Windows 外壳编程。我们所看到的资源管理器以及整个桌面,都是一个 Shell。
关于 Windows 外壳的基本概念,我这里不做详细介绍,不了解的朋友,可以看看 姜伟华 的
Windows外壳名字空间的浏览。
好,现在让我们从基础学起,早日做出一个强大的资源管理器软件。(偶也是初学者,多多指教)
1 - 基础,浏览一个文件夹
我们知道,在win32中是以外壳名字空间的形式来组织文件系统的,在外壳名字空间里的每一个对象(注)都实现了一个IShellFolder的接口,通过这个接口我们可以直接查询或间接得到其他相关的接口。
(注:这里的对象指的是外壳名字空间中的一个节点,对象有可能是一个文件夹,有可能是一个文件,也有可能是一个虚拟文件夹,例如:我的电脑,网上邻居,控制面板等)
在C#中,我们这样定义 IShellFolder 接口:
IShellFolder.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
![None.gif](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
namespace WinShell
![ExpandedBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![ContractedBlock.gif](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214E6-0000-0000-C000-000000000046")]
public interface IShellFolder
![ExpandedSubBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
}
}
当然,这个接口还没有列出细节函数。我们要做的仅仅是从最基础开始。
首先我们必须了解,在外壳编程中,要使用 PIDL 路径代替普通路径(如果对 PIDL 不熟悉,请看
Windows外壳名字空间的浏览)。
“桌面”是最顶级的文件夹,外壳名字空间中其他各项都可以用从“桌面”开始的 PIDL
加以表示。
如何获取“桌面”的 PIDL 和其 IShellFolder 接口呢,可以通过 API SHGetDesktopFolder:
[DllImport(
"
shell32.dll
"
)]
public
static
extern
Int32 SHGetDesktopFolder(
out
IntPtr ppshf);
![None.gif](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![ExpandedBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
/**/
/// <summary>
/// 获得桌面 Shell
/// </summary>
public
static
IShellFolder GetDesktopFolder(
out
IntPtr ppshf)
![ExpandedBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
SHGetDesktopFolder(out ppshf);
Object obj = Marshal.GetObjectForIUnknown(ppshf);
return (IShellFolder)obj;
}
//
获得桌面 PIDL
IntPtr desktopPtr;
IShellFolder desktop
=
API.GetDesktopFolder(
out
desktopPtr);
好的,我们取得“桌面”的 IShellFolder 接口,就已经成功了一半。现在我需要通过“桌面”,来获取“C:\”这个路径的 PIDL 和
IShellFolder 接口,可以通过 IShellFolder 的 ParseDisplayName 和 BindToObject 函数来实现:
void
ParseDisplayName(
IntPtr hwnd,
IntPtr pbc,
[MarshalAs(UnmanagedType.LPWStr)]
string
pszDisplayName,
out
uint
pchEaten,
out
IntPtr ppidl,
ref
uint
pdwAttributes);
![None.gif](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
void
BindToObject(
IntPtr pidl,
IntPtr pbc,
[In()]
ref
Guid riid,
out
IShellFolder ppv);
//
获取 C 盘的 PIDL
string
FolderPath
=
@"
C:\
"
;
IntPtr Pidl
=
IntPtr.Zero;
IShellFolder Root;
uint
i, j
=
0
;
desktop.ParseDisplayName(Handle, IntPtr.Zero, FolderPath,
out
i,
out
Pidl,
ref
j);
desktop.BindToObject(Pidl, IntPtr.Zero,
ref
Guids.IID_IShellFolder,
out
Root);
前提是你应该保证路径存在,因为我没有做任何出错控制。这样我们就获得了一个 Root,它表示C盘。通过这个Root,我们可以用 EnumObjects 来循环获取其子项(子文件和子文件夹):
[PreserveSig]
int
EnumObjects(IntPtr hWnd, SHCONTF flags,
out
IntPtr enumIDList);
//
循环查找 C 盘下面的文件/文件夹的 PIDL
IEnumIDList fileEnum
=
null
;
IEnumIDList folderEnum
=
null
;
IntPtr fileEnumPtr
=
IntPtr.Zero;
IntPtr folderEnumPtr
=
IntPtr.Zero;
IntPtr pidlSub;
int
celtFetched;
![None.gif](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
//
获取子文件夹
if
(Root.EnumObjects(
this
.Handle, SHCONTF.FOLDERS
|
SHCONTF.INCLUDEHIDDEN,
out
fileEnumPtr)
==
API.S_OK)
![ExpandedBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
fileEnum = (IEnumIDList)Marshal.GetObjectForIUnknown(fileEnumPtr);
while (fileEnum.Next(1, out pidlSub, out celtFetched) == 0 && celtFetched == API.S_FALSE)
![ExpandedSubBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
//获取显示名称
string name = API.GetNameByPIDL(pidlSub);
lvFile.Items.Add(name, 1);
}
}
![None.gif](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
//
获取子文件
if
(Root.EnumObjects(
this
.Handle, SHCONTF.NONFOLDERS
|
SHCONTF.INCLUDEHIDDEN,
out
folderEnumPtr)
==
API.S_OK)
![ExpandedBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
folderEnum = (IEnumIDList)Marshal.GetObjectForIUnknown(folderEnumPtr);
while (folderEnum.Next(1, out pidlSub, out celtFetched) == 0 && celtFetched == API.S_FALSE)
![ExpandedSubBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
string name = API.GetNameByPIDL(pidlSub);
lvFile.Items.Add(name, 0);
}
}
事实上,代码到此结束。然而我发现有太多的结构体和枚举没有介绍(以后会有更多),有兴趣的朋友可以自己查 MSDN ,否则就等待我下一节再介绍了。
最后把图和代码贴上,下一节再详细介绍。
源代码:
/Files/lemony/WinShell.rar