In MFC, is there an Open Folder Dialog? That is, rather than choosing a filename, it chooses a folder name? Ideally, I'd like it to be the way Visual Studio does it when navigating for a "Project Location" (when creating a new project), which looks very much like a normal file dialog. But I could make do with one of the vertical tree sort of interfaces if the former doesn't exist.
This code will get you a open folder dialog (this was taken from somewhere on the web but I don't really know where).
CString szSelectedFolder = _T("");
// This is the recommended way to select a directory
// in Win95 and NT4.
BROWSEINFO bi;
memset((LPVOID)&bi, 0, sizeof(bi));
TCHAR szDisplayName[_MAX_PATH];
szDisplayName[0] = '\0';
bi.hwndOwner = GetSafeHwnd();
bi.pidlRoot = NULL;
bi.pszDisplayName = szDisplayName;
bi.lpszTitle = _T("Select a folder");
bi.ulFlags = BIF_RETURNONLYFSDIRS;
// Set the callback function
bi.lpfn = BrowseCallbackProc;
LPITEMIDLIST pIIL = ::SHBrowseForFolder(&bi);
TCHAR szReturnedDir[_MAX_PATH];
BOOL bRet = ::SHGetPathFromIDList(pIIL, (TCHAR*)&szReturnedDir);
if (bRet)
{
if (szReturnedDir != _T(""))
{
szSelectedFolder = szReturnedDir;
}
LPMALLOC pMalloc;
HRESULT HR = SHGetMalloc(&pMalloc);
pMalloc->Free(pIIL);
pMalloc->Release();
}
you'll also have to implement this callback function:
TCHAR szInitialDir[_MAX_PATH];
// Set the initial path of the folder browser
int CALLBACK BrowseCallbackProc(HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
// Look for BFFM_INITIALIZED
if (uMsg == BFFM_INITIALIZED)
{
SendMessage(hWnd, BFFM_SETSELECTION, TRUE, (LPARAM)szInitialDir);
}
return 0;
}
Related
How to select an existing folder (or create new) from a native Win32 application?
Here is a similar question. It has a good answer for C#/.NET. But I want the same thing for native Win32.
Anybody knows a solution, free code, etc?
Update:
I tried the function from the answer. Everything worked as expected, except it is necessary to call the SHGetPathFromIDList function to retrieve the name of selected directory. Here is a sample screen shot:
SHBrowseForFolder
Do your users a favor, and set at least the BIF_NEWDIALOGSTYLE flag.
To set the initial folder, add the following code:
static int CALLBACK BrowseFolderCallback(
HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
if (uMsg == BFFM_INITIALIZED) {
LPCTSTR path = reinterpret_cast<LPCTSTR>(lpData);
::SendMessage(hwnd, BFFM_SETSELECTION, true, (LPARAM) path);
}
return 0;
}
// ...
BROWSEINFO binf = { 0 };
...
binf.lParam = reinterpret_cast<LPARAM>(initial_path_as_lpctstr);
binf.lpfn = BrowseFolderCallback;
...
and provide a suitable path (such as remembering the last selection, your applications data folder, or similar)
Just as a go to for future users, this article helped me a lot with getting a directory dialog in C++
http://www.codeproject.com/Articles/2604/Browse-Folder-dialog-search-folder-and-all-sub-fol
Here is my code (heavily based/taken on the article)
NOTE: You should be able to copy/paste this into a file / compile it (g++, see VS in ninja edit below) and it'll work.
#include <windows.h>
#include <string>
#include <shlobj.h>
#include <iostream>
#include <sstream>
static int CALLBACK BrowseCallbackProc(HWND hwnd,UINT uMsg, LPARAM lParam, LPARAM lpData)
{
if(uMsg == BFFM_INITIALIZED)
{
std::string tmp = (const char *) lpData;
std::cout << "path: " << tmp << std::endl;
SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
}
return 0;
}
std::string BrowseFolder(std::string saved_path)
{
TCHAR path[MAX_PATH];
const char * path_param = saved_path.c_str();
BROWSEINFO bi = { 0 };
bi.lpszTitle = ("Browse for folder...");
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
bi.lpfn = BrowseCallbackProc;
bi.lParam = (LPARAM) path_param;
LPITEMIDLIST pidl = SHBrowseForFolder ( &bi );
if ( pidl != 0 )
{
//get the name of the folder and put it in path
SHGetPathFromIDList ( pidl, path );
//free memory used
IMalloc * imalloc = 0;
if ( SUCCEEDED( SHGetMalloc ( &imalloc )) )
{
imalloc->Free ( pidl );
imalloc->Release ( );
}
return path;
}
return "";
}
int main(int argc, const char *argv[])
{
std::string path = BrowseFolder(argv[1]);
std::cout << path << std::endl;
return 0;
}
EDIT: I've updated the code to show people how to remember the last selected path and use that.
Also, for VS, using Unicode character set. replace this line:
const char * path_param = saved_path.c_str();
With this:
std::wstring wsaved_path(saved_path.begin(),saved_path.end());
const wchar_t * path_param = wsaved_path.c_str();
My Test code above is compiled with g++, but doing this fixed it in VS for me.
For Windows Vista and above, it's best to use IFileOpenDialog with the FOS_PICKFOLDERS option for a proper open dialog rather than this tree dialog. See Common Item Dialog on MSDN for more details.
Problem:
If the user holds the "enter" keyboard button and opens OPENFILENAME Save As Dialog, it will automatically save the file - dialog only blinks.
Desired result:
The user holds the "enter" keyboard button, opens OPENFILENAME Save As Dialog, nothing happens. He needs to click on the Save button or click again the "enter" keyboard button to save a file.
My current code:
OPENFILENAME ofn;
TCHAR szFile[260] = { 't','e','s','t'}; // example filename
// Initialize OPENFILENAME
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hWnd;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
//Files like: (ALL - *.*), (Text - .TXT)
ofn.lpstrFilter = _T("All\0*.*\0Text\0*.TXT\0");
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
if (GetSaveFileName(&ofn) == TRUE)
{
// file saved
}
Possible solution:
When ofn.lpstrFile is empty do nothing; Can't save file when there
is no filename
When ofn.lpstrFile has suggested filename then turn off focus on "Save" button or somehow ignore button enter holding.
I was trying to do that but failed, I am a beginner in CPP :(
Thanks for help
The easy solution to prevent data loss is to add the OFN_OVERWRITEPROMPT flag. This does not prevent the issue from happening if the suggested name does not already exist as a file.
To actually interact with the dialog you need OFN_ENABLEHOOK and a hook function. When you receive WM_NOTIFY, you can handle CDN_FILEOK to block the suggested name if not enough time has passed or maybe it is possible to change the focus in CDN_INITDONE.
Either way, you have to be mindful of the fact that you are changing how a common dialog works and this might anger some users.
Here is one way to do it. The actual delay to return the dialog to normal is something you have to decide for yourself.
const int btnid = 1337;
void CALLBACK resetsavedlgdefpush(HWND hWnd, UINT Msg, UINT_PTR idEvent, DWORD Time)
{
KillTimer(hWnd, idEvent);
HWND hDlg = GetParent(hWnd);
UINT id = LOWORD(SendMessage(hDlg, DM_GETDEFID, 0, 0));
if (id == btnid)
{
SendMessage(hDlg, DM_SETDEFID, IDOK, 0);
}
}
UINT_PTR CALLBACK mysavehook(HWND hWndInner, UINT Msg, WPARAM wParam, LPARAM lParam)
{
if (Msg == WM_NOTIFY)
{
OFNOTIFY*pOFN = (OFNOTIFY*) lParam;
if (pOFN->hdr.code == CDN_INITDONE)
{
HWND hDlg = GetParent(hWndInner);
CreateWindowEx(0, TEXT("BUTTON"), 0, BS_DEFPUSHBUTTON|BS_TEXT|WS_CHILD|WS_VISIBLE, 0, 0, 0, 0, hWndInner, (HMENU) btnid, 0, 0);
SendMessage(hDlg, DM_SETDEFID, btnid, 0);
PostMessage(hDlg, DM_SETDEFID, btnid, 0);
int keydelay = 0;
SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &keydelay, 0);
SetTimer(hWndInner, 0, (250 * ++keydelay) * 5, resetsavedlgdefpush);
}
}
return 0;
}
...
ofn.Flags = OFN_PATHMUSTEXIST|OFN_EXPLORER|OFN_OVERWRITEPROMPT|OFN_ENABLESIZING|OFN_ENABLEHOOK;
ofn.lpfnHook = mysavehook;
MessageBox(ofn.hwndOwner, TEXT("Hold enter to test..."), 0, 0);
if (GetSaveFileName(&ofn) == TRUE) ...
I've been trying for a couple of hours to interrogate tooltips to give up the text they contain to no avail. I've found How to get tooltip text for a given HWND? and tried that without success.
This shouldn't be that hard. I'm just not sure what I'm doing wrong. Here's a section of my code:
BOOL CALLBACK EnumWindowsProc(
_In_ HWND hwnd,
_In_ LPARAM lParam
)
{
TCHAR className[200];
GetClassName(hwnd, className, _countof(className));
ASSERT(IsWindow(hwnd));
if (_tcscmp(className, _T("tooltips_class32")) == 0)
{
TOOLINFO ti = { 0 };
ti.cbSize = sizeof(TOOLINFO);
TCHAR text[500] = { 0 };
ti.lpszText = text;
ti.hwnd = GetParent(hwnd);
IsWindow(ti.hwnd);
ti.uId = GetDlgCtrlID(hwnd);
int result = SendMessage(hwnd, TTM_GETTEXT, _countof(text), (LPARAM)&ti);
CString info;
info.Format(_T("%p: %s \"%s\"\r\n"), hwnd, className, ti.lpszText);
CString& output = *(CString*)lParam;
output += info;
}
return 1;
}
void CTooltipVerifyDlg::OnTimer(UINT_PTR nIDEvent)
{
m_output = "";
VERIFY(EnumWindows(EnumWindowsProc, (LPARAM)&m_output));
SYSTEMTIME systemTime;
GetLocalTime(&systemTime);
CString text;
text.Format(_T("%02u:%02u:%02u.%03u\r\n"), systemTime.wHour, systemTime.wMinute, systemTime.wSecond, systemTime.wMilliseconds);
m_output = text + m_output;
this->UpdateData(FALSE);
CDialogEx::OnTimer(nIDEvent);
}
CTooltipVerifyDlg is a dialogue with a text box which I communicate to with m_output, a CString that is bound to the text box.
When the SendMessage call is done, something on my desktop (or even the desktop manager) crashes. Any ideas why it would be crashing and not giving me the text that I desire?
How to select an existing folder (or create new) from a native Win32 application?
Here is a similar question. It has a good answer for C#/.NET. But I want the same thing for native Win32.
Anybody knows a solution, free code, etc?
Update:
I tried the function from the answer. Everything worked as expected, except it is necessary to call the SHGetPathFromIDList function to retrieve the name of selected directory. Here is a sample screen shot:
SHBrowseForFolder
Do your users a favor, and set at least the BIF_NEWDIALOGSTYLE flag.
To set the initial folder, add the following code:
static int CALLBACK BrowseFolderCallback(
HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
if (uMsg == BFFM_INITIALIZED) {
LPCTSTR path = reinterpret_cast<LPCTSTR>(lpData);
::SendMessage(hwnd, BFFM_SETSELECTION, true, (LPARAM) path);
}
return 0;
}
// ...
BROWSEINFO binf = { 0 };
...
binf.lParam = reinterpret_cast<LPARAM>(initial_path_as_lpctstr);
binf.lpfn = BrowseFolderCallback;
...
and provide a suitable path (such as remembering the last selection, your applications data folder, or similar)
Just as a go to for future users, this article helped me a lot with getting a directory dialog in C++
http://www.codeproject.com/Articles/2604/Browse-Folder-dialog-search-folder-and-all-sub-fol
Here is my code (heavily based/taken on the article)
NOTE: You should be able to copy/paste this into a file / compile it (g++, see VS in ninja edit below) and it'll work.
#include <windows.h>
#include <string>
#include <shlobj.h>
#include <iostream>
#include <sstream>
static int CALLBACK BrowseCallbackProc(HWND hwnd,UINT uMsg, LPARAM lParam, LPARAM lpData)
{
if(uMsg == BFFM_INITIALIZED)
{
std::string tmp = (const char *) lpData;
std::cout << "path: " << tmp << std::endl;
SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
}
return 0;
}
std::string BrowseFolder(std::string saved_path)
{
TCHAR path[MAX_PATH];
const char * path_param = saved_path.c_str();
BROWSEINFO bi = { 0 };
bi.lpszTitle = ("Browse for folder...");
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
bi.lpfn = BrowseCallbackProc;
bi.lParam = (LPARAM) path_param;
LPITEMIDLIST pidl = SHBrowseForFolder ( &bi );
if ( pidl != 0 )
{
//get the name of the folder and put it in path
SHGetPathFromIDList ( pidl, path );
//free memory used
IMalloc * imalloc = 0;
if ( SUCCEEDED( SHGetMalloc ( &imalloc )) )
{
imalloc->Free ( pidl );
imalloc->Release ( );
}
return path;
}
return "";
}
int main(int argc, const char *argv[])
{
std::string path = BrowseFolder(argv[1]);
std::cout << path << std::endl;
return 0;
}
EDIT: I've updated the code to show people how to remember the last selected path and use that.
Also, for VS, using Unicode character set. replace this line:
const char * path_param = saved_path.c_str();
With this:
std::wstring wsaved_path(saved_path.begin(),saved_path.end());
const wchar_t * path_param = wsaved_path.c_str();
My Test code above is compiled with g++, but doing this fixed it in VS for me.
For Windows Vista and above, it's best to use IFileOpenDialog with the FOS_PICKFOLDERS option for a proper open dialog rather than this tree dialog. See Common Item Dialog on MSDN for more details.
When I try to instantiate a CFileDialog object it shows both the folders and files. How do you create a CFileDialog that browses for folders alone?
It is very simple, really.
Use CFolderPickerDialog which is derived from the class CFileDialog!
You can't do it with CFileDialog.
Either you will use SHBrowseForFolder Function or a wrapper for it, like CFolderDialog - Selecting Folders.
Starting from Vista it's recommended to use IFileDialog with the FOS_PICKFOLDERS option (see msdn):
CFileDialog od(TRUE/*bOpenFileDialog*/, NULL, NULL,
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT , NULL, NULL, 0,
TRUE/*bVistaStyle*/);
IFileOpenDialog * openDlgPtr = od.GetIFileOpenDialog();
if ( openDlgPtr != NULL )
{
openDlgPtr->SetOptions(FOS_PICKFOLDERS);
openDlgPtr->Release();
}
od.DoModal();
Like someone mentioned, use CFolderPickerDialog which works great. I would like to give you example how to use it especially when using the multi select flag:
CFolderPickerDialog folderPickerDialog(initialFolder, OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_ENABLESIZING, this,
sizeof(OPENFILENAME));
CString folderPath;
if (folderPickerDialog.DoModal() == IDOK)
{
POSITION pos = folderPickerDialog.GetStartPosition();
while (pos)
{
folderPath = folderPickerDialog.GetNextPathName(pos);
}
}
starting from windows vista,you can use the Common Item Dialog .
void CQiliRegrvDlg::OnBnClickedSelectDir()
{
HRESULT hr = S_OK;
// Create a new common open file dialog.
IFileOpenDialog *pfd = NULL;
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
// Set the dialog as a folder picker.
DWORD dwOptions;
hr = pfd->GetOptions(&dwOptions);
if (SUCCEEDED(hr))
{
hr = pfd->SetOptions(dwOptions | FOS_PICKFOLDERS);
}
// Set the title of the dialog.
if (SUCCEEDED(hr))
{
hr = pfd->SetTitle(L"Folder");
}
// Show the open file dialog.
if (SUCCEEDED(hr))
{
hr = pfd->Show(m_hWnd);
if (SUCCEEDED(hr))
{
// Get the selection from the user.
IShellItem *psiResult = NULL;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr))
{
PWSTR pszPath = NULL;
hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
if (SUCCEEDED(hr))
{
m_appDir = pszPath;
SetDlgItemText(IDC_STATIC, m_appDir);
CoTaskMemFree(pszPath);
}
psiResult->Release();
}
}
}
pfd->Release();
}
}
Seems to me the answer you are asking for is inside the code of
CMFCPropertyGridFileProperty::OnClickButton(CPoint /*point*/)
of the
<Your Visual Studio installation folder>\VC\atlmfc\src\mfc\afxpropertygridctrl.cpp
file.
If you do not have access to the code, I will post the essential part of it:
CString strPath = m_varValue.bstrVal;
BOOL bUpdate = FALSE;
if (m_bIsFolder)
{
if (afxShellManager == NULL)
{
CWinAppEx* pApp = DYNAMIC_DOWNCAST(CWinAppEx, AfxGetApp());
if (pApp != NULL)
{
pApp->InitShellManager();
}
}
if (afxShellManager == NULL)
{
ASSERT(FALSE);
}
else
{
bUpdate = afxShellManager->BrowseForFolder(strPath, m_pWndList, strPath);
}
}
else
{
CFileDialog dlg(m_bOpenFileDialog, m_strDefExt, strPath, m_dwFileOpenFlags, m_strFilter, m_pWndList);
if (dlg.DoModal() == IDOK)
{
bUpdate = TRUE;
strPath = dlg.GetPathName();
}
}
As you see, Microsoft itself does not use the Cfiledialog class when wants to open a dialog for picking folders.
For using code like that, your application class MUST be derived from CWinAppEx, not CWinApp
Actually there is a way to do this - I found it in codeguru: "Selected files and folders in CFileDialog"
If you are willing to make your own implementation of CFileDialog such as:
class CMyFileDialog : public CFileDialog
You can add the following code and it should work (It is slightly different from the codeguru example):
// This code overrides the OnNotify message of the CFileDialog
// and catches the CDN_SELCHANGE, this way you can also do
// something with the selected folders.
BOOL CMyFileDialog::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
NMHDR* pNotificationParam = (NMHDR*)lParam;
// Check that we got to the selection change notification.
int code = pNotificationParam->code;
if (code == CDN_SELCHANGE)
{
CStringArray theSelection;
GetListControllSelectedItems(theSelection);
// Do as you want with theSelection.
}
return CFileDialog::OnNotify(wParam, lParam, pResult);
}
// The following Code is accessing the selection in the CFileDialog
// and filling a string array with the selected names
BOOL CMyFileDialog::GetListControllSelectedItems(CStringArray& selectedItemNames)
{
BOOL rc = FALSE;
// Get the list control of the file dialog.
CWnd* pParentWnd = GetParent();
CWnd* pListControlWnd = pParentWnd->GetDlgItem(lst2);
if (pListControlWnd) {
// Get the selection from the list control.
CListCtrl* pListCtrl = (CListCtrl*)(pListControlWnd->GetDlgItem(1));
UINT selectionCount = pListCtrl->GetSelectedCount();
// When there are items selected.
if (selectionCount) {
rc = TRUE;
selectedItemNames.RemoveAll();
POSITION itemPos = pListCtrl->GetFirstSelectedItemPosition();
while (itemPos != NULL)
{
int itemNum = pListCtrl->GetNextSelectedItem(itemPos);
CString currentItemName = pListCtrl->GetItemText(itemNum, 0);
selectedItemNames.Add(currentItemName);
}
}
}
return rc;
}
Note: In CFileDialog::OnFileNameChange of the Microsoft MFC documentation they actually do hint toward this solution, but without elaborating too much.
I had a problem in my very old, legacy code, where I have a customized file dialog that actually needs to save a folder!!!
After twenty two years of hardship and pain, my code is now complete...