在Windows图标缓存、C#或VB.NET中预加载特定文件夹的文件夹图标

在Windows图标缓存、C#或VB.NET中预加载特定文件夹的文件夹图标,c#,.net,vb.net,winapi,icons,C#,.net,Vb.net,Winapi,Icons,我需要提到一个第三方程序,或者更确切地说是程序的源代码,它包含用C#编写的所有必要代码,以便在Windows缩略图缓存中预加载文件的缩略图,正如您在本演示中看到的: 问题是,我需要预加载的是文件夹的图标,但该方法不允许传递文件夹项,它将返回错误代码0x8004B200(): Shell项不支持缩略图提取。例如,.exe或.lnk项 换句话说,我需要做与WinThumbsPreloader程序相同的事情,但要做的是文件夹图标,而不是文件夹/图标缩略图 因此,我的意思是,我有一个文件夹,里面有一个

我需要提到一个第三方程序,或者更确切地说是程序的源代码,它包含用C#编写的所有必要代码,以便在Windows缩略图缓存中预加载文件的缩略图,正如您在本演示中看到的:

问题是,我需要预加载的是文件夹的图标,但该方法不允许传递文件夹项,它将返回错误代码
0x8004B200
():

Shell项不支持缩略图提取。例如,.exe或.lnk项

换句话说,我需要做与WinThumbsPreloader程序相同的事情,但要做的是文件夹图标,而不是文件夹/图标缩略图

因此,我的意思是,我有一个文件夹,里面有一个desktop.ini文件,您可能知道,它可以用来替换存储该desktop.ini文件的文件夹的默认图标/缩略图。desktop.ini文件内容示例:

[.ShellClassInfo]
IconResource=FolderPreview.ico,0
我需要预加载文件夹的原因是为了避免每次浏览文件夹时生成图标缓存

为了消除疑虑,我希望避免这种缓慢的文件夹图标生成:

相反,要提高速度:

我的问题是:在C#或VB.NET中,如何以编程方式在Windows图标缓存中预加载特定文件夹的图标

看来,IThumbnailCache接口不是解决这个问题的方法


更新1

根据@Jimi的评论建议,这是我尝试的方法:

Public Shared Sub PreloadInIconCache(path As String, 
                                     Optional iconSize As Integer = 256)

    Dim iIdIShellItem As New Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")
    Dim shellItem As IShellItem = Nothing
    Dim shellItemIF As IShellItemImageFactory
    Dim iconFlags As IShellItemImageFactoryGetImageFlags = 
                     IShellItemImageFactoryGetImageFlags.IconOnly

    SHCreateItemFromParsingName(path, IntPtr.Zero, iIdIShellItem, shellItem)
    shellItemIF = DirectCast(shellItem, IShellItemImageFactory)
    shellItemIF.GetImage(New NativeSize(iconSize, iconSize), iconFlags, Nothing)

    Marshal.ReleaseComObject(shellItemIF)
    Marshal.ReleaseComObject(shellItem)
    shellItemIF = Nothing
    shellItem = Nothing

End Sub
它缓存图标。如果我为一个包含1000个自定义图标子文件夹的目录调用此方法,那么iconcache_256.db的大小会增加大约250 MB,因此这是图标正在缓存的明显证据,但是我做了一些错误的事,因为那些缓存的图标没有被操作系统使用。我的意思是,如果在我调用该方法,然后手动使用Explorer.exe导航到该目录后,操作系统再次开始提取和缓存所有1000个子文件夹的图标,并创建新的图标引用,因此iconcache_256.db将其文件大小加倍到500 MB左右,这证明iconcache_256.db包含我使用上述方法缓存的图标和操作系统本身缓存的图标,因此调用上述方法生成的图标缓存引用与操作系统本身生成的图标缓存引用在某种程度上不同,这就是我做错的

我做错了什么

更新2

使用WindowsAPICodePackv1.1库(来自Nuget manager),我遇到了与使用
IShellItemImageFactory.GetImage
更新1的代码中描述的问题相同的问题:我可以提取图标,图标缓存在iconcache_256.db文件中(其他*.db文件的con缓存大小也较小),但如果我通过Explorer.exe导航到该目录,则操作系统将开始提取并再次缓存我已缓存的相同文件夹的图标

完整代码示例:

Imports Microsoft.WindowsAPICodePack.Shell
...

Dim directoryPath As String = "C:\Directory"
Dim searchPattern As String = "*"
Dim searchOption As SearchOption = SearchOption.AllDirectories

For Each dir As DirectoryInfo In New DirectoryInfo(directoryPath).EnumerateDirectories(searchPattern, searchOption)

    Console.WriteLine($"Extracting icon for directory: '{dir.FullName}'")

    Using folder As ShellFolder = DirectCast(ShellFolder.FromParsingName(dir.FullName), ShellFolder)
        folder.Thumbnail.FormatOption = ShellThumbnailFormatOption.IconOnly
        folder.Thumbnail.RetrievalOption = ShellThumbnailRetrievalOption.Default

        Using ico As Bitmap = folder.Thumbnail.Bitmap ' Or: folder.Thumbnail.Icon
            ' PictureBox1.Image = ico
            ' PictureBox1.Update()
            ' Thread.Sleep(50)
        End Using

    End Using

Next dir
我不知道为什么操作系统坚持提取新图标并在我已经缓存它们时缓存它们,这会将iconcache_256.db中缓存的图标引用乘以x2(以及其他*.db文件中),因为如果我从目录中迭代1000个子文件夹以提取和缓存其图标,如果在迭代之后通过Explorer.exe导航到该文件夹,则O.S.将再次提取和缓存这些1000个子文件夹图标,并在iconcache_256.db中(以及其他*.db文件中)创建新条目

