有几种不同的方法可以做到这一点。一种方法是创建一个 ImageList 并将其链接到您的 ListView,然后检索每个单独文件的图标,将其添加到您的 ImageList,并设置 ListViewItem 以在您的 ImageList 中的适当索引处显示图标。
另一种方法是利用由 shell 维护的系统映像列表。这将避免需要自己维护图标的重复副本。想象一下,如果您的 ListView 中有一堆文件夹。它们都将具有相同的图标,但无需特别注意,您将保存与显示使用它的项目一样多的该文件夹图标的副本。 shell 实现会为您处理所有这些重复跟踪。系统图像列表仅包含所需的图标(您明确请求的图标),然后每个图标仅包含一个副本。我认为这是一个更干净,更优雅的设计,所以让我们实现它。我们需要一堆 P/Invoke 代码。
internal static class NativeMethods
{
public const uint LVM_FIRST = 0x1000;
public const uint LVM_GETIMAGELIST = (LVM_FIRST + 2);
public const uint LVM_SETIMAGELIST = (LVM_FIRST + 3);
public const uint LVSIL_NORMAL = 0;
public const uint LVSIL_SMALL = 1;
public const uint LVSIL_STATE = 2;
public const uint LVSIL_GROUPHEADER = 3;
[DllImport("user32")]
public static extern IntPtr SendMessage(IntPtr hWnd,
uint msg,
uint wParam,
IntPtr lParam);
[DllImport("comctl32")]
public static extern bool ImageList_Destroy(IntPtr hImageList);
public const uint SHGFI_DISPLAYNAME = 0x200;
public const uint SHGFI_ICON = 0x100;
public const uint SHGFI_LARGEICON = 0x0;
public const uint SHGFI_SMALLICON = 0x1;
public const uint SHGFI_SYSICONINDEX = 0x4000;
[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260 /* MAX_PATH */)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
[DllImport("shell32")]
public static extern IntPtr SHGetFileInfo(string pszPath,
uint dwFileAttributes,
ref SHFILEINFO psfi,
uint cbSizeFileInfo,
uint uFlags);
[DllImport("uxtheme", CharSet = CharSet.Unicode)]
public static extern int SetWindowTheme(IntPtr hWnd,
string pszSubAppName,
string pszSubIdList);
}
现在,让我们使用它。出于演示目的,我在表单中添加了一个 ListView 控件,名为listView1,将其设置为以“详细信息”模式显示,并将以下代码转储到表单的 Load 事件处理程序中:
private void Form1_Load(object sender, EventArgs e)
{
// Obtain a handle to the system image list.
NativeMethods.SHFILEINFO shfi = new NativeMethods.SHFILEINFO();
IntPtr hSysImgList = NativeMethods.SHGetFileInfo("",
0,
ref shfi,
(uint)Marshal.SizeOf(shfi),
NativeMethods.SHGFI_SYSICONINDEX
| NativeMethods.SHGFI_SMALLICON);
Debug.Assert(hSysImgList != IntPtr.Zero); // cross our fingers and hope to succeed!
// Set the ListView control to use that image list.
IntPtr hOldImgList = NativeMethods.SendMessage(listView1.Handle,
NativeMethods.LVM_SETIMAGELIST,
NativeMethods.LVSIL_SMALL,
hSysImgList);
// If the ListView control already had an image list, delete the old one.
if (hOldImgList != IntPtr.Zero)
{
NativeMethods.ImageList_Destroy(hOldImgList);
}
// Set up the ListView control's basic properties.
// Put it in "Details" mode, create a column so that "Details" mode will work,
// and set its theme so it will look like the one used by Explorer.
listView1.View = View.Details;
listView1.Columns.Add("Name", 500);
NativeMethods.SetWindowTheme(listView1.Handle, "Explorer", null);
// Get the items from the file system, and add each of them to the ListView,
// complete with their corresponding name and icon indices.
string[] s = Directory.GetFileSystemEntries(@"C:\...");
foreach (string file in s)
{
IntPtr himl = NativeMethods.SHGetFileInfo(file,
0,
ref shfi,
(uint)Marshal.SizeOf(shfi),
NativeMethods.SHGFI_DISPLAYNAME
| NativeMethods.SHGFI_SYSICONINDEX
| NativeMethods.SHGFI_SMALLICON);
Debug.Assert(himl == hSysImgList); // should be the same imagelist as the one we set
listView1.Items.Add(shfi.szDisplayName, shfi.iIcon);
}
}
请注意,作为一个简单的示例程序,这几乎没有错误检查。我投入了一些 Debug.Assert 健全性检查只是为了衡量。
另外,为了完整起见,我继续输入代码以使 ListView 看起来像 Explorer 使用的那样。这是由SetWindowTheme function 处理的。
shell 还可以做一些其他的事情,但是你会在这里遗漏。例如,外壳覆盖——出现在某些图标上的小徽章,比如那些已经被检查到源代码控制系统中的图标。这留给读者作为练习。 (提示:LVSIL_STATE 标志用于为 ListView 控件设置“状态”图像列表。)当然,他们不是一天写的 Explorer。