0

By using Microsoft windows SDK 7.0, explorerDataProvider, I installed a virtual folder on windows 7.

image

When I open the file browse dialog from an application,

CFileDialog dlg(TRUE, NULL, 0, OFN_ENABLESIZING | OFN_ALLOWMULTISELECT, L"all(*.*)|*.*||", this);

INT_PTR result = dlg.DoModal();

if (result == IDOK)
{
   .
   . 
   .
   //some actions
}

it can also display the virtual folder:

image

But when I select the file, like "Zero" can then clicks "Open",

image

I tried to add breakpoints to

INT_PTR result = dlg.DoModal();

But it seems that this error happens inside DoModal().

I doing some research and revise the code of virtual folder:

HRESULT CFolderViewImplFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, ULONG *rgfInOut)
{
// If SFGAO_FILESYSTEM is returned, GetDisplayNameOf(SHGDN_FORPARSING) on that item MUST
// return a filesystem path.
HRESULT hr = E_INVALIDARG;

DWORD dwAttribs = 0;
dwAttribs |= SFGAO_BROWSABLE;
if (1 == cidl)
{
    int nLevel = 0;
    hr = _GetLevel(apidl[0], &nLevel);
    if (SUCCEEDED(hr))
    {
        BOOL fIsFolder = FALSE;
        hr = _GetFolderness(apidl[0], &fIsFolder);
        if (SUCCEEDED(hr))
        {

            if (fIsFolder)
            {
                dwAttribs |= SFGAO_FOLDER;

                dwAttribs |= SFGAO_FILESYSANCESTOR;
            }
            else
            {
                dwAttribs |= SFGAO_SYSTEM;

                dwAttribs |= SFGAO_FILESYSTEM;
            }

            if (nLevel < g_nMaxLevel)
            {
                dwAttribs |= SFGAO_HASSUBFOLDER;
            }
        }
    }
}

*rgfInOut &= dwAttribs;

return hr;
}

And return the path inGetDisplayNameOf()

HRESULT CFolderViewImplFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, SHGDNF shgdnFlags, STRRET *pName)
{
HRESULT hr = S_OK;
if (shgdnFlags & SHGDN_FORPARSING)
{
    WCHAR szDisplayName[MAX_PATH];
    if (shgdnFlags & SHGDN_INFOLDER)
    {
        // This form of the display name needs to be handled by ParseDisplayName.
        hr = _GetName(pidl, szDisplayName, ARRAYSIZE(szDisplayName));
    }
    else
    {
        PWSTR pszThisFolder = L"Computer\\Jerry";

            StringCchCopy(szDisplayName, ARRAYSIZE(szDisplayName), pszThisFolder);
            StringCchCat(szDisplayName, ARRAYSIZE(szDisplayName), L"\\");

            WCHAR szName[MAX_PATH];
            hr = _GetName(pidl, szName, ARRAYSIZE(szName));
            if (SUCCEEDED(hr))
            {
                StringCchCat(szDisplayName, ARRAYSIZE(szDisplayName), szName);
            }
    }
    if (SUCCEEDED(hr))
    {
        hr = StringToStrRet(szDisplayName, pName);
    }
}
else
{
    PWSTR pszName;
    hr = _GetName(pidl, &pszName);
    if (SUCCEEDED(hr))
    {
        hr = StringToStrRet(pszName, pName);
        CoTaskMemFree(pszName);
    }
}
return hr;
}

But it still stats error message of "Path does not exist" in the application. I add breakpoints in GetDisplayNameOf, but it is never triggered when the application opens a file browse dialog. But if I open a regular folder just by double click "Computer", it will be triggered.

The error message of "Path does not exist" seems cannot be deprecated or covered. Any way that I can revise the virtual folder to return the correct path ?


Update: I tried IFileDialog, I copied the code from the MSDN sample, code, and add breakpoints to see what will happen. However,

// Show the dialog
hr = pfd->Show(NULL);//the breakpoint is added here
if (SUCCEEDED(hr))
{
  // Obtain the result once the user clicks 
 // the 'Open' button.
 // The result is an IShellItem object.
 IShellItem *psiResult;
 hr = pfd->GetResult(&psiResult);
 if (SUCCEEDED(hr))
 {
   //do something
 }
}

When I click "Open" button, the error message box of "Path does not exist, check the path and try again" still show up. And it never comes out of pfd->Show(NULL) after I click Open" button.

