切换线程语言后恢复,获取已知文件夹多语言文本失败原因?
问题:获取特定语言的已知文件夹文本异常
我想获取已知文件夹的特定语言文本,参考之前的思路用SetThreadUILanguage()切换线程语言后获取区域名称。我的系统安装了英文和泰语,当前系统语言为英文。切换线程语言后,IPropertyDescription::GetDisplayName()能正常获取对应语言的属性名称,但获取属性值时出现以下异常:
SHGetFileInfo()第一次切换到泰语时可正常返回对应名称,但切回系统语言后,依然返回泰语文本,失效IPropertyStore::GetValue()始终只返回系统语言(英文)的文本
我需要用SHGetFileInfo()或替代方法,获取当前线程语言对应的文件夹文本,测试代码如下:
#include <iostream> #include <objbase.h> #include <propsys.h> #include <propkey.h> #include <shellapi.h> #include <Shlobj.h> #pragma comment(lib, "Ole32.lib") // Included libraries here to help build the test program. #pragma comment(lib, "Propsys.lib") #pragma comment(lib, "Shell32.lib") void ShowPropName(const PROPERTYKEY& infoKey) { IPropertyDescription *pIDesc; WCHAR *pczPropName; if(PSGetPropertyDescription(infoKey, IID_PPV_ARGS(&pIDesc)) == S_OK) { pIDesc->GetDisplayName(&pczPropName); wprintf(L"Property Name <%s>\n", pczPropName); CoTaskMemFree(pczPropName); pIDesc->Release(); } } void ShowItemNameFromFileInfo(REFKNOWNFOLDERID fidFolder) { LPITEMIDLIST pidlFolder; SHFILEINFO infoFile; if(SHGetKnownFolderIDList(fidFolder, 0, NULL, &pidlFolder) == S_OK) { if(SHGetFileInfo((LPCTSTR)pidlFolder, 0, &infoFile, sizeof(infoFile), SHGFI_PIDL | SHGFI_DISPLAYNAME) != 0) { wprintf(L"Item Name <%s>\n", infoFile.szDisplayName); } ILFree(pidlFolder); } } void ShowItemNameFromPropStore(const PROPERTYKEY& infoKey, REFKNOWNFOLDERID fidFolder) { LPITEMIDLIST pidlFolder; IPropertyStore *pIStore; PROPVARIANT propValue; if(SHGetKnownFolderIDList(fidFolder, 0, NULL, &pidlFolder) == S_OK) { if(SHGetPropertyStoreFromIDList(pidlFolder, GPS_DEFAULT, IID_IPropertyStore, (void **)&pIStore) == S_OK) { PropVariantInit(&propValue); if(pIStore->GetValue(infoKey, &propValue) == S_OK) { wprintf(L"Item Name <%s>\n", propValue.bstrVal); PropVariantClear(&propValue); } if(pIStore != NULL) { pIStore->Release(); } } } } int main() { LANGID nLangPC; LANGID nLang2; CoInitialize(nullptr); nLang2 = 0x041E; nLangPC = GetThreadUILanguage(); wprintf(L"\n------- 0x%04X ------------\n", nLang2); SetThreadUILanguage(nLang2); // Set language 2 ShowPropName(PKEY_FileName); // Language 2 - Correct ShowItemNameFromFileInfo(FOLDERID_Music); // Language 2 - Correct ShowItemNameFromPropStore(PKEY_FileName, FOLDERID_Music); // Language PC - Wrong wprintf(L"\n------- 0x%04X ------------\n", nLangPC); SetThreadUILanguage(nLangPC); // Set language PC ShowPropName(PKEY_FileName); // Language PC - Correct ShowItemNameFromFileInfo(FOLDERID_Music); // Language 2 - Wrong ShowItemNameFromPropStore(PKEY_FileName, FOLDERID_Music); // Language PC - Correct CoUninitialize(); return 0; }
解决方法
修复SHGetFileInfo()缓存问题
SHGetFileInfo()会缓存获取到的显示名称,导致切换语言后返回旧值。两种解决方式:
- 禁用缓存:调用
SHGetFileInfo时添加SHGFI_NO_CACHE标志,修改后的函数:
void ShowItemNameFromFileInfo(REFKNOWNFOLDERID fidFolder) { LPITEMIDLIST pidlFolder; SHFILEINFO infoFile; if(SHGetKnownFolderIDList(fidFolder, 0, NULL, &pidlFolder) == S_OK) { // 添加SHGFI_NO_CACHE避免缓存 if(SHGetFileInfo((LPCTSTR)pidlFolder, 0, &infoFile, sizeof(infoFile), SHGFI_PIDL | SHGFI_DISPLAYNAME | SHGFI_NO_CACHE) != 0) { wprintf(L"Item Name <%s>\n", infoFile.szDisplayName); } ILFree(pidlFolder); } }
- 刷新缓存:切换语言后调用
SHChangeNotify()清除对应项的缓存:
// 在SetThreadUILanguage之后调用 SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_IDLIST, pidlFolder, NULL);
替换IPropertyStore::GetValue()获取本地化名称
IPropertyStore获取的是文件原始属性值,不支持线程语言本地化。改用IShellFolder::GetDisplayNameOf(),它会尊重当前线程UI语言设置,新增以下函数:
void ShowItemNameFromShellFolder(REFKNOWNFOLDERID fidFolder) { LPITEMIDLIST pidlFolder; if (SHGetKnownFolderIDList(fidFolder, 0, NULL, &pidlFolder) != S_OK) return; IShellFolder* pDesktopFolder; if (SHGetDesktopFolder(&pDesktopFolder) != S_OK) { ILFree(pidlFolder); return; } LPITEMIDLIST pidlRelative; // 将绝对PIDL转换为相对桌面的PIDL if (pDesktopFolder->ParseDisplayName(NULL, NULL, (LPOLESTR)pidlFolder, NULL, &pidlRelative, NULL) != S_OK) { pDesktopFolder->Release(); ILFree(pidlFolder); return; } STRRET strDispName; // 获取本地化显示名称 if (pDesktopFolder->GetDisplayNameOf(pidlRelative, SHGDN_NORMAL, &strDispName) == S_OK) { WCHAR szDisplayName[MAX_PATH]; StrRetToBuf(&strDispName, pidlRelative, szDisplayName, MAX_PATH); wprintf(L"Item Name <%s>\n", szDisplayName); } ILFree(pidlRelative); pDesktopFolder->Release(); ILFree(pidlFolder); }
在main函数中调用这个函数,即可获取当前线程语言对应的文件夹名称。
额外注意事项
- 切换线程语言后,所有Shell接口调用必须在同一线程上下文执行
- 频繁切换语言场景优先使用
SHGFI_NO_CACHE,避免缓存干扰 IShellFolder::GetDisplayNameOf()是获取本地化文件夹名称的可靠方案,比IPropertyStore更适配多语言需求
内容的提问来源于stack exchange,提问作者davecupper
相关产品推荐
相关产品推荐

