更改文件夹图标 & 使其立即生效

本文最后更新于:2024年7月27日 晚上

前情提要

众所周知,人类更倾向于看图而非文字

图片所包含的信息量更大,也更容易接受

(这就是大家不喜欢读书的原因ba)

言归正传,(这就是大家喜欢GUI的原因ba)

图像固然重要,但文字也不可或缺

于是乎,本着我都要的心态,现代图形界面中的 文件 & 文件夹 都被设计为:图标 + 文字描述 的形式

from folderico.com

需求

系统原生的文件夹图标是个黄色的“文件夹”(物理)

如果所有文件夹的图标长得都一样,那么也就失去了传递特异信息的能力

由于文字的解读要更加困难(后天能力),因此在众多千篇一律的文件夹中快速定位目标则成了一件烦人事

人云

这时候,有的同学要说了:要什么GUI,我都是直接CLI的…

打住打住,下一位

同学B:对于Windows,可以直接在资源管理器中(需要焦点)直接输入想要文件(夹)名称(支持输入法)即可定位

这位同学说得好,不过有了更差异化的图标便更能锦上添花 (๑¯ω¯๑)

设置文件夹图标

GUI

属性-自定义-更改图标[1]

相信大家早就知道了,跳过跳过(skip)

属性-自定义-更改图标

API

咳咳,身为Programmer,我们还是来讨论一下更深入♂♀的内容吧

そもそも,说到底,Windows设置文件夹图标的原理究竟是什么

本质上就是在这个文件夹内新建了一个desktop.ini(之前的文章也提到过:Windows 系统级个人文件夹 vs OneDrive简析

这个文件的属性比较特殊:SH,aka.System + Hide

1
2
[.ShellClassInfo]
IconResource=xxx.ico,0

这里除了.ico文件,还可以是exedll

我们可以通过以下代码来创建desktop.ini

1
2
3
4
5
6
7
8
9
10
11
QString iniPath = folderPath + "/desktop.ini";
QFile iniFile(iniPath);
if (iniFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&iniFile);
out << "[.ShellClassInfo]" << '\n';
out << "IconResource=" << iconPath << ",0" << '\n';
iniFile.close();

// Set desktop.ini file attributes:隐藏和系统文件
SetFileAttributesW(iniPath.toStdWString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
}

当你满怀欣喜地盯着文件夹时,—— 文件夹也在盯着你

Nothing happened.

事情并没有这么简单,也许还缺了什么

请看Windows官方文档:如何使用 Desktop.ini 自定义文件夹

使用以下步骤通过 Desktop.ini 自定义文件夹的样式:

  1. 使用 PathMakeSystemFolder 使文件夹成为系统文件夹。 这将设置文件夹上的只读位,以指示应启用为 Desktop.ini 保留的特殊行为。 也可以使用 attrib +s FolderName 命令行将文件夹设为系统文件夹。
  2. 为文件夹创建一个 Desktop.ini 文件。 应将其标记为隐藏系统,以确保对普通用户隐藏。
  3. 确保创建的 Desktop.ini 文件为 Unicode 格式。 这是存储可显示给用户的本地化字符串所必需的。

再看FolderIco的教程:如果自定义文件夹图标不显示怎么办? — What If the Custom Folder Icon Does Not Show?

To keep folder icon changed must be met following conditions:

  • Folder must have “Read Only” or “System” attribute, only these attributes allows to show customized folder icon.
  • Folder must contain “desktop.ini” file (This file contain path to the customized icon).

综上,我们还缺少一个条件:

  • 文件夹必须拥有 只读(R)系统(S)属性

Continue

我们可以在cmd中通过attrib +R Folder命令为其添加只读属性

或者

1
SetFileAttributesW(folderPath.toStdWString().c_str(), FILE_ATTRIBUTE_READONLY);

或者

1
2
// Make this a system folder, so that we look for desktop.ini when we navigate to this folder.
PathMakeSystemFolder(folderPath.toStdWString().c_str());

大家看到这里可能一头雾水,别急

  • 首先,这可能很反直觉,但是文件夹上的只读(R)属性与文件不同,并不是“只读”的本意。”This attribute is not honored on directories. “ – SetFileAttributesW - READONLY. 在文件夹上,该属性和S属性一样,更多的这是一个标记,指示系统去进行一些特殊操作,例如:查找并加载desktop.ini
  • PathMakeSystemFolder,这个函数看起来是给文件夹赋予System属性,但实际上他会视情况,给予RS属性,一般情况下是Read-Only属性。由于Windows是闭源系统,所以这里给出一个不知道是不是源码的源码 (我看到了两份不同的实现,所以不确定代码是否可靠)

好的,不管怎么样,到目前为止,我们已经达成了为文件夹自定义图标的所有条件。

正片叠底

以上都是洒洒水,相信大家噼里啪啦、叽里呱啦就查出来了

图标缓存

主要问题在于:为什么上述条件都达成之后,文件夹图标还是没有变化,或者说,延迟更新

传统功夫(無駄)

可能有聪明的同学会说了:这题我会,用这个函数通知系统更改即可,SHChangeNotify(SHCNE_ATTRIBUTES, ...)

很遗憾,起码对于Windows 11的文件夹来说,该函数没有任何鸟用

无论是:SHCNE_ATTRIBUTESSHCNE_UPDATEITEM 或是 SHCNE_ASSOCCHANGED,甚至是SHCNE_ALLEVENTS

Even:SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, NULL)

