尝试使用从SHGetFileInfo为文件夹检索的图标时System.Drawing.dll中的“System.ObjectDisposedException”

时间:2016-08-05 23:13:28

标签: c#

我在获取“文件夹”图标时遇到了一些问题,或者保持图标的持久性。我编写的类允许我管理对文件和文件夹的所有图标功能的访问,如下所示:

提供了完整的类,以便您可以比较用于检索图标的不同方法,并在使用返回数据的类似结构的所有其他情况下显示它正在按预期工作。这个问题集中在使用GetFolderIcon的结果的问题,与GetFileIcons相比,这是返回数据的类似结构。您还可以使用ShellFileGetInfo.IconCollections.shell32检索超过300个图标的列表,也可以使用类似的结构。

public class ShellFileGetInfo
{
    [DllImport("shell32.dll")]
    private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);

    [DllImport("user32.dll", EntryPoint = "DestroyIcon", SetLastError = true)]
    private static extern int DestroyIcon(IntPtr hIcon);

    [DllImport("shell32.dll", CharSet = CharSet.Auto)]
    private static extern int ExtractIconEx(string szFileName, int nIconIndex, IntPtr[] phiconLarge, IntPtr[] phiconSmall, int nIcons);

    [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;
    };

    // Enum for return of the file type
    public enum ShellFileType
    {
        FileNotFound,
        Unknown,
        Dos,
        Windows,
        Console
    }

    public class FolderIcons
    {
        public Icon open;
        public Icon closed;
    }

    public class FileIcons
    {
        public List<Icon> small;
        public List<Icon> large;
    }

    public static class IconCollections
    {
        private static FileIcons _explorer;
        public static FileIcons explorer
        {
            get
            {
                if(_explorer == null )
                {
                    _explorer = GetFileIcons(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe"));
                }
                return _explorer;
            }
        }

        private static FileIcons _shell32;
        public static FileIcons shell32
        {
            get
            {
                if (_shell32 == null )
                {
                    _shell32 = GetFileIcons(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "shell32.dll"));
                }
                return _shell32;
            }
        }

    }

    // Apply the appropriate overlays to the file's icon. The SHGFI_ICON flag must also be set.
    public const uint SHGFI_ADDOVERLAYS = 0x000000020;

    // Modify SHGFI_ATTRIBUTES to indicate that the dwAttributes member of the SHFILEINFO structure at psfi contains the specific attributes that are desired. These attributes are passed to IShellFolder::GetAttributesOf. If this flag is not specified, 0xFFFFFFFF is passed to IShellFolder::GetAttributesOf, requesting all attributes. This flag cannot be specified with the SHGFI_ICON flag.
    public const uint SHGFI_ATTR_SPECIFIED = 0x000020000;

    // Retrieve the item attributes. The attributes are copied to the dwAttributes member of the structure specified in the psfi parameter. These are the same attributes that are obtained from IShellFolder::GetAttributesOf.
    public const uint SHGFI_ATTRIBUTES = 0x000000800;

    // Retrieve the display name for the file, which is the name as it appears in Windows Explorer. The name is copied to the szDisplayName member of the structure specified in psfi. The returned display name uses the long file name, if there is one, rather than the 8.3 form of the file name. Note that the display name can be affected by settings such as whether extensions are shown.
    public const uint SHGFI_DISPLAYNAME = 0x000000200;

    // Retrieve the type of the executable file if pszPath identifies an executable file. The information is packed into the return value. This flag cannot be specified with any other flags.
    public const uint SHGFI_EXETYPE = 0x000002000;

    // Retrieve the handle to the icon that represents the file and the index of the icon within the system image list. The handle is copied to the hIcon member of the structure specified by psfi, and the index is copied to the iIcon member.
    public const uint SHGFI_ICON = 0x000000100;

    // Retrieve the name of the file that contains the icon representing the file specified by pszPath, as returned by the IExtractIcon::GetIconLocation method of the file's icon handler. Also retrieve the icon index within that file. The name of the file containing the icon is copied to the szDisplayName member of the structure specified by psfi. The icon's index is copied to that structure's iIcon member.
    public const uint SHGFI_ICONLOCATION = 0x000001000;

    // Modify SHGFI_ICON, causing the function to retrieve the file's large icon. The SHGFI_ICON flag must also be set.
    public const uint SHGFI_LARGEICON = 0x000000000;

    // Modify SHGFI_ICON, causing the function to add the link overlay to the file's icon. The SHGFI_ICON flag must also be set.
    public const uint SHGFI_LINKOVERLAY = 0x000008000;

    // Modify SHGFI_ICON, causing the function to retrieve the file's open icon. Also used to modify SHGFI_SYSICONINDEX, causing the function to return the handle to the system image list that contains the file's small open icon. A container object displays an open icon to indicate that the container is open. The SHGFI_ICON and/or SHGFI_SYSICONINDEX flag must also be set.
    public const uint SHGFI_OPENICON = 0x000000002;

    // Version 5.0. Return the index of the overlay icon. The value of the overlay index is returned in the upper eight bits of the iIcon member of the structure specified by psfi. This flag requires that the SHGFI_ICON be set as well.
    public const uint SHGFI_OVERLAYINDEX = 0x000000040;

    // Indicate that pszPath is the address of an ITEMIDLIST structure rather than a path name.
    public const uint SHGFI_PIDL = 0x000000008;

    // Modify SHGFI_ICON, causing the function to blend the file's icon with the system highlight color. The SHGFI_ICON flag must also be set.
    public const uint SHGFI_SELECTED = 0x000010000;

    // Modify SHGFI_ICON, causing the function to retrieve a Shell-sized icon. If this flag is not specified the function sizes the icon according to the system metric values. The SHGFI_ICON flag must also be set.
    public const uint SHGFI_SHELLICONSIZE = 0x000000004;

    // Modify SHGFI_ICON, causing the function to retrieve the file's small icon. Also used to modify SHGFI_SYSICONINDEX, causing the function to return the handle to the system image list that contains small icon images. The SHGFI_ICON and/or SHGFI_SYSICONINDEX flag must also be set.
    public const uint SHGFI_SMALLICON = 0x000000001;

    // Retrieve the index of a system image list icon. If successful, the index is copied to the iIcon member of psfi. The return value is a handle to the system image list. Only those images whose indices are successfully copied to iIcon are valid. Attempting to access other images in the system image list will result in undefined behavior.
    public const uint SHGFI_SYSICONINDEX = 0x000004000;

    // Retrieve the string that describes the file's type. The string is copied to the szTypeName member of the structure specified in psfi.
    public const uint SHGFI_TYPENAME = 0x000000400;

    // Indicates that the function should not attempt to access the file specified by pszPath. Rather, it should act as if the file specified by pszPath exists with the file attributes passed in dwFileAttributes. This flag cannot be combined with the SHGFI_ATTRIBUTES, SHGFI_EXETYPE, or SHGFI_PIDL flags.
    public const uint SHGFI_USEFILEATTRIBUTES = 0x000000010;

    /// <summary>
    /// Get a list of open and closed icons for the specified folder
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    public static FolderIcons GetFolderIcon(string path)
    {
        FolderIcons fi = new FolderIcons();
        SHFILEINFO shInfo = new SHFILEINFO();
        IntPtr ptr = new IntPtr();

        try
        {
            ptr = SHGetFileInfo(path, 0, ref shInfo, (uint)Marshal.SizeOf(shInfo), SHGFI_ICON | SHGFI_LARGEICON | SHGFI_ADDOVERLAYS);
            if (ptr != IntPtr.Zero)
            {
                fi.closed = Icon.FromHandle(shInfo.hIcon);
            }
        }
        catch (Exception)
        {
            fi.closed = null;
        } finally
        {
            if(shInfo.hIcon != IntPtr.Zero)
            {
                DestroyIcon(shInfo.hIcon);
            }
        }

        try {
            ptr = SHGetFileInfo(path, 0, ref shInfo, (uint)Marshal.SizeOf(shInfo), SHGFI_ICON | SHGFI_LARGEICON | SHGFI_ADDOVERLAYS | SHGFI_OPENICON);
            if (ptr != IntPtr.Zero) {
                fi.open = Icon.FromHandle(shInfo.hIcon);
            }
        }
        catch (Exception)
        {
            fi.closed = null;
        } finally
        {
            if(shInfo.hIcon != IntPtr.Zero)
            {
                DestroyIcon(shInfo.hIcon);
            }
        }

        return fi;
    }

    /// <summary>
    /// Determine the type of executable the file is
    /// </summary>
    /// <param name="file"></param>
    /// <returns></returns>
    public static ShellFileType GetFileType(string file)
    {
        ShellFileType type = ShellFileType.FileNotFound;
        if (File.Exists(file))
        {
            SHFILEINFO shinfo = new SHFILEINFO();
            IntPtr ptr = SHGetFileInfo(file, 128, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_EXETYPE);
            int wparam = ptr.ToInt32();
            int loWord = wparam & 0xffff;
            int hiWord = wparam >> 16;

            type = ShellFileType.Unknown;

            if (wparam != 0)
            {
                if (hiWord == 0x0000 && loWord == 0x5a4d)
                {
                    type = ShellFileType.Dos;
                }
                else if (hiWord == 0x0000 && loWord == 0x4550)
                {
                    type = ShellFileType.Console;
                }
                else if ((hiWord != 0x0000) && (loWord == 0x454E || loWord == 0x4550 || loWord == 0x454C))
                {
                    type = ShellFileType.Windows;
                }
            }
        }
        return type;
    }

    /// <summary>
    /// Get a single icon from a file
    /// </summary>
    /// <param name="resource"></param>
    /// <param name="largeIcon"></param>
    /// <returns></returns>
    public static Icon GetFileIcon(string resource, bool largeIcon = false)
    {
        SHFILEINFO shinfo = new SHFILEINFO();
        Icon icon;
        try
        {
            IntPtr ico;
            if (largeIcon == true)
            {
                ico = SHGetFileInfo(resource, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SMALLICON);
            }
            else
            {
                ico = SHGetFileInfo(resource, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_LARGEICON);
            }
            icon = (Icon)Icon.FromHandle(shinfo.hIcon);
        }
        catch (Exception)
        {
            icon = null;
        }
        finally
        {
            if (shinfo.hIcon != IntPtr.Zero)
            {
                DestroyIcon(shinfo.hIcon);
            }
        }
        return icon;
    }

    /// <summary>
    /// Get all large and small icons from a file
    /// </summary>
    /// <param name="file"></param>
    /// <returns></returns>
    public static FileIcons GetFileIcons(string file)
    {
        FileIcons icons = new FileIcons()
        {
            small = new List<Icon>(),
            large = new List<Icon>()
        };
        IntPtr[] large = new IntPtr[999];
        IntPtr[] small = new IntPtr[999];
        Icon ico;
        try
        {
            int count = ExtractIconEx(file, -1, large, small, 999);
            if (count > 0)
            {
                large = new IntPtr[count - 1];
                small = new IntPtr[count - 1];

                ExtractIconEx(file, 0, large, small, count);
                foreach (var x in large)
                {
                    if (x != IntPtr.Zero)
                    {
                        ico = (Icon)Icon.FromHandle(x).Clone();
                        icons.large.Add(ico);
                    }
                }

                foreach (var x in small)
                {
                    if (x != IntPtr.Zero)
                    {
                        ico = (Icon)Icon.FromHandle(x).Clone();
                        icons.small.Add(ico);
                    }
                }
            }
        }
        catch (Exception e)
        {
            System.Diagnostics.Debug.WriteLine(e.Message);
        }
        finally
        {
            foreach (IntPtr ptr in large)
            {
                if (ptr != IntPtr.Zero)
                {
                    DestroyIcon(ptr);
                }
            }
            foreach (IntPtr ptr in small)
            {
                if (ptr != IntPtr.Zero)
                {
                    DestroyIcon(ptr);
                }
            }
        }
        return icons;
    }
}