Based on the comments in the sample code, it should come out of hr = pfd->Show(NULL); and reach to the next line.but the error message happens inside the hr = pfd->Show(NULL);.

Update:

In the IFileDialog, I tried

hr = pfd->GetOptions(&dwFlags);
            if (SUCCEEDED(hr))
            {

                hr = pfd->SetOptions(dwFlags | FOS_ALLNONSTORAGEITEMS);
                if (SUCCEEDED(hr))
                {
                    // Show the dialog
                    hr = pfd->Show(NULL);
                    if (SUCCEEDED(hr))
                    {
                        // Obtain the result once the user clicks 
                        // the 'Open' button.
                        // The result is an IShellItem object.
                        IShellItem *psiResult;
                        hr = pfd->GetResult(&psiResult);
                        if (SUCCEEDED(hr))
                        {
                            // We are just going to print out the 
                            // name of the file for sample sake.
                            PWSTR pszFilePath = NULL;
                            hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH,
                                &pszFilePath);
                            if (SUCCEEDED(hr))
                            {
                                TaskDialog(NULL,
                                    NULL,
                                    L"CommonFileDialogApp",
                                    pszFilePath,
                                    NULL,
                                    TDCBF_OK_BUTTON,
                                    TD_INFORMATION_ICON,
                                    NULL);
                                CoTaskMemFree(pszFilePath);
                            }
                            psiResult->Release();
                        }
                    }

                }

I also tried

hr = pfd->SetOptions(dwFlags);//delete FOS_FORCEFILESYSTEM         

And tried

