在 Windows 图标缓存中预加载特定文件夹的文件夹图标,在 C# 或 VB.NET 中 [英] Preload folder icon for a specific folder in Windows Icon cache, in C# or VB.NET

查看:52
本文介绍了在 Windows 图标缓存中预加载特定文件夹的文件夹图标,在 C# 或 VB.NET 中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要提到一个 3rd 方程序,或者更好地说

问题是我需要预加载的是文件夹的图标,但是

相反,提高速度:

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

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

<小时><块引用>

更新 1

按照@Jimi 的评论建议,这是我正在尝试的方法:

Public Shared Sub PreloadInIconCache(path As String,可选的 iconSize 作为整数 = 256)Dim iIdIShellItem As New Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")将 shellItem 调暗为 IShellItem = 无将 shellItemIF 调暗为 IShellItemImageFactory将 iconFlags 调暗为 IShellItemImageFactoryGetImageFlags =IShellItemImageFactoryGetImageFlags.IconOnlySHCreateItemFromParsingName(path, IntPtr.Zero, iIdIShellItem, shellItem)shellItemIF = DirectCast(shellItem, IShellItemImageFactory)shellItemIF.GetImage(New NativeSize(iconSize, iconSize), iconFlags, Nothing)Marshal.ReleaseComObject(shellItemIF)Marshal.ReleaseComObject(shellItem)shellItemIF = 无shellItem = 无结束子

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

我做错了什么?.

<块引用>

更新 2

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

完整代码示例:

导入 Microsoft.WindowsAPICodePack.Shell...Dim directoryPath As String = "C:\Directory"Dim searchPattern As String = "*"Dim searchOption As SearchOption = SearchOption.AllDirectories对于每个目录作为 DirectoryInfo In New DirectoryInfo(directoryPath).EnumerateDirectories(searchPattern, searchOption)Console.WriteLine($"提取目录图标:'{dir.FullName}'")使用文件夹作为 ShellFolder = DirectCast(ShellFolder.FromParsingName(dir.FullName), ShellFolder)folder.Thumbnail.FormatOption = ShellThumbnailFormatOption.IconOnlyfolder.Thumbnail.RetrievalOption = ShellThumbnailRetrievalOption.Default使用 ico As Bitmap = folder.Thumbnail.Bitmap ' 或:folder.Thumbnail.Icon' PictureBox1.Image = ico' PictureBox1.Update()' Thread.Sleep(50)结束使用结束使用下一个目录

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

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

<块引用>

更新 3

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

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

我真的不知道我还能尝试什么……*.db 图标缓存文件中的重复"引用有何不同.

解决方案

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

目标

我的主要目标是并且是从一堆特定文件夹中预加载文件夹图标.该图标在desktop.ini"中指定.每个文件夹中的文件,这是一个操作系统功能,在该功能上,操作系统在视觉上将文件夹表示为图标,它实际上用文件内容预览替换了常见的黄色默认文件夹图标,对于您选择的图标,在桌面.ini"文件.这是一个漂亮的预览功能,例如包含视频游戏、音乐专辑或电影的文件夹,您可以在其中使用游戏、电影或音乐封面作为该文件夹的图标.

代码方法

使用一些代码调用 IShellItemImageFactory.GetImage() 函数,或者使用 WindowsAPICodePack 库 我能够让操作系统生成图标并将它们各自的条目添加到 Iconcache_nnn.db 文件中.

问题

我发现的问题是操作系统忽略了这些图标,它不想使用我缓存的图标来预览文件夹图标,而是操作系统创建了具有不同哈希值但具有相同图标的新条目,换句话说,操作系统重新缓存我已经缓存的图标.

问题的根源

通过反复试验,我发现运行我的代码以调用 IShellItemImageFactory.GetImage() 函数的进程架构 (x86/x64) 非常重要.

因此,在使用 64 位 Windows 时,我总是在 x86 模式下运行我的代码,这导致在我没有意识到问题的情况下,我的 x86 进程生成的图标条目具有与操作系统的条目不同的哈希值正在 Iconcache_nnn.db 文件中创建,这就是操作系统再次缓存图标的原因,因为操作系统无法识别来自我的 x86 进程的缓存图标的哈希值.>

我真的不知道为什么会以这种方式发生,但就是这样,所以从@Simon Mourier 中拯救了这条评论:

<块引用>

AFAIK,路径的图标/拇指哈希是使用大小计算的(文件夹不存在)、上次修改日期和文件标识符

对于该计算,我们需要添加另一个因素:流程架构差异化?

解决办法

我的 64 位操作系统中的解决方案只是确保在 x64 中编译进程(以编程方式获取文件夹图标)以匹配操作系统/Explorer.exe 的相同架构.如果使用的是 32 位 Windows,则无需多说,请运行您的 x86 进程.