我不知道如何读取iconcache_256.db文件的数据库格式,因此我不知道结构格式,但如果结构将目录路径作为其字段之一,那么我怀疑我使用的代码方法可能会强制添加与操作系统在图标缓存中添加的不同的目录路径字段当我通过Explorer.exe导航到文件夹以缓存图标时,可能是因为这个原因,图标缓存引用会成倍增加x2…只是我猜测

更新3

我认为真正的问题可能是图标缓存*.db文件中添加的引用可能是每个进程的引用,因此,当我使用Explorer.exe导航到目录时,它会再次开始提取和缓存我在Visual Studio中调试可执行文件时已提取和缓存的图标

因此,我做了一个测试,运行相同的代码从两个不同的进程中提取/缓存文件夹图标,只需将同一个项目构建到两个具有不同名称和不同程序集GUID的可执行文件中。我发现在运行第二个可执行文件时,*.db文件中的图标缓存引用不会成倍增加/重复,因此每个进程的想法是被丢弃


我真的不知道还有什么可以尝试…以及*.db图标缓存文件中的“重复”引用有什么不同。

让我从头开始总结所有内容,因为可能评论框中有这么多文本和注释,问题和悬赏可能会让用户非常困惑:

目标 我的主要目标是从一堆特定文件夹中预加载文件夹图标。图标在“desktop.ini”中指定文件在每个文件夹中,这是一个O.S功能,在该功能上,O.S以图标的形式直观地表示文件夹,它用文件内容预览替换常见的黄色默认文件夹图标,用于“desktop.ini”中指定的您选择的图标这是一个很好的预览功能,例如用于包含视频游戏、音乐专辑或电影的文件夹,您可以在其中使用游戏、电影或音乐
    ''' ----------------------------------------------------------------------------------------------------
    ''' <summary>
    ''' Preloads the icon of a directory into Windows Icon Cache system files (IconCache_xxx.db).
    ''' <para></para>
    ''' The folder must contain a "desktop.ini" file with the icon specified.
    ''' </summary>
    ''' ----------------------------------------------------------------------------------------------------
    ''' <remarks>
    ''' Because the Windows Icon Cache system creates icon's entry hashes 
    ''' with some kind of process architecture differentiation, 
    ''' the user must make sure to call <see cref="PreloadInIconCache"/> function from
    ''' a process that matches the same architecture of the running operating system. 
    ''' <para></para>
    ''' In short, <see cref="PreloadInIconCache"/> function should be called 
    ''' from a x64 process if running under Windows x64, and  
    ''' from a x86 process if running under Windows x86.
    ''' <para></para>
    ''' Otherwise, if for example <see cref="PreloadInIconCache"/> function is called  
    ''' from a x86 process if running under Windows x64, it will happen that 
    ''' when accesing the specified folder via Explorer.exe, the operating system 
    ''' will ignore the icon cached using <see cref="PreloadInIconCache"/> function,
    ''' and it will cache again the folder, generating a new additional entry 
    ''' in the Icon Cache system.
    ''' <para></para>
    ''' Read more about this problem at: <see href="https://stackoverflow.com/a/66773765/1248295"/>
    ''' </remarks>
    ''' ----------------------------------------------------------------------------------------------------
    ''' <example> This is a code example.
    ''' <code language="VB.NET">
    ''' Dim directoryPath As String = "C:\Movies\"
    ''' Dim searchPattern As String = "*"
    ''' Dim searchOption As SearchOption = SearchOption.TopDirectoryOnly
    ''' Dim iconSize As IconSizes = IconSizes._256x256
    ''' 
    ''' For Each dir As DirectoryInfo In New DirectoryInfo(directoryPath).EnumerateDirectories(searchPattern, searchOption)
    '''     Console.WriteLine($"Preloading icon ({CStr(iconSize)}x{CStr(iconSize)}) for directory: '{dir.FullName}'")
    '''     DirectoryUtil.PreloadInIconCache(dir.FullName, iconSize)
    ''' Next file
    ''' </code>
    ''' </example>
    ''' ----------------------------------------------------------------------------------------------------
    ''' <param name="dirPath">
    ''' The directory path.
    ''' </param>
    ''' 
    ''' <param name="iconSize">
    ''' The requested icon size, in pixels. 
    ''' <para></para>
    ''' Default value is 256.
    ''' </param>
    ''' ----------------------------------------------------------------------------------------------------
    <DebuggerStepThrough>
    Public Shared Sub PreloadInIconCache(directory As DirectoryInfo, Optional iconSize As Integer = 256)
        PreloadInIconCache(directory.FullName, iconSize)
    End Sub

    <DebuggerStepThrough>
Public Shared Sub PreloadInIconCache(path As String, Optional iconSize As Integer = 256)
    Using folder As ShellFileSystemFolder = DirectCast(ShellObject.FromParsingName(path), ShellFileSystemFolder)
        folder.Thumbnail.FormatOption = ShellThumbnailFormatOption.IconOnly
        folder.Thumbnail.RetrievalOption = ShellThumbnailRetrievalOption.Default
        folder.Thumbnail.AllowBiggerSize = True
        folder.Thumbnail.CurrentSize = New System.Windows.Size(iconSize, iconSize)

        Using thumbnail As Bitmap = folder.Thumbnail.Bitmap
        End Using
    End Using
End Sub
Dim folders As DirectoryInfo() = New DirectoryInfo("C:\").GetDirectories("*", SearchOption.AllDirectories)
For Each folder As DirectoryInfo In folders
    PreloadFolderIcon(folder, 256)
Next folder