(本系列文章由檸檬的(lc_mtt)原創,轉載請註明出處,謝謝~)
有關 PIDL
PIDL亦有“絕對路徑”與“相對路徑”的概念。表示“相對路徑”的PIDL(本文簡稱為“相對PIDL”)只有一個ITEMIDLIST結構的元素,用於標識相對於父資料夾的“路徑”;表示“絕對路徑”的PIDL(簡稱為“絕對PIDL”)有若干個ITEMIDLIST結構的元素,第一個元素表示外殼名字空間根資料夾(“案頭”)下的某一子檔案夾A,第二個元素則表示檔案夾A下的某一子檔案夾B,其餘依此類推。這樣絕對PIDL就通過儲存一條從“案頭”下的直接子檔案夾或檔案的絕對PIDL與相對PIDL是相同的,而其他的檔案夾或檔案的相對PIDL就只是其絕對PIDL的最後一部分了。
為什麼要說這些呢?因為有些函數,必須使用絕對PIDL,例標,如果不使用絕對PIDL,某些表徵圖是無法正常獲得的(磁碟機、控制台等)。
但使用 EnumObjects 獲得的,僅僅是相對PIDL,如果通過相對PIDL擷取絕對PIDL呢?我參考了開源項目 C# FileBrowser 中的 PIDL 類
PIDL.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Runtime.InteropServices;
namespace WinShell
{
public class PIDL
{
private IntPtr pidl = IntPtr.Zero;
public PIDL(IntPtr pidl, bool clone)
{
if (clone)
this.pidl = ILClone(pidl);
else
this.pidl = pidl;
}
public PIDL(PIDL pidl, bool clone)
{
if (clone)
this.pidl = ILClone(pidl.Ptr);
else
this.pidl = pidl.Ptr;
}
public IntPtr Ptr
{
get { return pidl; }
}
public void Insert(IntPtr insertPidl)
{
IntPtr newPidl = ILCombine(insertPidl, pidl);
Marshal.FreeCoTaskMem(pidl);
pidl = newPidl;
}
public void Free()
{
if (pidl != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pidl);
pidl = IntPtr.Zero;
}
}
private static int ItemIDSize(IntPtr pidl)
{
if (!pidl.Equals(IntPtr.Zero))
{
byte[] buffer = new byte[2];
Marshal.Copy(pidl, buffer, 0, 2);
return buffer[1] * 256 + buffer[0];
}
else
return 0;
}
private static int ItemIDListSize(IntPtr pidl)
{
if (pidl.Equals(IntPtr.Zero))
return 0;
else
{
int size = ItemIDSize(pidl);
int nextSize = Marshal.ReadByte(pidl, size) + (Marshal.ReadByte(pidl, size + 1) * 256);
while (nextSize > 0)
{
size += nextSize;
nextSize = Marshal.ReadByte(pidl, size) + (Marshal.ReadByte(pidl, size + 1) * 256);
}
return size;
}
}
public static IntPtr ILClone(IntPtr pidl)
{
int size = ItemIDListSize(pidl);
byte[] bytes = new byte[size + 2];
Marshal.Copy(pidl, bytes, 0, size);
IntPtr newPidl = Marshal.AllocCoTaskMem(size + 2);
Marshal.Copy(bytes, 0, newPidl, size + 2);
return newPidl;
}
public static IntPtr ILCombine(IntPtr pidl1, IntPtr pidl2)
{
int size1 = ItemIDListSize(pidl1);
int size2 = ItemIDListSize(pidl2);
IntPtr newPidl = Marshal.AllocCoTaskMem(size1 + size2 + 2);
byte[] bytes = new byte[size1 + size2 + 2];
Marshal.Copy(pidl1, bytes, 0, size1);
Marshal.Copy(pidl2, bytes, size1, size2);
Marshal.Copy(bytes, 0, newPidl, bytes.Length);
return newPidl;
}
}
}
該類實現了 PIDL 的複製和結合功能。現在我們修改 ShellItem 類,使它帶有父節點的 IShellFolder 以及提供擷取絕對 PIDL 的屬性:
private ShellItem m_ParentItem;
public ShellItem ParentItem
{
get { return m_ParentItem; }
set { m_ParentItem = value; }
}
/**//// <summary>
/// 絕對 PIDL
/// </summary>
public PIDL PIDLFull
{
get
{
PIDL pidlFull = new PIDL(PIDL, true);
ShellItem current = ParentItem;
while (current != null)
{
pidlFull.Insert(current.PIDL);
current = current.ParentItem;
}
return pidlFull;
}
}
擷取表徵圖
言歸正傳,既然已經獲得絕對 PIDL,那麼擷取表徵圖就是很簡單的事情了,我們使用的是 SHGetFileInfo 這個API:
[DllImport("shell32", EntryPoint = "SHGetFileInfo", ExactSpelling = false,
CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SHGetFileInfo(
IntPtr ppidl,
FILE_ATTRIBUTE dwFileAttributes,
ref SHFILEINFO sfi,
int cbFileInfo,
SHGFI uFlags);
[DllImport("Shell32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SHGetFileInfo(
string Path,
FILE_ATTRIBUTE fileAttributes,
out SHFILEINFO sfi,
int cbFileInfo, SHGFI flags);
這裡提供了一個重載,你可以選擇是通過 PIDL 還是 路徑 擷取表徵圖(如果是路徑,那麼僅僅能擷取 檔案夾/檔案 的表徵圖)。
/**//// <summary>
/// 擷取小表徵圖索引
/// </summary>
public static int GetSmallIconIndex(string strFilename)
{
SHFILEINFO psfi = new SHFILEINFO();
IntPtr ipIcon = SHGetFileInfo(strFilename, 0, out psfi, Marshal.SizeOf(psfi),
SHGFI.ICON | SHGFI.SMALLICON | SHGFI.SYSICONINDEX);
return psfi.iIcon;
}
public static int GetSmallIconIndex(IntPtr ipIDList)
{
SHFILEINFO psfi = new SHFILEINFO();
IntPtr ipIcon = SHGetFileInfo(ipIDList, 0, ref psfi, Marshal.SizeOf(psfi),
SHGFI.ICON | SHGFI.PIDL | SHGFI.SMALLICON | SHGFI.SYSICONINDEX);
return psfi.iIcon;
}
大家也許會覺得奇怪,GetSmallIconIndex 返回的是 int ,到底要怎麼使用?
其實沒錯,GetSmallIconIndex 僅僅是返回該表徵圖在系統映像列表(System ImageList)的索引(Index)而已。我們只要擷取系統映像列表的指標,再把它關聯到你的 TreeView 或 ListView ,即可通過 Icon Index 來顯示表徵圖了。
IntPtr m_ipSmallSystemImageList;
IntPtr m_ipLargeSystemImageList;
//擷取系統 ImageList
SHFILEINFO shfi = new SHFILEINFO();
m_ipSmallSystemImageList = API.SHGetFileInfo("", 0, out shfi, Marshal.SizeOf(typeof(SHFILEINFO)),
SHGFI.SYSICONINDEX | SHGFI.SMALLICON | SHGFI.USEFILEATTRIBUTES);
m_ipLargeSystemImageList = API.SHGetFileInfo("", 0, out shfi, Marshal.SizeOf(typeof(SHFILEINFO)),
SHGFI.SYSICONINDEX | SHGFI.LARGEICON | SHGFI.USEFILEATTRIBUTES);
//把系統 ImageList 關聯到 TreeView 和 ListView
API.SendMessage(Tree1.Handle, API.TVM_SETIMAGELIST, API.TVSIL_NORMAL, m_ipSmallSystemImageList);
API.SendMessage(lvFile.Handle, API.LVM_SETIMAGELIST, API.LVSIL_NORMAL, m_ipLargeSystemImageList);
OK,我們修改以往的例子,就可以在 Tree 節點上顯示表徵圖了:
ShellItem shellItem=new ShellItem(pidlSub, iSub, sItem);
int imgIndex = API.GetSmallIconIndex(shellItem.PIDLFull.Ptr);
TreeNode nodeSub = new TreeNode(name, imgIndex, imgIndex);
(註:關於文中出現的一些結構體或常量,讀者可以自行查閱 MSDN,精力有限實在不能一一說明。)
我們來看一下效果:
事實上,這個代碼改了很多,也涉及到下一節的部分內容,因此代碼將在下一節中拋出...