都无济于事…

还有一些無駄的方法我也罗列一下:

  • ie4uinit.exe -show
  • nircmd.exe sysrefresh (第三方)

参见:windows - Update folder icon with desktop.ini & instantly change (C++) - Stack Overflow

NO it doesn’t work at all. 5 hour in computer during the night works as mirage —— Piotr Sydow

以上方法都无法立即刷新图标缓存(通常在几分钟后刷新)

暴力美学(保底)

当然,我们都知道,重启能解决90%问题

是的没错,重启资源管理器(explorer.exe)可以解决这个问题

1
2
taskkill /f /im explorer.exe
start explorer

BUT:

  1. 重启并不能算立即
  2. 用户体验,非常非常非常非常,BAD,納得できない

从人机交互和产品设计角度来说,非常失败,无法接受,仅此一项就会让用户流失

更不用说已经有软件能够做到不重启的情况下立即刷新 - FolderIco

ta 能做到,就说明:理论存在,实践开始

毁灭

还有一种方法在民间广为流传,实属暴力楷模:[2]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
rem 关闭Windows外壳程序explorer
taskkill /f /im explorer.exe
rem 清理系统图标缓存数据库
attrib -h -s -r "%userprofile%\AppData\Local\IconCache.db"
del /f "%userprofile%\AppData\Local\IconCache.db"
attrib /s /d -h -s -r "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\*"
del /f "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_32.db"
del /f "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_96.db"
del /f "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_102.db"
del /f "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_256.db"
del /f "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_1024.db"
del /f "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_idx.db"
del /f "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_sr.db"
rem 清理 系统托盘记忆的图标
echo y|reg delete "HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify" /v IconStreams
echo y|reg delete "HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify" /v PastIconsStream
rem 重启Windows外壳程序explorer
start explorer

直接删掉所有缓存文件和注册表项(逼Windows重建),并重启资源管理器

哦,我的老天爷啊

杀鸡焉用牛刀,太不优雅了吧,不到万不得已并不建议使用 -_-||

优雅而精准

FolderIco已经向我们证明了,存在一种方案,既不需要删除文件,也不需要重启资源管理器,就能刷新图标缓存的方案

虽然问题在于这是闭源软件,看不到源码呜呜

// 难道要反编译嘛,aaa

不,不可能!

要不发个邮件好了,啊,在那之前,一定有办法,o(╥﹏╥)o

SHGetSetFolderCustomSettings

经过了七七八十一天的搜索,在见识到了GPT-4oClaude 3.5对于Windows API的无力之后

我终于找到了,那本真经:SHGetSetFolderCustomSettings

还得是:Stackoverflow

节选评论:

I did some further tests where I found that the supposed “catch all” SHCNE_ASSOCCHANGED is unreliable aswell. But SHGetSetFolderCustomSettings() always updates the icon immediately (despite being a deprecated API since Win XP SP3)!

I also used SHChangeNotify() and it’s not reliable in Win10. Sometimes it works, sometimes not. It doesn’t change it immediately but takes 1 minutes or so.

Yesterday I played around with SHCNE_UPDATEITEM but couldn’t get consistent results. Sometimes it would update the folder icon, sometimes not. I also tried to add SHCNF_FLUSH and SHCNE_UPDATEDIR but the result was still unreliable.

SHGetSetFolderCustomSettings是专门用于读取和写入desktop.ini的函数

