首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 取得檔案關聯圖示的兩大方案:Managed API 與 Shell API 深度解析

     編輯:關於C語言

在檔案總管、自訂開啟對話框、拖放介面等場景,顯示檔案原生圖示能大幅提升使用者辨識度。.NET 提供兩條路徑:純 Managed Icon.ExtractAssociatedIcon 簡單易用;P/Invoke Shell32 SHGetFileInfo 功能完整(支援大/小圖、資料夾、僅副檔名、Overlay 圖層)。本文完整實作並比較兩者優劣。

方案一:Icon.ExtractAssociatedIcon(純 Managed,.NET Framework / Core 通用)

using System.Drawing;

public static Icon GetFileIcon(string filePath, bool large = false)
{
    // 僅支援 32x32(large=false)或 16x16,無法指定 48x48、256x256 等尺寸
    Icon icon = Icon.ExtractAssociatedIcon(filePath);
    return large ? new Icon(icon, 32, 32) : new Icon(icon, 16, 16);
}

// 使用範例
Icon small = GetFileIcon(@"C:\Windows\notepad.exe");
Icon large = GetFileIcon(@"C:\Windows\notepad.exe", true);
pictureBox1.Image = large.ToBitmap();

優點:零 P/Invoke、自動處理圖示快取、程式碼極短。
缺點:僅支援系統預設兩種尺寸、需實體檔案存在、無法取得資料夾圖示、無 Overlay(捷徑箭頭、鎖定圖示等)。

方案二:SHGetFileInfo(Shell API,功能完整)

定義結構與 P/Invoke 簽名:

using System;
using System.Drawing;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SHFILEINFO
{
    public IntPtr hIcon;
    public int iIcon;
    public uint dwAttributes;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szDisplayName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] public string szTypeName;
}

