I have seen this in many applications. You get the option to open a file and if it does not exists, it is created and you don't get any complain. All from the same Open File dialog.
I use IFileOpenDialog to open a file and if I input a file that does not exists, it shows me an error and I cannot get the path to this file.
What I want is not to get an error but have the non existing file name accepted. Later I will create it. Is this possible?
if (SUCCEEDED(hr))
{
IFileOpenDialog *pFileOpen;
// Create the FileOpenDialog object
hr = CoCreateInstance(
CLSID_FileOpenDialog,
NULL,
CLSCTX_ALL,
IID_IFileOpenDialog,
reinterpret_cast<void**>(&pFileOpen)
);
if (SUCCEEDED(hr))
{
// Show the Open dialog box.
hr = pFileOpen->Show(NULL);
// Get the file name from the dialog box.
if (SUCCEEDED(hr))
{
IShellItem *pItem;
hr = pFileOpen->GetResult(&pItem);
if (SUCCEEDED(hr))
{
PWSTR pszFilePath;
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
// Check if file actually exists
if (SUCCEEDED(hr))
{
// Create file if not found
if (PathFileExists(pszFilePath) != 1)
{
}
CoTaskMemFree(pszFilePath);
}
pItem->Release();
}
}
pFileOpen->Release();
}
CoUninitialize();
}
call IFileDialog::GetOptions to get the default options, remove FOS_PATHMUSTEXIST and FOS_FILEMUSTEXIST bits, then set the new options back with IFileDialog::SetOptions.
Related
When I create a common file dialog on Windows, I can add application specific shortcuts on the left pane with IFileDialog::AddPlace.
The shortcuts are all displayed in a virtual folder called "Application Links". But when I click on "Application Links" the folder seems to be empty showing only "No items match your search.":
Is it possible to display all the shortcuts listed at "Application Links" on the left pane, also in the right pane when I click on "Application Links" itself? And if so, how can I accomplish that?
Here is a minimal working example of the code producing the screenshot above:
#include <windows.h>
#include <shobjidl.h>
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr)) {
IFileDialog *pfd = NULL;
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr)) {
IShellItem *psiShortcut;
// Get ShellItem for "C:\Windows\System32"
hr = SHCreateItemFromParsingName(L"C:\\Windows\\System32", 0, IID_IShellItem, (void**) &psiShortcut);
if (SUCCEEDED(hr)) {
// Add the ShellItem for "C:\Windows\System32" to the file dialogs shortcut list
hr = pfd->AddPlace(psiShortcut, FDAP_BOTTOM);
if (SUCCEEDED(hr)) {
// Open file dialog and show result in a message box on OK
hr = pfd->Show(NULL);
if (SUCCEEDED(hr) && hr != HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
IShellItem *psiFileName;
hr = pfd->GetResult( &psiFileName);
if (SUCCEEDED(hr)) {
PWSTR pwszFileName;
hr = psiFileName->GetDisplayName(SIGDN_FILESYSPATH, &pwszFileName);
if (SUCCEEDED(hr)) {
MessageBoxW(NULL, pwszFileName, L"Note", MB_OK);
CoTaskMemFree( pwszFileName );
}
psiFileName->Release();
}
}
}
psiShortcut->Release();
}
pfd->Release();
}
CoUninitialize();
}
return 0;
}
By using Microsoft windows SDK 7.0, explorerDataProvider, I installed a virtual folder on windows 7.
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:
But when I select the file, like "Zero" can then clicks "Open",
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:
Update:
hr = pfd->SetOptions(dwFlags | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE);
This works for me!!
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!
The platform I am using is windows 7. I need to create shortcut for the virtual folder on windows 7.
I use windows 7 SDK sample to create a virtual folder under Computer:
The sample project name is called ExplorerDataProvider, it defines the CLSID for the IShellFolder class:
// add classes supported by this module here
const CLASS_OBJECT_INIT c_rgClassObjectInit[] =
{
{ &CLSID_FolderViewImpl, CFolderViewImplFolder_CreateInstance },
{ &CLSID_FolderViewImplContextMenu,CFolderViewImplContextMenu_CreateInstance },
};
The definition for CFolderViewImplFolder_CreateInstance is:
HRESULT CFolderViewImplFolder_CreateInstance(REFIID riid, void **ppv)
{
*ppv = NULL;
CFolderViewImplFolder* pFolderViewImplShellFolder = new (std::nothrow) CFolderViewImplFolder(0);
HRESULT hr = pFolderViewImplShellFolder ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = pFolderViewImplShellFolder->QueryInterface(riid, ppv);
pFolderViewImplShellFolder->Release();
}
return hr;
}
And the CFolderViewImplFolder implement IShellFolder2 amd IPersistFolder2.
I found a similar code which is used to create shortcut for printer here:
https://www.codeproject.com/Articles/596642/Creating-a-shortcut-programmatically-in-Cplusplus
and in
https://msdn.microsoft.com/en-us/library/aa969393.aspx#Shellink_Item_Identifiers
Once you have the class identifier for IShellFolder, you can call the CoCreateInstance function to retrieve the address of the interface. Then you can call the interface to enumerate the objects in the folder and retrieve the address of the item identifier for the object that you are searching for. Finally, you can use the address in a call to the IShellLink::SetIDList member function to create a shortcut to the object.
I revised
hr = SHGetMalloc(&pMalloc);
hr = SHGetDesktopFolder( &pDesktopFolder );
hr = SHGetSpecialFolderLocation( NULL, CSIDL_PRINTERS, &netItemIdLst );
hr = pDesktopFolder->BindToObject( netItemIdLst, NULL, IID_IShellFolder, (void **)&pPrinterFolder );
to
// testFolder is the CLSID for the virtual folder implementation
hr = CoCreateInstance(testFolder, NULL, CLSCTX_INPROC_SERVER, IID_IShellFolder, (LPVOID*)&pVirtualFolder);
or
hr = CoCreateInstance(testFolder, NULL, CLSCTX_INPROC_SERVER, IID_IShellFolder2, (LPVOID*)&pVirtualFolder);
But the pVirtualFolder is still NULL, and it prints that "cannot find the corresponding interface".
Is there anything wrong with CoCreateInstance when I use it ? Or I shouldn't use this solution ? Any sample code for it ?
To create a shortcut, you can use the official documentation. Here is a sample code that creates a shortcut for a children of "This PC" (aka: ComputerFolder)
int main()
{
CoInitialize(NULL);
// I've used my Apple's iCloud as an example (name is in french)
// it's a virtual folder, a shell namespace extension
HRESULT hr = CreateComputerChildShortCut(L"Photos iCloud", L"c:\\temp\\my icloud");
printf("hr:0x%08X\n", hr);
CoUninitialize();
return 0;
}
HRESULT CreateComputerChildShortCut(LPWSTR childDisplayName, LPWSTR path)
{
// get My Computer folder's ShellItem (there are other ways for this...)
CComPtr<IShellItem> folder;
HRESULT hr = SHCreateItemInKnownFolder(FOLDERID_ComputerFolder, 0, NULL, IID_PPV_ARGS(&folder));
if (FAILED(hr)) return hr;
// enumerate children
CComPtr<IEnumShellItems> items;
hr = folder->BindToHandler(NULL, BHID_EnumItems, IID_PPV_ARGS(&items));
if (FAILED(hr)) return hr;
for (CComPtr<IShellItem> item; items->Next(1, &item, NULL) == S_OK; item.Release())
{
// get parsing path (if's often useful)
CComHeapPtr<wchar_t> parsingPath;
item->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &parsingPath);
wprintf(L"Path: %s\n", parsingPath);
// get display name
CComHeapPtr<wchar_t> displayName;
item->GetDisplayName(SIGDN_NORMALDISPLAY, &displayName);
wprintf(L" Name: %s\n", displayName);
if (!lstrcmpi(childDisplayName, displayName))
{
// get pidl
// it's the unambiguous way of referencing a shell thing
CComHeapPtr<ITEMIDLIST> pidl;
hr = SHGetIDListFromObject(item, &pidl);
if (FAILED(hr)) return hr;
// create an instance of the standard Shell's folder shortcut creator
CComPtr<IShellLink> link;
hr = link.CoCreateInstance(CLSID_FolderShortcut);
if (FAILED(hr)) return hr;
// just use the pidl
hr = link->SetIDList(pidl);
if (FAILED(hr)) return hr;
CComPtr<IPersistFile> file;
hr = link->QueryInterface(&file);
if (FAILED(hr)) return hr;
// save the shortcut (we could also change other IShellLink parameters)
hr = file->Save(path, FALSE);
if (FAILED(hr)) return hr;
break;
}
}
return S_OK;
}
Of course, if you know an absolute parsing path or an absolute pidl, you don't have to enumerate anything, this was just for demonstration purposes.
I want to open the html file from IE.
So, I use the ShellExecute API like this.
ShellExecute(NULL, L"open", html file path, NULL, NULL, SW_SHOW);
but, My Default Browser is Chrome.
Html file is open to the Chrome new tab.
So, I try like this.
ShellExecute(NULL, L"open", IE file path, html file path, NULL, SW_SHOW);
I set parameter to the html file path.
but, That method is create new window.
I want to open the html file from IE.(new tab!)
Thanks.
Instead of ShellExecute, you have to use an instace of IShellWindows to open a new tab in Internet Explorer. Check the code taken from Warwick Systems to accomplish this:
void OutputToWebBrowser(const CString & path)
{
IShellWindows *psw;
HRESULT hr = CoCreateInstance(CLSID_ShellWindows,NULL,CLSCTX_ALL,IID_IShellWindows,(void**)&psw);
if (!SUCCEEDED(hr))
return;
IWebBrowser2* pBrowser2 = 0;
bool found = false;
long nCount = 0;
hr = psw->get_Count(&nCount);
if (SUCCEEDED(hr))
{
for (long i = nCount - 1; (i >= 0) && (!found); i--) {
// get interface to item no i
_variant_t va(i, VT_I4);
IDispatch * spDisp;
hr = psw->Item(va,&spDisp);
hr = spDisp->QueryInterface(IID_IWebBrowserApp,(void **)&pBrowser2);
if (SUCCEEDED(hr))
{
BSTR name;
pBrowser2->get_FullName(&name);
CString n(name);
if (n.Find("IEXPLORE") == -1)
pBrowser2->Release();
else
found = true;
}
}
psw->Release();
}
if (!found)
hr = CoCreateInstance(CLSID_InternetExplorer, NULL, CLSCTX_LOCAL_SERVER,IID_IWebBrowser2,(void**)&pBrowser2);
if (SUCCEEDED(hr))
{
VARIANT vEmpty;
VariantInit(&vEmpty);
_variant_t URL, Flag, TargetFrameName, PostData, Headers;
Flag.ChangeType(VT_I4, &Flag);
if (found)
Flag.intVal = 0x800;
URL.SetString(path);
hr = pBrowser2->Navigate2(&URL, &Flag, &vEmpty, &vEmpty, &vEmpty);
if (SUCCEEDED(hr))
{
pBrowser2->put_Visible(TRUE);
}
else
{
pBrowser2->Quit();
}
pBrowser2->Release();
}
}
I have a helper method that looks like this:
HRESULT DeleteFolderAndContainedFiles(IShellItem *psiFolder)
{
IFileOperation *pfo;
HRESULT hr = CreateAndInitializeFileOperation(IID_PPV_ARGS(&pfo));
if (SUCCEEDED(hr))
{
hr = pfo->DeleteItem(psiFolder, NULL);
if (SUCCEEDED(hr))
{
hr = pfo->PerformOperations();
}
pfo->Release();
}
return hr;
}
How can I call this method, using IShellItem (which I have no experience with)? I want to pass it a path like C:\data\test and it should clear everything under the test directory but not delete the test directory.
IShellItem* item = NULL;
SHCreateItemFromParsingName(L"C:\\data\\test", NULL, IID_PPV_ARGS(&item));
Since you do not want to delete the specified folder itself, only its contents, you will have to enumerate the folder's contents and delete them individually. Try calling IShellItem::BindToHandler(BHID_EnumItems) to get an IEnumShellItems that represents the contents and pass it to IFileOperation::DeleteItems(), rather than passing the IShellItem itself to IFileOperation::DeleteItem():
hr = psiFolder->BindToHandler(NULL, BHID_EnumItems, IID_IEnumShellItems, (void**)&pEnum);
if (SUCCEEDED(hr))
{
hr = pfo->DeleteItems(pEnum);
if (SUCCEEDED(hr))
{
hr = pfo->PerformOperations();
}
}