(不过为什么GPT不告诉我,aaaaa)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void setFolderIcon(const QString &folderPath, const QString &iconPath, int iconIndex = 0)
{
SHFOLDERCUSTOMSETTINGS fcs = {0}; // 初始化所有成员为0
fcs.dwSize = sizeof(SHFOLDERCUSTOMSETTINGS);
fcs.dwMask = FCSM_ICONFILE;
auto iconWStr = iconPath.toStdWString(); // IMPORTANT: 不能写为 iconPath.toStdWString().c_str(),因为返回的是临时对象,导致指针无效
fcs.pszIconFile = LPWSTR(iconWStr.c_str());
fcs.cchIconFile = 0;
fcs.iIconIndex = iconIndex;

// 这里返回临时对象指针没事,因为语句没结束不会被释放
HRESULT hr = SHGetSetFolderCustomSettings(&fcs, folderPath.toStdWString().c_str(), FCS_FORCEWRITE);
if (FAILED(hr)) {
qWarning() << "Failed to set folder icon";
}
}

游戏结束,根本不需要自行新建desktop.ini巴拉的,直接包办

语法小细节

这里有个细节坑了我一下

1
2
auto iconWStr = iconPath.toStdWString();
fcs.pszIconFile = LPWSTR(iconWStr.c_str());

这里不能缩写为:

1
fcs.pszIconFile = LPWSTR(iconPath.toStdWString().c_str());

否则,最终写入desktop.ini中的路径会变得很奇怪

因为.toStdWString()返回的是一个临时对象,那么.toStdWString().c_str()也就是一个临时对象的指针

随时会被销毁(语句结束后)

所以最终就会造成野指针问题,变成随机字符串,bomb(快用Rust

那么又有小盆友要问了,为什么这句没事

1
SHGetSetFolderCustomSettings(&fcs, folderPath.toStdWString().c_str(), FCS_FORCEWRITE);

因为临时对象在语句结束后销毁,所以在SHGetSetFolderCustomSettings执行过程中都万事大吉

Why SHGetSetFolderCustomSettings

好的,那么,凭什么,为什么SHGetSetFolderCustomSettings可以做到立即刷新,他调用了什么API,做了什么操作呢?

什么,你说你不想知道,诶,别走啊

咳咳,留下来的都是好饱饱

好吧,答案是:很遗憾,Windows是闭源操作系统,hhhhhhhhhh

o(╥﹏╥)o

真滴米有办法了吗,不行,我去GitHub上搜一搜

你别说,还真有:nt5src/Source/XPSP1/NT/shell/shell32/fldsets.c at master · tongzx/nt5src (github.com)

听说是XP代码泄露

不过呢,不知道是版本太老,还是可信度太低

我在代码里并没有看到什么特殊操作

1
2
3
4
5
6
7
8
9
...
if (SUCCEEDED(hret) && (dwReadWrite & FCS_FORCEWRITE))
{
// Make desktop.ini hidden
SetFileAttributes(szIniFile, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
// Make this a system folder, so that we look for desktop.ini when we navigate to this folder.
PathMakeSystemFolder(pszPath);
}
....

都是我们的基操

所以到底为啥啊,aaaaaa

Windows十大未解之谜,看来只能入职微软了

Peace

Ref

用desktop.ini更新文件夹图标&立即更改(C++)-腾讯云开发者社区-腾讯云 (tencent.com)

batch file - Changing desktop.ini doesn’t update folder icon automatically in Windows - Stack Overflow

windows - Update folder icon with desktop.ini & instantly change (C++) - Stack Overflow

windows - Refresh Icon Cache Without Rebooting - Super User

c++ - How to refresh the folder icon instantly in Windows - Stack Overflow

如何使用 Desktop.ini 自定义文件夹 - Win32 apps | Microsoft Learn

pathMakeSystemFolderW 函数 (shlwapi.h) - Win32 apps | Microsoft Learn

SHChangeNotify 函数 (shlobj_core.h) - Win32 apps | Microsoft Learn

What If the Custom Folder Icon Does Not Show?

tongzx/nt5src: Source code of Windows XP (NT5). Leaks are not from me. I just extracted the archive and cabinet files. (github.com)

SHGetSetFolderCustomSettings 函数 (shlobj_core.h) - Win32 apps | Microsoft Learn

如何改变文件夹的图标(未完成。。。。。。)-CSDN博客

windows - How can I immediately reload a folder icon when desktop.ini is changed - Stack Overflow

Windows-Server-2003/shell/shlwapi/path.c at 5c6fe3db626b63a384230a1aa6b92ac416b0765f · selfrender/Windows-Server-2003 (github.com)


更改文件夹图标 & 使其立即生效
https://mrbeancpp.github.io/2024/07/27/Set-Folder-Icon-and-Refresh-Cache-Immediately/
作者
MrBeanC
发布于
2024年7月27日
许可协议