public static class Shell32
{
    [DllImport("Shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr SHGetFileInfo(
        string pszPath, uint dwFileAttributes,
        ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);

    [DllImport("User32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool DestroyIcon(IntPtr hIcon);
}

[Flags]
public enum FileInfoFlags : uint
{
    SHGFI_ICON = 0x100,
    SHGFI_DISPLAYNAME = 0x200,
    SHGFI_TYPENAME = 0x400,
    SHGFI_ATTRIBUTES = 0x800,
    SHGFI_ICONLOCATION = 0x1000,
    SHGFI_EXETYPE = 0x2000,
    SHGFI_SYSICONINDEX = 0x4000,
    SHGFI_LINKOVERLAY = 0x8000,
    SHGFI_SELECTED = 0x10000,
    SHGFI_ATTR_SPECIFIED = 0x20000,
    SHGFI_LARGEICON = 0x0,      // 32x32
    SHGFI_SMALLICON = 0x1,      // 16x16
    SHGFI_OPENICON = 0x2,       // 打開狀態資料夾
    SHGFI_SHELLICONSIZE = 0x4,  // 系統圖示大小(通常 32x32)
    SHGFI_PIDL = 0x8,
    SHGFI_USEFILEATTRIBUTES = 0x10, // pszPath 僅為副檔名,不需實體檔案
    SHGFI_ADDOVERLAYS = 0x20,   // 疊加捷徑/共用/鎖定 overlay
    SHGFI_OVERLAYINDEX = 0x40
}

通用取圖示方法

public static Icon GetIcon(string pathOrExtension, bool large, bool useFileAttributes = false)
{
    var shfi = new SHFILEINFO();
    uint flags = FileInfoFlags.SHGFI_ICON | FileInfoFlags.SHGFI_SYSICONINDEX;
    flags |= large ? FileInfoFlags.SHGFI_LARGEICON : FileInfoFlags.SHGFI_SMALLICON;
    if (useFileAttributes) flags |= FileInfoFlags.SHGFI_USEFILEATTRIBUTES;

    IntPtr h = Shell32.SHGetFileInfo(pathOrExtension, 0, ref shfi, (uint)Marshal.SizeOf(shfi), flags);
    if (h == IntPtr.Zero || shfi.hIcon == IntPtr.Zero) return null;

    Icon icon = Icon.FromHandle(shfi.hIcon).Clone() as Icon; // Clone 避免原生控制權問題
    Shell32.DestroyIcon(shfi.hIcon); // 釋放非受控資源
    return icon;
}

使用範例

// 1. 實體檔案大圖示(含 overlay)
Icon exeLarge = GetIcon(@"C:\Windows\notepad.exe", true);

// 2. 僅副檔名取圖示(無需實體檔案)
Icon pdfSmall = GetIcon(".pdf", false, useFileAttributes: true);

// 3. 資料夾圖示(大/小、開啟/關閉)
Icon folderLarge = GetFolderIcon(true);
Icon folderOpen = GetFolderIcon(true, open: true);

public static Icon GetFolderIcon(bool large, bool open = false)
{
    var shfi = new SHFILEINFO();
    uint flags = FileInfoFlags.SHGFI_ICON | FileInfoFlags.SHGFI_USEFILEATTRIBUTES;
    flags |= large ? FileInfoFlags.SHGFI_LARGEICON : FileInfoFlags.SHGFI_SMALLICON;
    if (open) flags |= FileInfoFlags.SHGFI_OPENICON;

    IntPtr h = Shell32.SHGetFileInfo("", 0x10, ref shfi, (uint)Marshal.SizeOf(shfi), flags); // FILE_ATTRIBUTE_DIRECTORY
    if (h == IntPtr.Zero || shfi.hIcon == IntPtr.Zero) return null;
    Icon icon = Icon.FromHandle(shfi.hIcon).Clone() as Icon;
    Shell32.DestroyIcon(shfi.hIcon);
    return icon;
}

兩大方案完整比較

特性Icon.ExtractAssociatedIconSHGetFileInfo (P/Invoke)
程式複雜度極低(1 行)中(需結構、P/Invoke、資源釋放)
支援尺寸16x16, 32x3216x16, 32x32, 48x48 (Vista+), 256x256 (Win7+)
需實體檔案是否(SHGFI_USEFILEATTRIBUTES)
資料夾圖示不支援支援(含開啟狀態)
Overlay 圖層不支援支援(SHGFI_ADDOVERLAYS)
顯示名稱/類型名不支援支援(SHGFI_DISPLAYNAME/TYPENAME)
效能(大量檔案)較慢(每次查詢註冊表)較快(Shell 快取),可配合 SHGFI_SYSICONINDEX 配合 ImageList 批次繪製
跨平台僅 Windows僅 Windows

高效能批次顯示建議:ImageList + ListView

// 取得系統影像清單控制碼(SHGFI_SYSICONINDEX)
var shfi = new SHFILEINFO();
Shell32.SHGetFileInfo("", 0x10, ref shfi, (uint)Marshal.SizeOf(shfi),
    FileInfoFlags.SHGFI_ICON | FileInfoFlags.SHGFI_SYSICONINDEX | FileInfoFlags.SHGFI_SMALLICON);
IntPtr hSmall = shfi.hIcon; // 此時 hIcon 實為系統 ImageList 索引

// 建立 .NET ImageList 並關聯系統控制碼
var imgList = new ImageList();
imgList.Handle = hSmall; // 直接掛載系統 ImageList
listView1.SmallImageList = imgList;

// 新增項目時只需指定 ImageIndex = shfi.iIcon
listView1.Items.Add("Document.pdf", shfi.iIcon);

此法避免重複提取圖示、大幅降低記憶體與 GDI 物件壓力,適合檔案總管類應用。

資源釋放鐵律

  • Icon.FromHandle 取得的 Icon 必須 .Clone() 再 DestroyIcon 原始 hIcon,否則會導致 GDI 物件洩漏。
  • 長期執行的服務/應用,建議實作快取機制(Key: 副檔名/路徑 + 尺寸),避免重複 P/Invoke。

依專案需求選擇:原型驗證、簡易工具用 ExtractAssociatedIcon;正式產品、檔案總管、需 Overlay/資料夾/高解析度圖示者,請投資實作 SHGetFileInfo 封裝庫,長期受益無窮。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved