用于加载和使用文件关联图标的类






4.84/5 (13投票s)
一篇关于在 C# 中使用 SHGetFileInfo 的文章(包含一个简单的类和演示)。
引言
大家都知道 Windows 通用控件的两个成员 - ListView
/ImageList
或 TreeView
/ImageList
的经典组合。左边部分以多种格式表示数据,右边部分提供图像列表以便看起来更好。在几乎所有情况下,创建和加载所需图像的任务都由您或您的设计者完成。但是,如果您需要实现一个具有 Windows 资源管理器界面的应用程序,该界面应显示与资源管理器相同的磁盘文件夹、文件、快捷方式等的图标呢?有一个函数 System.Driwing.Icon. ExtractAssociatedIcon
,它可以返回与给定文件关联的图标。这是最简单的方法,但不幸的是,没有重载版本可以让您指定要获取的图标的大小 – 它总是返回 32x32 的图标(注意:MSDN 没有解释将返回什么大小,所以我假设它将是系统定义的大图标大小,可以通过 SystemInformation.IconSize
检查)。或者,您可以使用特殊的 Windows API 函数和 .NET PInvoke 将其引入您的代码。为了使其可重用,我决定创建一个类来利用此功能。
关于 SHGetFileInfo 函数
此函数是系统外壳的一部分,从 Windows 95 和 Windows NT 4.0 开始的每个 Microsoft 操作系统中都可用,它可以为您提供有关文件系统对象(如文件、文件夹等)的许多信息。完整的描述可以从 MSDN 获取。但由于我们不需要所有这些功能,让我们专注于为我们的应用程序获取文件图标。
使用代码
如我所说,我创建了一个类,您可以在本文开头处的存档中找到它 – SystemIconsImageList
。此类派生自 Object
,而不是 ImageList
或 Component
。因此,您必须在窗体的代码隐藏中手动声明、创建和初始化它。稍后我将回来解释我为何这样做。让我们仔细看看它。
首先,我们需要执行 SHGetFileInfo
函数的 Platform Invoke。
[DllImport("shell32.dll")]
public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes,
ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
并预定义该函数使用的某些系统常量和结构。
private const uint SHGFI_ICON = 0x100;
private const uint SHGFI_LARGEICON = 0x0;
private const uint SHGFI_SMALLICON = 0x1;
[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
public IntPtr hIcon;
public IntPtr iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
此外,此类包含两个 System.Windows.Forms.ImageList
控件来存储系统图标:一个用于小图标,一个用于大图标。尽管 ImageList
是一个托管控件,但它包含非托管资源 – 图标或图像。这就是为什么 ImageList
实现 IDisposable
接口,这迫使我们也为我们的类实现 IDisposable
。现在,让我们看看如何使用这个类。首先,让我们声明并创建一个实例。
private SystemIconsImageList sysIcons = new SystemIconsImageList();
基本上,此构造函数只是创建 ImageList
对象,并将其 ImageSize
属性初始化为当前的系统值。
_smallImageList.ColorDepth = ColorDepth.Depth32Bit;
_smallImageList.ImageSize = SystemInformation.SmallIconSize;
_largeImageList.ColorDepth = ColorDepth.Depth32Bit;
_largeImageList.ImageSize = SystemInformation.IconSize;
现在,我们可以将表示实际 ImageList
对象的属性附加到我们窗体代码隐藏中对应的 ListView
(或 TreeView
)属性。
listView1.SmallImageList = sysIcons.SmallIconsImageList;
listView1.LargeImageList = sysIcons.LargeIconsImageList;
除此之外,此类还有一个函数,可以加载图标并返回它们的索引,我们需要在 ListViewItem.ImageIndex
中指定这些索引。
public int GetIconIndex(string FileName);
它只有一个输入参数 – FileName
,即文件夹或文件的完整路径,包括其名称,用于获取与之关联的图标。此文件或文件夹应存在;否则,将引发异常。
第一个技巧是不要一次加载所有文件类型的已注册图标,而只在我们需要显示特定图标时才加载。第二个技巧是不要重复加载和存储相同的图标;否则,我们将有很多重复项,例如文件夹图标,从而白白浪费内存。为了实现这一点,我们将使用文件扩展名作为图标索引,并检查我们是否已有此文件类型的图标。
但是,等等!这仅适用于非可执行文件,那么文件夹、具有相同“.exe”扩展名的可执行文件、应由父图标表示的快捷方式以及没有扩展名的文件呢?我们必须以不同的方式处理所有这些情况。例如,对于可执行文件和快捷方式,最好的方法可能是使用它们的名称作为图标索引(为了减少内存使用,我决定只使用名称而不使用完整路径);对于文件夹和没有扩展名的文件,我们将使用 GUID 作为唯一键,因为它们应该由每个图标表示。
FileInfo info = new FileInfo(FileName);
string ext = info.Extension;
if (String.IsNullOrEmpty(ext))
{
if ((info.Attributes & FileAttributes.Directory) != 0)
ext = "5EEB255733234c4dBECF9A128E896A1E";
// for directories
else
ext = "F9EB930C78D2477c80A51945D505E9C4";
// for files without extension
}
else
if (ext.Equals(".exe", StringComparison.InvariantCultureIgnoreCase) ||
ext.Equals(".lnk", StringComparison.InvariantCultureIgnoreCase))
ext = info.Name;
然后,我们只需检查是否已有此文件类型的图标,或者我们必须加载一个新图标并将其添加到内部 ImageList
。
if (_smallImageList.Images.ContainsKey(ext))
{
return _smallImageList.Images.IndexOfKey(ext);
}
else
{
SHGetFileInfo(FileName, 0, ref shinfo,
(uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SMALLICON);
Icon smallIcon;
try
{
smallIcon = Icon.FromHandle(shinfo.hIcon);
}
catch (ArgumentException ex)
{
throw new ArgumentException(String.Format("File \"{0}\" does" +
" not exist!", FileName), ex);
}
_smallImageList.Images.Add(ext, smallIcon);
SHGetFileInfo(FileName, 0, ref shinfo,
(uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_LARGEICON);
Icon largeIcon = Icon.FromHandle(shinfo.hIcon);
_largeImageList.Images.Add(ext, largeIcon);
return _smallImageList.Images.Count - 1;
}
……我们就完成了!
为什么我们没有基于 Component
实现此功能?在这种情况下,我们就可以将它们放入 Visual Studio 工具箱并在设计时设置所需的属性。
首先,Microsoft 将 ImageList
设为 sealed
,因此没有子组件可以派生自它。此外,我想同时加载小图标和大图标,所以我需要两个 ImageList
对象。由于我的组件不能派生自 ImageList
,因此我将无法在设计时为 ListView
/TreeView
分配 SmallImageList
和 LargeImageList
属性,因此无论如何都需要在代码中进行一些小的操作。
最后一点:我更喜欢在代码隐藏中手动创建非可视化组件,而不是在 Visual Studio 工具箱中寻找它们:)
尽情享用!
历史
- 2008 年 5 月 1 日 - 小更新。
- 2008 年 4 月 24 日 - 初始版本。