65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (13投票s)

2008年4月24日

CPOL

5分钟阅读

viewsIcon

83380

downloadIcon

3372

一篇关于在 C# 中使用 SHGetFileInfo 的文章(包含一个简单的类和演示)。

引言

大家都知道 Windows 通用控件的两个成员 - ListView/ImageListTreeView/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,而不是 ImageListComponent。因此,您必须在窗体的代码隐藏中手动声明、创建和初始化它。稍后我将回来解释我为何这样做。让我们仔细看看它。

systemiconsimagelist2.png

首先,我们需要执行 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 分配 SmallImageListLargeImageList 属性,因此无论如何都需要在代码中进行一些小的操作。

最后一点:我更喜欢在代码隐藏中手动创建非可视化组件,而不是在 Visual Studio 工具箱中寻找它们:)

尽情享用!

历史

  • 2008 年 5 月 1 日 - 小更新。
  • 2008 年 4 月 24 日 - 初始版本。
© . All rights reserved.