DWORD dwFlags = 0;
if (SUCCEEDED(hr))
{
    // overwrite options completely
    hr = pfd->SetOptions(dwFlags | FOS_ALLNONSTORAGEITEMS);

I also tried to return the whole name in namespace extension code:

WCHAR szDisplayName[MAX_PATH];
    if (shgdnFlags & SHGDN_INFOLDER)
    {
        // This form of the display name needs to be handled by ParseDisplayName.
        hr = _GetName(pidl, szDisplayName, ARRAYSIZE(szDisplayName));
    }
    else
    {
        PWSTR pszThisFolder;// = L"Computer\\Jerry";
        hr = SHGetNameFromIDList(m_pidl, (shgdnFlags & SHGDN_FORADDRESSBAR) ? SIGDN_DESKTOPABSOLUTEEDITING : SIGDN_DESKTOPABSOLUTEPARSING, &pszThisFolder);
        if (SUCCEEDED(hr))
        {
            StringCchCopy(szDisplayName, ARRAYSIZE(szDisplayName), pszThisFolder);
            StringCchCat(szDisplayName, ARRAYSIZE(szDisplayName), L"\\");

            WCHAR szName[MAX_PATH];
            hr = _GetName(pidl, szName, ARRAYSIZE(szName));
            if (SUCCEEDED(hr))
            {
                StringCchCat(szDisplayName, ARRAYSIZE(szDisplayName), szName);
            }
            CoTaskMemFree(pszThisFolder);
        }
    }
    if (SUCCEEDED(hr))
    {
        hr = StringToStrRet(szDisplayName, pName);
    }

But the result is: enter image description here

Update:

hr = pfd->SetOptions(dwFlags | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE);

This works for me!!

beasone
  • 1,073
  • 1
  • 14
  • 32
  • Your SNE is a child of the root Computer namespace, not of any filesystem drive. You can lie about your item attributes all you want, your SNE is still not part of the filesystem, so there are no filesystem path for the dialog to retrieve. It is clear you are invoking an old dialog that only knows how to deal with the filesystem, not a newer dialog that supports virtual items – Remy Lebeau Nov 08 '17 at 01:13
  • @RemyLebeau Can you tell me which newer dialog can support virtual items? – beasone Nov 08 '17 at 01:27
  • 1
    [`IFile(Open|Save)Dialog`](https://msdn.microsoft.com/en-us/library/windows/desktop/bb776913.aspx), which was introduced in Vista. [`CFileDialog`](https://docs.microsoft.com/en-us/cpp/mfc/reference/cfiledialog-class) originally wrapped [`Get(Open|Save)FileName()`](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646960.aspx), which only supports the filesystem. In VS2008+, `CFileDialog` has a `bVistaStyle` parameter in its constructor, and *should* use the newer API by default when you compile for Vista+. I would suggest ditching `CFileDialog` and use `IFileOpenDialog` directly. – Remy Lebeau Nov 08 '17 at 01:35
  • @RemyLebeau Can you check my updates ? Seems it doesn't work. – beasone Nov 08 '17 at 18:23
  • 1
    What options are you passing to `IFileDialog` before displaying it? Please show a [mcve]. Are you enabling the `FOS_FORCEFILESYSTEM` option, by chance? "*Ensures that returned items are file system items (`SFGAO_FILESYSTEM`)*", which yours is not. Have you tried the `FOS_ALLNONSTORAGEITEM` option? "*Enables the user to choose any item in the Shell namespace, not just those with `SFGAO_STREAM` or `SFAGO_FILESYSTEM` attributes.*" – Remy Lebeau Nov 08 '17 at 18:54
  • @RemyLebeau I just update all code , can you check the updates again please ? – beasone Nov 08 '17 at 19:10
  • @RemyLebeau I just tried to add FOS_NOVALIDATE, this time it works for me!! – beasone Nov 08 '17 at 19:19

1 Answers1

0

With the help of @RemyLebeau, I did some tests in my code. Finally, I found the working solution for me: I copied the code from MSDN for IFileDialog: https://msdn.microsoft.com/en-us/library/windows/desktop/bb776913.aspx#file_types and then revise it a little bit:

HRESULT BasicFileOpen()
{
// CoCreate the File Open Dialog object.
IFileDialog *pfd = NULL;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog,
    NULL,
    CLSCTX_INPROC_SERVER,
    IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
    // Create an event handling object, and hook it up to the dialog.
    IFileDialogEvents *pfde = NULL;
    hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
    if (SUCCEEDED(hr))
    {
        // Hook up the event handler.
        DWORD dwCookie;
        hr = pfd->Advise(pfde, &dwCookie);
        if (SUCCEEDED(hr))
        {
            // Set the options on the dialog.
            DWORD dwFlags = 0;
            if (SUCCEEDED(hr))
            {
                // the default flag is FOS_PATHMUSTEXIST and FOS_FILEMUSTEXIST, so I set overwrite it with these two flags.
                hr = pfd->SetOptions(dwFlags | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE);
                if (SUCCEEDED(hr))
                {
                    // Show the dialog
                    hr = pfd->Show(NULL);
                    if (SUCCEEDED(hr))
                    {
                        // Obtain the result once the user clicks 
                        // the 'Open' button.
                        // The result is an IShellItem object.
                        IShellItem *psiResult;
                        hr = pfd->GetResult(&psiResult);
                        if (SUCCEEDED(hr))
                        {
                            // We are just going to print out the 
                            // name of the file for sample sake.
                            PWSTR pszFilePath = NULL;
                            hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH,
                                &pszFilePath);
                            if (SUCCEEDED(hr))
                            {
                                TaskDialog(NULL,
                                    NULL,
                                    L"CommonFileDialogApp",
                                    pszFilePath,
                                    NULL,
                                    TDCBF_OK_BUTTON,
                                    TD_INFORMATION_ICON,
                                    NULL);
                                CoTaskMemFree(pszFilePath);
                            }
                            psiResult->Release();
                        }
                    }

                }
            }
            // Unhook the event handler.
            pfd->Unadvise(dwCookie);
        }
        pfde->Release();
    }
    pfd->Release();
}
return hr;
}

The key point is to overwrite the options by SetOptions. Based on the MSDN:https://msdn.microsoft.com/en-us/library/windows/desktop/dn457282(v=vs.85).aspx ,

FOS_PATHMUSTEXIST
The item returned must be in an existing folder. This is a default value.
FOS_FILEMUSTEXIST
The item returned must exist. This is a default value for the Open dialog.

These flags are default values which prevent it returns. So I set it to :

hr = pfd->SetOptions(dwFlags | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE);

These flags mean:

FOS_NOVALIDATE
Do not check for situations that would prevent an application from opening the selected file, such as sharing violations or access denied errors.

and

FOS_ALLNONSTORAGEITEMS
Enables the user to choose any item in the Shell namespace, not just those with SFGAO_STREAM or SFAGO_FILESYSTEM attributes. This flag cannot be combined with FOS_FORCEFILESYSTEM.

Then it finally works!

beasone
  • 1,073
  • 1
  • 14
  • 32