当我尝试使用负责获取文件夹的打开和关闭图标为GetFolderIcon的{​​{1}}函数时,它会返回一个值,但是当我尝试将其添加到我的列表视图中时,我我得到以下例外:

  

抛出异常:System.Drawing.dll中的“System.ObjectDisposedException”

我用来使用该函数并将图标添加到listview的图像集的代码如下:

FolderIcons

请务必注意,在使用// Initialize the imagelist object listView1.LargeImageList = new ImageList() { ColorDepth = ColorDepth.Depth32Bit, ImageSize = new Size(32, 32) }; // Create a new listview item ListViewItem lvi = new ListViewItem() { Text = "My Computer", ImageIndex = viewer1.LargeImageList.Images.Count, StateImageIndex = viewer1.LargeImageList.Images.Count }; // Retrieve the open/closed icons into a class 'x' (i tried as a struct with the same result) var x = ShellFileGetInfo.GetFolderIcon( Environment.GetFolderPath( Environment.SpecialFolder.Desktop ) ); // Try adding the icon to the listview's image collection listView1.LargeImageList.Images.Add(x.closed); // Add the listview item to the listview listView1.Items.Add(lvi); GetFileIcon方法后,如果在需要时调用GetFileIcons后以相似的结构返回结果,我就没有这个问题。

