I am using SHCreateItemFromParsingName
to turn a path into a IShellItem
:
IShellItem ParseName(String path)
{
IShellItem shellItem;
HRESULT hr = SHCreateItemFromParsingName(path, null, IShellItem, out shellItem);
if (Failed(hr))
throw new ECOMException(hr);
return shellItem;
}
Note: A
IShellItem
was introduced around 2006 to provide a handy wrapper around the Windows 95-eraIShellFolder
+pidl
constructs. You can even ask aIShellItem
to cough up it's underlyingIShellFolder
andpidl
with theIParentAndItem.GetParentAndItem
interface and method.
I can get ahold of some well-known locations in the shell namespace, and see their absolute parsing (SIGDN_DESKTOPABSOLUTEPARSING
) and editing (SIGDN_DESKTOPABSOLUTEEDITING
) display names:
Path | Editing | Parsing |
---|---|---|
C:\ | "C:" | "C:" |
C:\Windows | "C:\Windows" | "C:\Windows" |
Desktop | "Desktop" | "C:\Users\Ian\Desktop" |
Computer | "This PC" | "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" |
Recycle Bin | "Recycle Bin" | "::{645FF040-5081-101B-9F08-00AA002F954E}" |
Documents Library | "Libraries\Documents" | "::{031E4825-7B94-4DC3-B131-E946B44C8DD5}\Documents.library-ms" " |
Startup | "C:\Users\Ian\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup" | "C:\Users\Ian\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup" |
I can use IFileOpenDialog
to let the user select one of these folders. But i'd really like the user to be able to type
and be able to parse it into an IShellItem
.
The problem is that some of the paths are not parsed by SHCreateItemFromParsingName
:
SHCreateItemFromParsingName("C:\")
: ParsesSHCreateItemFromParsingName("C:\Windows")
: ParsesSHCreateItemFromParsingName("")
: Parses (but becomes "This PC")
SHCreateItemFromParsingName("This PC")
: FailsSHCreateItemFromParsingName("Recycle Bin")
: FailsSHCreateItemFromParsingName("Libraries")
: FailsSHCreateItemFromParsingName("OneDrive")
: FailsSHCreateItemFromParsingName("Libraries\Documents")
: FailsSHCreateItemFromParsingName("Network")
: FailsSHCreateItemFromParsingName("Startup")
: FailsMeanwhile, the IFileOpenDialog control that my program uses can parse them fine:
How can i parse the various special shell name places that a user might type in (that Windows Explorer and the IFileOpen dialog can parse) into an IShellItem
for that folder?
The real question is that i want the user to be able to have a recent MRU list that contains things like:
and be able to parse them later.
It would be interesting to debug Explorer and see how it does it.
My suggestion is; if the initial parse fails, prepend shell:
to the path string and try parsing it again with SHParseDisplayName
. If you set STR_PARSE_SHELL_PROTOCOL_TO_FILE_OBJECTS
in the bind context you can also bind to special files. The shell: protocol is able to parse the internal/canonical name of special/known folders but I don't know if it also checks the display name.
Edit:
I had a chance to play around a bit now and the shell: prefix is not a huge improvement because it only checks the known folder canonical names:
PCWSTR paths[] = {
TEXT("C:\\"),
TEXT("C:\\Windows"),
TEXT(""),
TEXT("This PC"),
TEXT("MyComputerFolder"), // Canonical KF name
TEXT("Recycle Bin"),
TEXT("RecycleBinFolder"), // Canonical KF name
TEXT("Libraries"),
TEXT("OneDrive"),
TEXT("Libraries\\Documents"),
TEXT("Network"),
TEXT("NetworkPlacesFolder"), // Canonical KF name
TEXT("Startup"),
};
OleInitialize(0);
INT pad = 0, fill, i;
for (i = 0; i < ARRAYSIZE(paths); ++i) pad = max(pad, lstrlen(paths[i]));
for (i = 1, fill = printf("%-*s | Original | shell: |\n", pad, ""); i < fill; ++i) printf("-"); printf("\n");
for (i = 0; i < ARRAYSIZE(paths); ++i)
{
WCHAR buf[MAX_PATH], *p1 = NULL, *p2 = NULL;
IShellItem*pSI;
HRESULT hr = SHCreateItemFromParsingName(paths[i], NULL, IID_IShellItem, (void**) &pSI);
if (SUCCEEDED(hr)) pSI->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &p1), pSI->Release();
wsprintf(buf, L"shell:%s", paths[i]);
HRESULT hr2 = SHCreateItemFromParsingName(buf, NULL, IID_IShellItem, (void**) &pSI);
if (SUCCEEDED(hr2)) pSI->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &p2), pSI->Release();
wprintf(L"%-*s | %.8x | %.8x | %s\n", pad, paths[i], hr, hr2, p2 && *p2 ? p2 : p1 ? p1 : L"");
CoTaskMemFree(p1), CoTaskMemFree(p2);
}
gives me this output:
| Original | shell: |
-------------------------------------------
C:\ | 00000000 | 80070003 | C:\
C:\Windows | 00000000 | 80070003 | C:\Windows
| 00000000 | 80070003 | ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}
This PC | 80070002 | 80070003 |
MyComputerFolder | 80070002 | 00000000 | ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}
Recycle Bin | 80070002 | 80070003 |
RecycleBinFolder | 80070002 | 00000000 | ::{645FF040-5081-101B-9F08-00AA002F954E}
Libraries | 80070002 | 00000000 | ::{031E4825-7B94-4DC3-B131-E946B44C8DD5}
OneDrive | 80070002 | 80070003 |
Libraries\Documents | 80070002 | 80070002 |
Network | 80070002 | 80070003 |
NetworkPlacesFolder | 80070002 | 00000000 | ::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}
Startup | 80070002 | 00000000 | C:\Users\Anders\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
On Windows 8 SHCreateItemFromParsingName
calls SHParseDisplayName
(with STR_PARSE_AND_CREATE_ITEM
and STR_PARSE_TRANSLATE_ALIASES
) so even Microsoft have trouble separating parsing and display names in their API.
If you want to stay away from undocumented interfaces then you would have to add a third pass where you check the known folder display names. Or alternatively as Raymond Chen suggests in the comments; parse every path component manually against item display names in that IShellFolder
.
It would be interesting to debug Explorer and see how it does it.
begin from windows 7 shell use next undocumented interface (until it unchanged from win 7 up to latest win 10)
MIDL_INTERFACE("88DF9332-6ADB-4604-8218-508673EF7F8A") IShellUrl : public IUnknown
{
virtual HRESULT STDMETHODCALLTYPE ParseFromOutsideSource(PCWSTR,DWORD);
virtual HRESULT STDMETHODCALLTYPE GetUrl(PWSTR,DWORD);
virtual HRESULT STDMETHODCALLTYPE SetUrl(PCWSTR,DWORD);
virtual HRESULT STDMETHODCALLTYPE GetDisplayName(PWSTR,DWORD);
virtual HRESULT STDMETHODCALLTYPE GetPidl(ITEMIDLIST_ABSOLUTE * *);
virtual HRESULT STDMETHODCALLTYPE SetPidl(ITEMIDLIST_ABSOLUTE const *);
virtual HRESULT STDMETHODCALLTYPE SetPidlAndArgs(ITEMIDLIST_ABSOLUTE const *,PCWSTR);
virtual PWSTR STDMETHODCALLTYPE GetArgs();
virtual HRESULT STDMETHODCALLTYPE AddPath(ITEMIDLIST_ABSOLUTE const *);
virtual void STDMETHODCALLTYPE SetCancelObject(ICancelMethodCalls *);
virtual HRESULT STDMETHODCALLTYPE StartAsyncPathParse(HWND,PCWSTR,DWORD,ICancelMethodCalls *);
virtual HRESULT STDMETHODCALLTYPE GetParseResult();
virtual HRESULT STDMETHODCALLTYPE SetRequestID(int);
virtual HRESULT STDMETHODCALLTYPE GetRequestID(int *);
virtual HRESULT STDMETHODCALLTYPE SetNavFlags(int,int);
virtual HRESULT STDMETHODCALLTYPE GetNavFlags(long *);
virtual HRESULT STDMETHODCALLTYPE Execute(struct IShellNavigationTarget *,int *,DWORD);
virtual HRESULT STDMETHODCALLTYPE SetCurrentWorkingDir(ITEMIDLIST_ABSOLUTE const *);
virtual void STDMETHODCALLTYPE SetMessageBoxParent(HWND);
virtual HRESULT STDMETHODCALLTYPE GetPidlNoGenerate(ITEMIDLIST_ABSOLUTE * *);
virtual DWORD STDMETHODCALLTYPE GetStandardParsingFlags(BOOL);
};
class DECLSPEC_UUID("4BEC2015-BFA1-42FA-9C0C-59431BBE880E") ShellUrl;
we can use it for parse display names like Recycle Bin
, This PC
, etc.. (IFileOpenDialog dialog use it)
we can use it synchronous or asynchronous. for synchronous need call
ParseFromOutsideSource(L"your name", flags = GetStandardParsingFlags(0))
if this call is ok, we can get and use ITEMIDLIST_ABSOLUTE*
by call GetPidl
(when no longer need free it by ILFree
) also if file system path exist can get it by GetUrl
otherwise original name returned.
also possible use asynchronous parsing - you need call StartAsyncPathParse
- pass own hwnd and optional ICancelMethodCalls
interface. when operation finished shell post RegisterWindowMessage(L"AC_ParseComplete")
(wParam == IShellUrl*, lParam == 0
) to your window. you can get final status by call GetParseResult()
and if it ok - use GetPidl
code example for synchronous parsing
HRESULT ParsePath(PCWSTR path, IShellItem **ppsi)
{
IShellUrl* pShUrl;
HRESULT hr = CoCreateInstance(__uuidof(ShellUrl), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShUrl));
if (hr == S_OK)
{
if (SUCCEEDED(hr = pShUrl->ParseFromOutsideSource(path, pShUrl->GetStandardParsingFlags(TRUE))))
{
ITEMIDLIST_ABSOLUTE *pidl;
if (SUCCEEDED(hr = pShUrl->GetPidl(&pidl)))
{
hr = SHCreateItemFromIDList(pidl, IID_PPV_ARGS(ppsi));
//WCHAR sz[MAX_PATH];
//if (SUCCEEDED(pShUrl->GetUrl(sz, RTL_NUMBER_OF(sz)))) DbgPrint(">%S\n", sz);
ILFree(pidl);
}
}
pShUrl->Release();
}
return hr;
}
void tt(PCWSTR path)
{
IShellItem *psi;
if (0 <= ParsePath(path, &psi))
{
PWSTR szName;
if (S_OK == psi->GetDisplayName(SIGDN_NORMALDISPLAY, &szName))
{
DbgPrint("NORMALDISPLAY>%S\n", szName);
CoTaskMemFree(szName);
}
if (S_OK == psi->GetDisplayName(SIGDN_FILESYSPATH, &szName))
{
DbgPrint("FILESYSPATH>%S\n", szName);
CoTaskMemFree(szName);
}
psi->Release();
}
}
void tt()
{
if (0 <= CoInitialize(0))
{
tt(L"Recycle Bin");
tt(L"Startup");
CoUninitialize();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With