这是一个使用 WindowsAPICodePack 库预加载文件夹图标的简单代码:

 ''' ----------------------------------------------------------------------------------------------------''' <总结>''' 将目录的图标预加载到 Windows 图标缓存系统文件 (IconCache_xxx.db) 中.''' <para></para>''' 文件夹必须包含一个desktop.ini";带有指定图标的文件.''' </summary>''' ----------------------------------------------------------------------------------------------------''' <备注>''' 因为 Windows 图标缓存系统会创建图标的条目哈希'''具有某种流程架构差异化,''' 用户必须确保调用 <see cref="PreloadInIconCache"/>功能来自''' 与正在运行的操作系统的相同架构相匹配的进程.''' <para></para>''' 简而言之,<see cref="PreloadInIconCache"/>应该调用函数''' 来自 x64 进程(如果在 Windows x64 下运行),以及''' 来自 x86 进程,如果在 Windows x86 下运行.''' <para></para>''' 否则,如果例如<see cref="PreloadInIconCache"/>函数被调用来自 x86 进程的 ''' 如果在 Windows x64 下运行,则会发生''' 当通过 Explorer.exe 访问指定文件夹时,操作系统''' 将忽略使用 <see cref="PreloadInIconCache"/> 缓存的图标.功能,''' 它将再次缓存该文件夹,生成一个新的附加条目''' 在图标缓存系统中.''' <para></para>''' 在以下位置阅读有关此问题的更多信息:<see href="https://stackoverflow.com/a/66773765/1248295"/>''' </remarks>''' ----------------------------------------------------------------------------------------------------''' <示例>这是一个代码示例.''' <代码语言=VB.NET">''' Dim directoryPath As String = "C:\Movies\";''' Dim searchPattern As String = "*";''' Dim searchOption As SearchOption = SearchOption.TopDirectoryOnly''' 将 iconSize 调暗为 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)'''下一个文件'''''' </example>''' ----------------------------------------------------------------------------------------------------''' ''' 目录路径.''' </param>'''''' ''' 请求的图标大小,以像素为单位.''' <para></para>''' 默认值为 256.''' </param>''' ----------------------------------------------------------------------------------------------------<DebuggerStepThrough>Public Shared Sub PreloadInIconCache(directory As DirectoryInfo, Optional iconSize As Integer = 256)PreloadInIconCache(directory.FullName, iconSize)结束子<DebuggerStepThrough>Public Shared Sub PreloadInIconCache(path As String, Optional iconSize As Integer = 256)使用文件夹作为 ShellFileSystemFolder = DirectCast(ShellObject.FromParsingName(path), ShellFileSystemFolder)folder.Thumbnail.FormatOption = ShellThumbnailFormatOption.IconOnlyfolder.Thumbnail.RetrievalOption = ShellThumbnailRetrievalOption.Defaultfolder.Thumbnail.AllowBiggerSize = Truefolder.Thumbnail.CurrentSize = New System.Windows.Size(iconSize, iconSize)使用缩略图作为位图 = folder.Thumbnail.Bitmap结束使用结束使用结束子

示例用法:

Dim folders As DirectoryInfo() = New DirectoryInfo("C:\").GetDirectories("*", SearchOption.AllDirectories)对于每个文件夹作为 DirectoryInfo 在文件夹中PreloadFolderIcon(文件夹,256)下一个文件夹

请记住,如果在 64 位 Windows 下运行,包含该代码的 .NET 程序集必须在 x64 模式下运行,否则该代码将在 Iconcache_nnn.db 中生成图标条目文件,但当通过 Explorer.exe 访问文件夹时,操作系统将简单地忽略这些条目,而是再次重新缓存文件夹图标,生成新的附加条目.

最后,不要忘记您可以使用工具 Iconcache_nnn.db 文件中的图标条目@Max 提到的 rel="nofollow noreferrer">thumbcacheviewer.

I need to mention a 3rd party program, or better said the source-code of WinThumbsPreloader program, which contains all the necessary code written in C# to preload the thumbnail of a file in Windows thumbnails cache as you can see in this demo:

The problem is that what I need to preload is the icon of a folder, but the IThumbnailCache::GetThumbnail method does not allow to pass a folder item, and it will return error code 0x8004B200 (WTS_E_FAILEDEXTRACTION):

The Shell item does not support thumbnail extraction. For example, .exe or .lnk items.

In other words, I need to do the same thing WinThumbsPreloader program does, but for folder icons instead of folder/icon thumbnails.

So, what I mean is that I have a folder with a desktop.ini file inside, which as you probably know it can serve to replace the default icon/thumbnail for the folder that stores that desktop.ini file. An example of desktop.ini file content:

[.ShellClassInfo]
IconResource=FolderPreview.ico,0

The reason why I need to preload folders is to avoid the icon cache generation every time that I navigate through a folder.

To clear doubts, I would like to avoid this slow folder icon generation:

And instead, get this improved speed:

My question is: In C# or VB.NET, how can I programmatically preload the icon of a specific folder in Windows icon cache?.

It seems that IThumbnailCache interface is not the way to go for this problem...


UPDATE 1

Following @Jimi's commentary suggestions, this is the approach I'm trying:

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

It caches the icons. If I call this method for lets say a directory that contains 1000 subfolders with custom icons, then the size of iconcache_256.db increases like around 250 MB, so its a clear evidence that icons are being cached, however I'm doing something wrong because those cached icons are not used by the operating system. I mean, if after I call that method and then I manually use Explorer.exe to navigate to that directory, the operating system starts again extracting and caching the icons for all the 1000 subfolders creating NEW icon references, so iconcache_256.db doubles its file size to around 500 MB, and this is a evidence that iconcache_256.db contains both the icons I cached using the method above, and the icons cached by the operating system itself, so the icon cache references that I generate calling the method above differ in some way from the icon cache references generated by the operating system itself, and that is what I'm doing wrong...

What I'm doing wrong?.

UPDATE 2

Using WindowsAPICodePack v1.1 library (from Nuget manager) I'm having the same problem as described in the code of the UPDATE 1 using IShellItemImageFactory.GetImage: I can extract the icons and the icons are cached in iconcache_256.db file (and other *.db files too of less con cache size), but If I navigate to the directory through Explorer.exe then the operating system starts extracting and caching again the icon for the same folders I already cached...

Full code sample:

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

I'm not sure why the operating system insists to extract new icons and cache them when I already cached them, this multiplies x2 the cached icon references in iconcache_256.db (and in the other *.db files), because if I iterate 1000 subfolders from a directory to extract and cache their icons, if after I do that I navigate to that folder through Explorer.exe then the O.S. will extract and cache again those 1.000 subfolder icons creating NEW entries in iconcache_256.db (and in the other *.db files).

I don't know how to read the database format of iconcache_256.db file, so I don't know the structure format, but if the structure takes a directory path as one of its fields then I suspect the code approaches I used maybe forces to add a directory path field that differs from what the operating system adds in the icon cache field when I navigate to the folder to cache icons through Explorer.exe, and maybe for that reason the icon cache references are multiplied x2... just I'm speculating...

UPDATE 3

I thought that maybe the real problem could be that the references added in icon cache *.db files maybe are per-process and for that reason when I navigate to the directory with Explorer.exe it starts extracting and caching again the icons that I already extracted and cached when debugging my executable in Visual Studio...

So I did a test running the same code to extract/cache folder icons from two different processes just by building the same project to two executable with different names and different assembly guids. I discovered that the icon cache references in *.db files are NOT multiplied/duplicated when I run the second executable, so the per-process idea is discarded.

I really don't know what more can I try... and what is differing in the "duplicated" references inside the *.db icon cache files.

解决方案

Let me summarize everything starting from the scratch, since perhaps with so much text and comments in the comments box the question and the bounty could had been very confusing for users:

The Objective

My main goal was and is to preload the folder icon from a bunch of specific folders. The icon is specified in the "desktop.ini" file inside each of these folders, this is an O.S feature on which the O.S visually represents the folder as an icon, it literally replaces the common yellow default folder icon with the file contents preview, for the icon of your choose that is specified in the "desktop.ini" file. It's a beautiful preview feature for example for folders containing videogames, music albums or movies, on which you can use the game, movie or music cover as the icon for that folder.

The code approach

Using some code to call the IShellItemImageFactory.GetImage() function, or same thing using WindowsAPICodePack library I was able to get the operating system to generate the icons and add their respective entries to the Iconcache_nnn.db file.

The problem

The problem I found was that the O.S ignored these icons, it did not wanted to use my cached icons to preview the folder icons, but instead the O.S created new entries with different hashes but being the same icon, in other words, the O.S re-caches the icons that I already cached.

The root of the problem

By trial and error I discovered that the process architecture (x86 / x64) on which my code was ran to call IShellItemImageFactory.GetImage() function, it matters a lot.

So having a 64-bit Windows I was always running my code in x86 mode, and this was causing, without me being aware of the problem, that my x86 process generated icon entries with a different hash than the entries that the O.S was creating in the Iconcache_nnn.db file, and this is the reason why icons were cached again by the O.S, because the O.S didn't recognized the hashes of my cached icons from my x86 process.

I really don't know why this happens in this way, but it is like that, so rescuing this comment from @Simon Mourier:

AFAIK, the icon/thumb hash for a path is computed using the size (doesn't exist for a folder), last modified date, and the file identifier

To that computation we need to add another factor: process architecture differentiation ?

The solution

The solution in my 64-Bit O.S is simply to make sure compile the process (that programmatically gets the folder icons) in x64 to match the same architecture of the O.S / Explorer.exe. In case of having a 32-Bit Windows there is not much to say about, run your x86 process.

And this is a simple code to preload the icon of a folder using WindowsAPICodePack library:

    ''' ----------------------------------------------------------------------------------------------------
    ''' <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

Example usage:

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

Remember, a .NET assembly containing that code must be run in x64 mode if operating under a 64-Bit Windows, otherwise that code will generate the icon entries in the Iconcache_nnn.db file but when accessing the folders via Explorer.exe the O.S will simply ignore those entries and instead re-cache again the folder icons generating new additional entries.

Lastly, don't forget that you can check and preview the icon entries in the Iconcache_nnn.db file using the tool thumbcacheviewer mentioned by @Max.

这篇关于在 Windows 图标缓存中预加载特定文件夹的文件夹图标,在 C# 或 VB.NET 中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