我已经查看了其他几篇声称使用SHGetFileInfo检索文件夹图标的Stack Overflow文章,但这些方法似乎都不起作用。我上面的课程是最好的方法的混合,以及MSDN文章的实现。我还为每个描述其目的和用途的常量添加了the MSDN notes内联。

我经历过的一些Stack Overflow链接:

使用的其他一些引用不属于Stack Overflow:

我看过其他几个,但没有任何功能完全正常。我的方法,我希望将系统中的叠加图标添加到图标中,根据MSDN文档描述使用DestroyIcon()常量将自动合并叠加层(如果图标需要任何叠加)并返回合并结果。从本质上讲,我的SHGFI_ADDOVERLAYS方法设计得足够智能,可以处理打开和关闭状态图标以及已根据需要应用的任何叠加层的返回。

1 个答案:

答案 0 :(得分:0)

检索文件夹图标的代码只是检索参考信息,而不是实际获取图标数据。

要解决此问题,需要.Clone()方法制作Icon本身的副本。

而不是:

 Icon foo = Icon.FromHandle(shInfo.hIcon);

使用:

 Icon foo = (Icon)Icon.FromHandle(shInfo.hIcon).Clone();

以下是更正的GetFolderIcon方法

/// <summary>
/// Get a list of open and closed icons for the specified folder
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static FolderIcons GetFolderIcon(string path)
{
    FolderIcons fi = new FolderIcons();
    SHFILEINFO shInfo = new SHFILEINFO();
    IntPtr ptr = new IntPtr();

    try
    {
        ptr = SHGetFileInfo(path, 0, ref shInfo, (uint)Marshal.SizeOf(shInfo), SHGFI_ICON | SHGFI_LARGEICON | SHGFI_ADDOVERLAYS);
        if (ptr != IntPtr.Zero)
        {
            fi.closed = (Icon)Icon.FromHandle(shInfo.hIcon).Clone();
        }
    }
    catch (Exception)
    {
        fi.closed = null;
    } finally
    {
        if(shInfo.hIcon != IntPtr.Zero)
        {
            DestroyIcon(shInfo.hIcon);
        }
    }

    try {
        ptr = SHGetFileInfo(path, 0, ref shInfo, (uint)Marshal.SizeOf(shInfo), SHGFI_ICON | SHGFI_LARGEICON | SHGFI_ADDOVERLAYS | SHGFI_OPENICON);
        if (ptr != IntPtr.Zero) {
            fi.open = (Icon)Icon.FromHandle(shInfo.hIcon).Clone();
        }
    }
    catch (Exception)
    {
        fi.closed = null;
    } finally
    {
        if(shInfo.hIcon != IntPtr.Zero)
        {
            DestroyIcon(shInfo.hIcon);
        }
    }

    return fi;
}