Reputation: 25927
How can one get localized name of virtual Known Folder (such as This Computer, Control Panel etc.)?
Eg. for PL-pl they would be, respectively "Ten komputer", "Panel sterowania".
As suggested, I tried to use IKnownFolder
from Shell32. There's a 3rd party ready-to-use implementation of these APIs, WinAPICodePack. Sample code:
class Program
{
static void Main(string[] args)
{
// Add from nuget: WindowsAPICodePack-Shell
foreach (var folder in KnownFolders.All)
{
Console.WriteLine($"Canonical name: {folder.CanonicalName}");
Console.WriteLine($"\tPath exists: {folder.PathExists}");
Console.WriteLine($"\tLocalized name: {folder.LocalizedName}");
}
Console.ReadLine();
}
}
Unfortunately, mentioned "This Computer" and "Control Panel" entries does not have localized name.
Upvotes: 2
Views: 2107
Reputation: 33706
Note: .NET solution on the bottom.
you need got IShellItem
interface for your folder and call IShellItem::GetDisplayName
with SIGDN_NORMALDISPLAY
In UI this name is generally ideal for display to the user.
this return localized names
code in c++ can be like this
HRESULT GetKnownFolderName(int csidl, PWSTR* ppszName)
{
PIDLIST_ABSOLUTE pidl;
HRESULT hr = SHGetFolderLocation(0, csidl, 0, 0, &pidl);
if (S_OK == hr)
{
IShellItem* pItem;
hr = SHCreateItemFromIDList(pidl, IID_PPV_ARGS(&pItem));
ILFree(pidl);
if (S_OK == hr)
{
hr = pItem->GetDisplayName(SIGDN_NORMALDISPLAY, ppszName);
pItem->Release();
}
}
return hr;
}
void testDN()
{
if (0 <= CoInitialize(0))
{
PWSTR szName;
// CSIDL_CONTROLS - for "Control Panel"
// CSIDL_DRIVES - for "My Computer"
if (S_OK == GetKnownFolderName(CSIDL_DRIVES, &szName))
{
DbgPrint("%S\n", szName);
CoTaskMemFree(szName);
}
CoUninitialize();
}
}
also if we running only on Vista+ we can use SHGetKnownFolderIDList
instead SHGetFolderLocation
with FOLDERID_ComputerFolder
in place CSIDL_DRIVES
or we can get (or already have) IKnownFolder
interface first and then got IShellItem
from it by IKnownFolder::GetShellItem
- so yet two alternative variants begin from vista:
HRESULT GetKnownFolderName(IKnownFolder* kf, PWSTR* ppszName)
{
IShellItem* psi;
HRESULT hr = kf->GetShellItem(KF_FLAG_DEFAULT_PATH, IID_PPV_ARGS(&psi));
if (S_OK == hr)
{
hr = psi->GetDisplayName(SIGDN_NORMALDISPLAY, ppszName);
psi->Release();
}
return hr;
}
HRESULT GetKnownFolderNameVista2(REFKNOWNFOLDERID rfid, PWSTR* ppszName)
{
IKnownFolderManager* mgr;
HRESULT hr = CoCreateInstance(__uuidof(KnownFolderManager), 0, CLSCTX_ALL, IID_PPV_ARGS(&mgr));
if (0 <= hr)
{
IKnownFolder* kf;
hr = mgr->GetFolder(rfid, &kf);
mgr->Release();
if (S_OK == hr)
{
hr = GetKnownFolderName(kf, ppszName);
kf->Release();
}
}
return hr;
}
HRESULT GetKnownFolderNameVista(REFKNOWNFOLDERID rfid, PWSTR* ppszName)
{
PIDLIST_ABSOLUTE pidl;
HRESULT hr = SHGetKnownFolderIDList(rfid, KF_FLAG_NO_ALIAS, 0, &pidl);
if (S_OK == hr)
{
IShellItem* pItem;
hr = SHCreateItemFromIDList(pidl, IID_PPV_ARGS(&pItem));
ILFree(pidl);
if (S_OK == hr)
{
hr = pItem->GetDisplayName(SIGDN_NORMALDISPLAY, ppszName);
pItem->Release();
}
}
return hr;
}
void testDN()
{
if (0 <= CoInitialize(0))
{
PWSTR szName;
if (S_OK == GetKnownFolderNameVista(FOLDERID_ControlPanelFolder, &szName))
{
DbgPrint("%S\n", szName);
CoTaskMemFree(szName);
}
if (S_OK == GetKnownFolderNameVista2(FOLDERID_ComputerFolder, &szName))
{
DbgPrint("%S\n", szName);
CoTaskMemFree(szName);
}
CoUninitialize();
}
}
else one way - use IShellFolder::GetDisplayNameOf
with this code will be look like
HRESULT GetKnownFolderName(int csidl, PWSTR* ppszName)
{
PIDLIST_ABSOLUTE pidl;
HRESULT hr = SHGetFolderLocation(0, csidl, 0, 0, &pidl);
if (S_OK == hr)
{
IShellFolder* psf;
PCUITEMID_CHILD pidlLast;
hr = SHBindToParent(pidl, IID_PPV_ARGS(&psf), &pidlLast);
if (S_OK == hr)
{
STRRET str;
hr = psf->GetDisplayNameOf(pidlLast, SHGDN_NORMAL, &str);
psf->Release();
if (hr == S_OK)
{
str.uType == STRRET_WSTR ? *ppszName = str.pOleStr, S_OK : hr = E_FAIL;
}
}
}
return hr;
}
void testDN()
{
if (0 <= CoInitialize(0))
{
PWSTR szName;
if (S_OK == GetKnownFolderName(CSIDL_DRIVES, &szName))
{
DbgPrint("%S\n", szName);
CoTaskMemFree(szName);
}
if (S_OK == GetKnownFolderName(CSIDL_CONTROLS, &szName))
{
DbgPrint("%S\n", szName);
CoTaskMemFree(szName);
}
CoUninitialize();
}
}
You can use WinApiCodePack library (download from Nuget), which provides .NET implementation of several of mentioned before APIs. Sample code would look like following:
private static string GenerateLocalizedName(IKnownFolder shellFolder)
{
// Attempt to obtain localized name of folder
// 1. Directly from KnownFolder
string localizedName = shellFolder.LocalizedName;
// 2. From ShellObject (this solves This Computer and Control Panel issue)
if (String.IsNullOrEmpty(localizedName))
localizedName = (shellFolder as ShellObject)?.Name;
// 3. If folder is not virtual, use its localized name from desktop.ini
if (String.IsNullOrEmpty(localizedName) && Directory.Exists(shellFolder.Path))
{
try
{
localizedName = WinApiInterop.GetLocalizedName(shellFolder.Path);
}
catch
{
// Intentionally left empty
}
}
// 4. If folder is not virtual, use its filename
if (String.IsNullOrEmpty(localizedName) && Directory.Exists(shellFolder.Path))
localizedName = Path.GetFileName(shellFolder.Path);
// 5. If everything else fails, use its canonicalName (eg. MyComputerFolder)
if (String.IsNullOrEmpty(localizedName))
localizedName = shellFolder.CanonicalName;
return localizedName;
}
private void LoadShellFolders()
{
foreach (var shellFolder in KnownFolders.All)
{
string localizedName = GenerateLocalizedName(shellFolder);
string comment = shellFolder.PathExists ? shellFolder.Path : $"shell:{shellFolder.CanonicalName}";
infos.Add(new ShellFolderInfo(shellFolder.CanonicalName,
localizedName,
comment,
shellFolder.CanonicalName,
shellFolder.PathExists ? shellFolder.Path : null));
}
}
Also, the WinApiInterop class, which resolves localized strings from desktop.ini:
static class WinApiInterop
{
[DllImport("shell32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)]
internal static extern int SHGetLocalizedName(string pszPath, StringBuilder pszResModule, ref int cch, out int pidsRes);
[DllImport("user32.dll", EntryPoint = "LoadStringW", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)]
internal static extern int LoadString(IntPtr hModule, int resourceID, StringBuilder resourceValue, int len);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, EntryPoint = "LoadLibraryExW")]
internal static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags);
internal const uint DONT_RESOLVE_DLL_REFERENCES = 0x00000001;
internal const uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002;
[DllImport("kernel32.dll", ExactSpelling = true)]
internal static extern int FreeLibrary(IntPtr hModule);
[DllImport("kernel32.dll", EntryPoint = "ExpandEnvironmentStringsW", CharSet = CharSet.Unicode, ExactSpelling = true)]
internal static extern uint ExpandEnvironmentStrings(string lpSrc, StringBuilder lpDst, int nSize);
public static string GetFullPath(string path)
{
StringBuilder sb = new StringBuilder(1024);
ExpandEnvironmentStrings(path, sb, sb.Capacity);
return sb.ToString();
}
public static string GetLocalizedName(string path)
{
StringBuilder resourcePath = new StringBuilder(1024);
StringBuilder localizedName = new StringBuilder(1024);
int len, id;
len = resourcePath.Capacity;
if (SHGetLocalizedName(path, resourcePath, ref len, out id) == 0)
{
ExpandEnvironmentStrings(resourcePath.ToString(), resourcePath, resourcePath.Capacity);
IntPtr hMod = LoadLibraryEx(resourcePath.ToString(), IntPtr.Zero, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE);
if (hMod != IntPtr.Zero)
{
if (LoadString(hMod, id, localizedName, localizedName.Capacity) != 0)
{
return localizedName.ToString();
}
FreeLibrary(hMod);
}
}
return null;
}
}
Upvotes: 4