In VC++ 6.0, MFC I want to select a multiple files
CFileDialog opendialog(true); // opens the dialog for open;
opendialog.m_ofn.lpstrTitle="SELECT FILE"; //selects the file title;
opendialog.m_ofn.lpstrFilter="text files (*.txt)\0*.txt\0"; //selects the filter;
if(opendialog.DoModal()==IDOK) //checks wether ok or cancel button is pressed;
{
srcfilename=opendialog.GetPathName(); //gets the path name;
...
}
The code sample above allows only a single file being selected at a time, but I want to select multiple text files, for example by holding down control key (ctrl+select the multiple files). How can I achieve this?
So in the constructor for CFileDialog you can set the dwFlags parameter to have 'OFN_ALLOWMULTISELECT'. Thats the easy part, to actually get the multiple file names back you have to modify the m_ofn.lpstrFile member in the CFileDialog to point to a buffer that you have allocated. Have a look here:
http://msdn.microsoft.com/en-us/library/wh5hz49d(VS.80).aspx
Here is an example use of it, hope the comments suffice:
void CMainFrame::OnFileOpen()
{
char strFilter[] = { "Rule Profile (*.txt)|*.txt*||" };
CFileDialog FileDlg(TRUE, "txt", NULL, OFN_ALLOWMULTISELECT, strFilter);
CString str;
int nMaxFiles = 256;
int nBufferSz = nMaxFiles*256 + 1;
FileDlg.GetOFN().lpstrFile = str.GetBuffer(nBufferSz);
if( FileDlg.DoModal() == IDOK )
{
// The resulting string should contain first the file path:
int pos = str.Find(' ', 0);
if ( pos == -1 );
//error here
CString FilePath = str.Left(pos);
// Each file name is seperated by a space (old style dialog), by a NULL character (explorer dialog)
while ( (pos = str.Find(' ', pos)) != -1 )
{ // Do stuff with strings
}
}
else
return;
}
An example:
CString sFilter = _T("XXX Files (*.xxx)|*.xxx|All Files (*.*)|*.*||");
CFileDialog my_file_dialog(TRUE, _T("xxx"),NULL,
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT,
sFilter, this);
if ( my_file_dialog.DoModal()!=IDOK )
return;
POSITION pos ( my_file_dialog.GetStartPosition() );
while( pos )
{
CString filename= my_file_dialog.GetNextPathName(pos);
//do something with the filename variable
}
You should pass the OFN_ALLOWMULTISELECT flag in OpenFileName structure to allow the multi selection.
Insert this line:
opendialog.m_ofn.Flags |= OFN_ALLOWMULTISELECT;
Or set the flag in the CFileDialog constructor as DeusAduro did.
Related
I'm currently customizing an Explorer-type custom dialog and I want to add a recent file combo box. I have already added the combo box the recent file items. I was also able to map them to their file path. But I'm having problems on changing the working directory of the explorer type dialog. Upon selecting a file on the combo box, I want the directory to change with the corresponding folder location of the selected file. See image attached.
Recent File Combo Box
// Get the index of the selected item in the combo box
const int iCmbIdx = CBGetCurSel(cOPEN_RECENTFILE_CMB);
if (iCmbIdx < 0) break; // Break if it is invalid
int iDefaultFileIndex = -1;
// Key - value pair
// Returns the index of the file saved in registry
int iFileIdx = lIndex.GetValue(iCmbIdx, iDefaultFileIndex);
if (iFileIdx < 0 ) break; // Index not found
// Build the folder path
tchar szItem[LSTR];
tchar szFilePath[LSTR];
tsprintf(szItem, TEXT("File%d"), iFileIdx);
LUserSettingGetString(TEXT("RecentFileList"), szItem, TEXT(""), szFilePath);
if (szFilePath[0] == 0) break;
LFile::GetFolderFromPath(szFilePath, szFilePath);
if (!SetCurrentDirectory(szFilePath)) {
LFDEBUG("SetCurrentDirectory failed (%d)\n", GetLastError());
break;
}
There are no errors in SetCurrentDirectory() call but the dialog will not go to the specified location. Is this the correct API to use to change the location of the folder?
This is the flags I used for the OPENFILENAME structure:
ofn.Flags = OFN_ALLOWMULTISELECT | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_ENABLETEMPLATE | OFN_ENABLEHOOK;
SetCurrentDirectory is too low level, these dialogs use the shell namespace to navigate.
The classic open/save dialog API provides some messages to control it but changing the directory is not one of them. This leaves you with hacks like this:
void SetOpenSaveDlgFolderWithTextHack(HWND hTopDlg, LPCTSTR Path)
{
HWND hCtl = GetDlgItem(hTopDlg, 0x47C); // Combo
if (!hCtl) hCtl = GetDlgItem(hTopDlg, 0x480); // Old Edit
if (hCtl && *Path && (int)(INT_PTR) SendMessage(hCtl, WM_SETTEXT, 0, (SIZE_T) Path) > 0)
{
SendMessage(hTopDlg, WM_COMMAND, IDOK, (SIZE_T) GetDlgItem(hTopDlg, IDOK));
}
}
void SetOpenSaveDlgFolderWithShellBrowserHack(HWND hTopDlg, LPCTSTR Path)
{
UINT cwm_getishellbrowser = WM_USER + 7;
IShellBrowser *pSB, *pUnk = (IShellBrowser*) SendMessage(hTopDlg, cwm_getishellbrowser, 0, 0);
if (pUnk && SUCCEEDED(pUnk->QueryInterface(IID_IShellBrowser, (void**) &pSB)))
{
if (PIDLIST_ABSOLUTE pidl = ILCreateFromPath(Path))
{
pSB->BrowseObject(pidl, SBSP_ABSOLUTE);
ILFree(pidl);
}
pSB->Release();
}
else
{
SetOpenSaveDlgFolderWithTextHack(hTopDlg, Path);
}
}
To find hTopDlg, use OFN_ENABLEHOOK and call GetParent on the HWND given to you in the hook proc for WM_NOTIFY:CDN_INITDONE.
As mentioned by Simon Mourier in the comments, the new dialogs added in Vista allows you to call IFileDialog::SetFolder to change the current folder. The downside of this API is that you don't have full control of the dialog layout and you have to implement a COM object to catch events.
Currently using this code to Copy selected text in currently open Window in Windows 10. This code is working fine if I run it on its own by when my target program (Notepad) has focus. The selected text in notepad is copied into data variable OK.
wchar_t title[MAX_PATH];
HWND target_window = GetForegroundWindow();
GetWindowText(target_window, title, MAX_PATH);
std::wcout << "Target window is '" << title << "'" << std::endl;
// Send Control + C
int key_count = 4;
INPUT* input = new INPUT[key_count];
for (int i = 0; i < key_count; i++)
{
input[i].ki.dwFlags = 0;
input[i].type = INPUT_KEYBOARD;
}
input[0].ki.wVk = VK_CONTROL;
input[0].ki.wScan = MapVirtualKey(VK_CONTROL, MAPVK_VK_TO_VSC);
input[1].ki.wVk = 0x43; // Virtual key code for 'c'
input[1].ki.wScan = MapVirtualKey(0x43, MAPVK_VK_TO_VSC);
input[2].ki.dwFlags = KEYEVENTF_KEYUP;
input[2].ki.wVk = input[0].ki.wVk;
input[2].ki.wScan = input[0].ki.wScan;
input[3].ki.dwFlags = KEYEVENTF_KEYUP;
input[3].ki.wVk = input[1].ki.wVk;
input[3].ki.wScan = input[1].ki.wScan;
if (!SendInput(key_count, (LPINPUT)input, sizeof(INPUT)))
{
// TODO: error handling
}
else
{
// not ideal but not sure of another way to wait for SendInput to complete
Sleep(100);
if (OpenClipboard(NULL))
{
HGLOBAL hglb = GetClipboardData(CF_UNICODETEXT);
LPWSTR lpwstr = (LPWSTR)(GlobalLock(hglb));
std::wstring data(lpwstr);
GlobalUnlock(hglb);
CloseClipboard();
// do something with selected text in data
}
else
{
// TODO: error handling
}
}
However, if I launch the exact same code via Hotkey, it doesn't work:
if (RegisterHotKey(
NULL,
1,
MOD_CONTROL | MOD_ALT | MOD_NOREPEAT,
VK_OEM_2)) // back slash question mark key
{
std::cout << "Hotkey 'Ctrl+Alt+/' registered, using MOD_NOREPEAT flag\n";
}
MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0) != 0)
{
if (msg.message == WM_HOTKEY)
{
std::cout << "WM_HOTKEY received\n";
// Call function to COPY TEXT here
if (RegisterHotKey(
NULL,
1,
MOD_CONTROL | MOD_ALT | MOD_NOREPEAT,
VK_OEM_2)) // back slash question mark key
{
std::cout << "Hotkey 'Ctrl+Alt+/' registered, using MOD_NOREPEAT flag\n";
}
}
}
Now, in both cases, GetWindowText() is showing the title of the program I want to copy text from.
In addition, I wrote a simple test utility to check Ctrl+C is being passed to Window, which it is. It seems like Ctrl+C is being passed, but the copy is not occurring.
Is it possible that Alt is still down because of the hotkey and you are actually sending Ctrl+Alt+C? SendInput inserts the input directly into the global input queue.
You could try setting a timer in response to the hotkey and call GetAsyncKeyState in the timer handler until all modifier keys are up before generating input.
A better alternative would be to use UI Automation instead of a hack like this.
I am writing an MFC C++ application that has a Save As button for saving a .txt file to the disc. With it I am trying to add an extra verification for file overwriting (if a file with the same filename exists, then it should query the user if he wants to overwrite the old file or not). I have tried this with the below code, but it doesn't really work. When I click No on the MessageBox, it should reopen the Save As file dialog, but instead it gives me two errors: the first one is Debug assertion failed, and the second one is Encountered an improper argument. How should I do this better? This is the code:
char strFilter[] = { "Text Files (*.txt)|*.txt|" };
CFileDialog FileDlg(FALSE, CString(".txt"), NULL, 0, CString(strFilter));
while(true)
{
if( FileDlg.DoModal() == IDOK ) // this is the line which gives the errors
{
agendaName = FileDlg.GetFileName(); //filename
agendaPath = FileDlg.GetFolderPath(); //filepath (folders)
if(model->agendaExists(CSToString(agendaPath+TEXT("\\")+agendaName))) // there is another file called the same way
{
if(MessageBox(TEXT("A file with the specified name already exists. Overwrite?"), TEXT("File exists"), MB_YESNO) != 6) // user clicked NO (do not overwrite file)
{
continue;
}
}
model->sendToFile(CSToString(agendaPath+TEXT("\\")+agendaName)); // the file is unique so the agenda named agendaName found at path agendaPath is saved
return;
}
}
It should be mentioned that the errors occur on line 7 and only on the second loop through the while.
CFileDialog can detect itself if a file exists and prompt the user for overwriting.
explicit CFileDialog(
BOOL bOpenFileDialog,
LPCTSTR lpszDefExt = NULL,
LPCTSTR lpszFileName = NULL,
DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
LPCTSTR lpszFilter = NULL,
CWnd* pParentWnd = NULL,
DWORD dwSize = 0
);
Just pass OFN_OVERWRITEPROMPT for the flags.
As for your problem, run in Debugger and when you get that assertion press the Retry button to see where the problem comes from (you'll probably have to look through the call stack also). Maybe you should try putting this in the while loop:
CFileDialog FileDlg(FALSE, CString(".txt"), NULL, 0, CString(strFilter));
You should use the OFN_OVERWRITEPROMPT flag in the constructor. That flag is usually one of the default flags, but you have set your flags to 0. So, if you do:
CFileDialog FileDlg(FALSE, CString(".txt"), NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, CString(strFilter));
if (FileDlg.DoModal() == IDOK)
{
model->sendToFile(CSToString(FileDlg.GetPathName()));
}
It should work. By the way, GetPathName() gets the full path to the selected file, so you don't need to get the folder and the file name in 2 steps.
Try including below line inside the while loop (as first line in while loop)
CFileDialog FileDlg(FALSE, CString(".txt"), NULL, 0, CString(strFilter));
This line is outside the while loop in your code
I have a program and when they drop files into it I want it to get the path show a messagebox "of the path" then delete it. Can anyone shed some light on how to do this?
First of all, you'll need a window that can accept dropped files. This is accomplished by setting the ExStyle of your window to WS_EX_ACCEPTFILES:
//Create a window.
hWnd = CreateWindowEx
WS_EX_ACCEPTFILES, // Extended possibilites for variation.
gsClassName, // Classname.
gsTitle, // Title caption text.
WS_OVERLAPPEDWINDOW, // Default window.
CW_USEDEFAULT, // X Position.
CW_USEDEFAULT, // Y position.
230, // Window starting width in pixils.
150, // Window starting height in pixils.
HWND_DESKTOP, // The window is a child-window to desktop.
(HMENU)NULL, // No menu.
hInstance, // Program Instance handler.
NULL // No Window Creation data.
);
Second, you'll need to handle the WM_DROPFILES message in your WinProc() callback.
if(uMessage == WM_DROPFILES)
{
HDROP hDropInfo = (HDROP)wParam;
char sItem[MAX_PATH];
for(int i = 0; DragQueryFile(hDropInfo, i, (LPSTR)sItem, sizeof(sItem)); i++)
{
//Is the item a file or a directory?
if(GetFileAttributes(sItem) &FILE_ATTRIBUTE_DIRECTORY)
{
//Delete all of the files in a directory before we can remove it.
DeleteDirectoryRecursive(sItem);
}
else {
SetFileAttributes(sItem, FILE_ATTRIBUTE_NORMAL); //Make file writable
//DeleteFile(sItem);
}
}
DragFinish(hDropInfo);
}
Third, you'll need a function that can remove all of the sub-directories and files from any directories that are dropped onto your dialog:
bool DeleteDirectoryRecursive(const char *sDir)
{
WIN32_FIND_DATA fdFile;
HANDLE hFind = NULL;
char sPath[2048];
//Specify a file mask. *.* = We want everything!
sprintf(sPath, "%s\\*.*", sDir);
if((hFind = FindFirstFile(sPath, &fdFile)) == INVALID_HANDLE_VALUE)
{
printf("Path not found: [%s]\n", sDir);
return false;
}
do
{
//Find first file will always return "."
// and ".." as the first two directories.
if(strcmp(fdFile.cFileName, ".") != 0
&& strcmp(fdFile.cFileName, "..") != 0)
{
//Build up our file path using the passed in
// [sDir] and the file/foldername we just found:
sprintf(sPath, "%s\\%s", sDir, fdFile.cFileName);
//Is the entity a File or Folder?
if(fdFile.dwFileAttributes &FILE_ATTRIBUTE_DIRECTORY)
{
DeleteDirectoryRecursive(sPath); //Recursive call.
}
else{
printf("File: %s\n", sPath);
SetFileAttributes(sPath, FILE_ATTRIBUTE_NORMAL);
//DeleteFile(sPath);
}
}
}
while(FindNextFile(hFind, &fdFile)); //Find the next file.
FindClose(hFind); //Always, Always, clean things up!
SetFileAttributes(sDir, FILE_ATTRIBUTE_NORMAL);
//RemoveDirectory(sDir); //Delete the directory that was passed in.
return true;
}
Lastly, you'll need to be VERY CAREFUL with this snippet - it deletes files after all.
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...