(本系列文章由檸檬的(lc_mtt)原創,轉載請註明出處,謝謝~)
讓我們詳細解釋一下 Shell 編程中最基本的一些函數、結構體和枚舉。
SHGetDesktopFolder
擷取案頭的 IShellFolder 介面
[DllImport("shell32.dll")]
public static extern Int32 SHGetDesktopFolder(out IntPtr ppshf);
要使用這個函數,必須先定義一個 IntPtr 指標。然後通過指標,使用 GetObjectForIUnknown 返回通過指向 COM 物件的 IShellFolder 介面的指標執行個體。於是需要編寫以下函數:
public static IShellFolder GetDesktopFolder(out IntPtr ppshf)
{
SHGetDesktopFolder(out ppshf);
Object obj = Marshal.GetObjectForIUnknown(ppshf);
return (IShellFolder)obj;
}
ParseDisplayName
獲得對象的PIDL,即便對象在分類樹中處於目前的目錄下一層或更多層。例如,對於檔案對象來說,它的解析名就是它的路徑,我們用檔案系統對象的完全路徑名來調用案頭的IshellFolder介面的 ParseDisplayName 方法,它會返回這個對象的完全PIDL。定義:
void ParseDisplayName(
IntPtr hwnd,
IntPtr pbc,
[MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName,
out uint pchEaten,
out IntPtr ppidl,
ref uint pdwAttributes);
裡面最重要的參數就是 out IntPtr ppidl 了,它返回 pszDisplayName 指定路徑對應的 PIDL。然而僅僅是 PIDL 並不能讓你做更多的事情。這時候還需要調用 BindToObject 來返回 IShellFolder 介面。
BindToObject
根據 PIDL 建立和初始化 IShellFolder 對象。定義:
void BindToObject(
IntPtr pidl,
IntPtr pbc,
[In()] ref Guid riid,
out IShellFolder ppv);
裡面有一個 [In()] ref Guid riid 參數,表示介面的介面標識符 (IID)。GUID其實就是一個唯一的標識符。世界上的任何兩台電腦都不會產生重複的 GUID 值。GUID 主要用於在擁有多個節點、多台電腦的網路或系統中,分配必須具有唯一性的標識符。我們這裡使用 IID_IShellFolder 表示它擷取的是一個 IShellFolder 介面。
public static Guid IID_IShellFolder = new Guid("{000214E6-0000-0000-C000-000000000046}");
另外介紹 IEnumIDList 介面。IEnumIDList 介面使資源管理員獲得檔案夾包含的全部對象的PIDL,PIDL然後可以用來獲得這些對象的資訊。
因此,我們使用 EnumObjects 函數返回的將是 IEnumIDList 的指標:
int EnumObjects(IntPtr hWnd, SHCONTF flags, out IntPtr enumIDList);
其中 flags 是 SHCONTF 枚舉類型,它決定了枚舉的內容:
SHCONTF
public enum SHCONTF
{
FOLDERS = 0x20,
NONFOLDERS = 0x40,
INCLUDEHIDDEN = 0x80,
INIT_ON_FIRST_NEXT = 0x100,
NETPRINTERSRCH = 0x200,
SHAREABLE = 0x400,
STORAGE = 0x800
}
因此,我們可以通過 flags 的不同來分別列舉子檔案和子目錄。這裡會遇到一個問題,怎麼擷取 PIDL 對象的名稱呢。這裡編寫了2個函數,可以通過 PIDL 或者 IShellFolder 返回對象的名稱(詳細解釋留到下一節):
擷取名稱
/**//// <summary>
/// 擷取顯示名稱
/// </summary>
public static string GetNameByIShell(IShellFolder Root, IntPtr pidlSub)
{
IntPtr strr = Marshal.AllocCoTaskMem(MAX_PATH * 2 + 4);
Marshal.WriteInt32(strr, 0, 0);
StringBuilder buf = new StringBuilder(MAX_PATH);
Root.GetDisplayNameOf(pidlSub, SHGNO.INFOLDER, strr);
API.StrRetToBuf(strr, pidlSub, buf, MAX_PATH);
return buf.ToString();
}
/**//// <summary>
/// 根據 PIDL 擷取顯示名稱
/// </summary>
public static string GetNameByPIDL(IntPtr pidl)
{
SHFILEINFO info = new SHFILEINFO();
API.SHGetFileInfo(pidl, 0, ref info, Marshal.SizeOf(typeof(SHFILEINFO)),
SHGFI.PIDL | SHGFI.DISPLAYNAME | SHGFI.TYPENAME);
return info.szDisplayName;
}
例子二,從“案頭”開始展開
這個例子將使你深入理解之前的內容。它是這樣的一個例子,允許你從“案頭”開始,一直展開到最深層的對象。
例2
public partial class Sample2 : Form
{
private IShellFolder deskTop;
public Sample2()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//獲得案頭 PIDL
IntPtr deskTopPtr;
deskTop = API.GetDesktopFolder(out deskTopPtr);
//添加 案頭 節點
TreeNode tnDesktop = new TreeNode("案頭");
tnDesktop.Tag = deskTop;
tnDesktop.Nodes.Add("");
//把節點添加到樹中
Tree1.Nodes.Add(tnDesktop);
tnDesktop.Expand();
}
private void Tree1_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
判斷節點是否已經展開#region 判斷節點是否已經展開
if (e.Node.Nodes.Count != 1)
{
return;
}
else
{
if (e.Node.FirstNode.Text != "")
{
return;
}
}
e.Node.Nodes.Clear();
#endregion
IShellFolder root = (IShellFolder)e.Node.Tag;
//迴圈尋找子項
IEnumIDList Enum = null;
IntPtr EnumPtr = IntPtr.Zero;
IntPtr pidlSub;
int celtFetched;
if (root.EnumObjects(this.Handle, SHCONTF.FOLDERS, out EnumPtr) == API.S_OK)
{
Enum = (IEnumIDList)Marshal.GetObjectForIUnknown(EnumPtr);
while (Enum.Next(1, out pidlSub, out celtFetched) == 0 && celtFetched == API.S_FALSE)
{
string name = API.GetNameByIShell(root, pidlSub);
IShellFolder iSub;
root.BindToObject(pidlSub, IntPtr.Zero, ref Guids.IID_IShellFolder, out iSub);
TreeNode nodeSub = new TreeNode(name);
nodeSub.Tag = iSub;
nodeSub.Nodes.Add("");
e.Node.Nodes.Add(nodeSub);
}
}
}
private void Sample2_FormClosing(object sender, FormClosingEventArgs e)
{
//釋放資源
Marshal.ReleaseComObject(deskTop);
}
}
照例,附圖片和原始碼:
原始碼:/Files/lemony/WinShell2.rar