Issues with CFileDialog for multiple file selection - c++

I'm using the following code to retrieve multiple file selection via UI:
CFileDialog fd(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,
NULL, hParentWnd ? CWnd::FromHandle(hParentWnd) : NULL);
fd.m_pOFN->Flags |= OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_NODEREFERENCELINKS;
int nLnBuff = 32767;
TCHAR* pBuffFileSelect = new TCHAR[nLnBuff];
memset(pBuffFileSelect, 0, nLnBuff * sizeof(TCHAR));
fd.m_ofn.lpstrFile = pBuffFileSelect;
fd.m_ofn.nMaxFile = nLnBuff;
if(fd.DoModal() == IDOK)
{
POSITION fileNamesPosition = fd.GetStartPosition();
while(fileNamesPosition != NULL)
{
CString strSelPath = fd.GetNextPathName(fileNamesPosition);
TRACE("path: %s\n", CStringA(strSelPath));
}
}
delete[] pBuffFileSelect;
So when I try it on my PC, I run the method above and when the "Open File" dialog open up, just as a test, I navigated to my desktop and selected all files using Ctrl+A shortcut and then clicked Open. As a result I started getting the following paths:
The first path is a link, which is correct (it exists on my Public desktop):
"C:\Users\Public\Desktop\avp.lnk"
But then the second path is wrong. It gives me:
"C:\Users\Public\Desktop\1.txt"
when it's supposed to be (for the desktop that I picked):
"C:\Users\UserName\Desktop\1.txt"
and then every consecutive path has "Public" instead of "UserName".
I should point out that I have several user accounts set up on this PC and the one that I'm testing this method from is a Standard user account. The app that I'm running this method from is not running elevated (or with regular user privileges) so it should not have access to other user accounts anyway.
So what am I doing wrong here?

Checked the sources, and GetOpenFileName assumes that all the items are in fact in the same file path. This isn't true for the Desktop (there are items in different paths merged into one shell view), and so you'll see the bad behavior.
The solution is to use the Common Item dialogs, which use the shell namespace rather than file system paths. All the desktop items are in a common shell path, and then you can use IShellItem::GetDisplayName to convert to a file system path.
Unfortunately, MFC doesn't have a wrapper for the common item dialog, so you'll have to manage that yourself.

Related

IFileDialog Overwrite File and Open Folder/File

I have two issues related to IFileSaveDialog & IFileOpenDialog I did not managed to find a solution for, I hope you can help me.
When the user is saving a file with an existing name, the "Confirm Save As" prompt appears. I need the "Yes" option to be marked as default instead of "No".
The User can open/load a file or a folder from the same dialog.
Can these be done with this API? Or maybe other API?
I tried to google it and go over Microsoft documentation to find a solution, but with no luck.
When the user is saving a file with an existing name, the "Confirm Save As" prompt appears. I need the "Yes" option to be marked as
default instead of "No".
As #Remy Lebeau said that, you can use IFileDialogEvents::OnOverwrite method. And use MessageBox to create a suitable dialog.
Some code:
IFACEMETHODIMP OnOverwrite(IFileDialog* , IShellItem*psi, FDE_OVERWRITE_RESPONSE* response) {
int msgboxID = MessageBox(
NULL,
(LPCWSTR)L"Windows already exists, \n\rDo you want to replace it?",
(LPCWSTR)L"Confirm Save As",
MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON1
);
switch (msgboxID)
{
case IDYES:
*response = FDEOR_ACCEPT;
break;
case IDNO:
*response = FDEOR_REFUSE;
break;
}
return S_OK;
}
Debug:
The User can open/load a file or a folder from the same dialog. Can
these be done with this API? Or maybe other API?
There is no such method in the document, you can only select a folder or file.
As a compromise, I suggest that you can create a new MessageBox and set two buttons in it, namely "select folders" and "select files". When the user selects folders, folder dialog with the FOS_PICKFOLDERS style is opened. Otherwise, Files is selected by default.

CFileDialog with realtive path

I currently maintenance an old MFC application and have problems with opening file dialogs. The Program has multiple different parts were the user select files for loading, eg sound, video and other program specific formats.
Opening a dialog should always open in a "specific" folder, depending on the file ending. Giving an directory path that contains "..\" will accept and the Dialog opens with the "last selected file".
CString fileDirectory = myHelper.getPath();
// fileDirectory is now "C:\coding\svn\source\MyProgram\..\..\bin\..\data\..\Audio\"
CFileDialog FileDialog(true, _T("MP3;WAV"), _T(fileDirectory), OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, _T("All music files (*.WAV;*.MP3)));
if (FileDialog.DoModal() == IDOK)
{ ... }
I use different CDialog classes (about 15, eg for editing audiofiles, for videofiles) and they all have similar code for opening dialogs like above.
How can i support the relative paths for the CFileDialog?
The CFileDialog supports setting up initial/default folder. Here is the code snippet that demonstrates how to use it:
const TCHAR szFilter[] = _T("Parameter Files (*.npf)|*.npf|All Files (*.*)|*.*||");
CFileDialog dlg(TRUE, _T("npf"), NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, szFilter, this);
CString sParametersDir(CUtility::GetParametersDir());
dlg.m_ofn.lpstrInitialDir = sParametersDir.GetBuffer(_MAX_PATH);
if(dlg.DoModal() == IDOK)
{
m_ParametersFileEdit.SetWindowText(dlg.GetPathName());
}
sParametersDir.ReleaseBuffer();
Also regarding your code. There is no need to use _T() macro for CString objects. The CString class does support UNICODE automatically. The _T() macro should only be used for string literals.
You can use CPath class to normalize file path.
CPath path(sPath);
path.AddBackslash();
path.Append(_T("Config"));
path.Canonicalize();

I want to restart my application on restart of the system

I use GetModuleFileName to get the absolute path to my application, open the RunOnce registry key using RegOpenKeyEx and set a value using RegSetValueEx.
if (RegOpenKeyEx (HKEY_CURRENT_USER,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce",0, KEY_SET_VALUE, &hk1) == ERROR_SUCCESS)
{
RegSetValueEx(hk1, // subkey handle
"", // value name
0, // must be zero
REG_SZ, // value type
(LPBYTE) &path, sizeof(DWORD)); // length of value data
RegCloseKey(hk1);
}
However my application does not start after a system restart.
There are a few methods:
Place your application in your start-up folder. This is a very easy method. When your system (PC) will be restarted, the application will get started (You need to login for this);
Use windows task planner;
Make the application an service.
I prefer the last option if it always needs to run. But you will need to add service handling.
You can create a task using Task Scheduler to run your application when the computer starts.
Open Task Scheduler by clicking the Start button , clicking Control Panel, clicking System and Security, clicking Administrative Tools, and then double-clicking Task Scheduler.‌ If you're prompted for an administrator password or confirmation, type the password or provide confirmation.
Click the Action menu, and then click Create Basic Task.
Type a name for the task and an optional description, and then click Next.
Click When the computer starts, and then click Next.
To schedule a program to start automatically, click Start a program, and then click Next.
Click Browse to find the program you want to start, and then click Next.
Select the Open the Properties dialog for this task when I click Finish check box and click Finish.
In the Properties dialog box, select Run whether user is logged on or not, and then click OK
Source:
Windows 7 - Schedule a task
PS: You must be logged on as an administrator to perform these steps
There are a number of things to keep in mind when using the solution you opted for:
The application does not start when the system starts but rather when the current user logs on.
If you write to the RunOnce key the operation will be performed only once. If you want your application to always start when the user logs on you should instead use the Run key.
In addition to the above, if you want to create a value you will have to give it a name. From the documentation of the lpValueName parameter for RegSetValueEx:
If lpValueName is NULL or an empty string, "", the function sets the type and data for the key's unnamed or default value.
The default (unnamed) value is the one that shows up as (Default) when using regedit. To get this to work you will have to provide a name for the value. This should be unique so that it does not conflict with other values under that key.
On a less technical note, implementing an auto-start feature for an application should only be done after thorough consideration.
You are passing the wrong parameter values to RegSetValueEx(). You need to use it like this instead:
TCHAR path[MAX_PATH+1] = {0}
GetModuleFileName(NULL, path, MAX_PATH);
if (RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce"), 0, KEY_SET_VALUE, &hk1) == ERROR_SUCCESS)
{
RegSetValueEx(hk1, // subkey handle
TEXT("MyApp"), // value name
0, // must be zero
REG_SZ, // value type
(LPBYTE) path,
(lstrlen(path)+1) * sizeof(TCHAR)); // length of value data, in bytes
RegCloseKey(hk1);
}

Windows LoadMenu error: "The specified resource name cannot be found in the image file."

I'm writing a program that, among other things, needs to display a context menu on right-click. I'm trapping WM_NOTIFY, the identifier of the control being clicked on, and NM_RCLICK. That all works great.
The problem comes in when I'm processing that right-click:
case NM_RCLICK:
{
HMENU Popup = LoadMenu(0, MAKEINTRESOURCE(IDR_NED_MENU));
if ( !Popup ) {
DWORD err = GetLastError();
char* buf;
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, err, 0, buf, 1<<19, 0);
_ERROR("LoadMenu(0, MAKEINTRESOURCE(IDR_NED_MENU)); Error '%s' thrown; no menu loaded.", buf);
delete [] buf;
}
Popup = GetSubMenu(Popup, 0);
CheckMenuItem(Popup, 1, MF_CHECKED|MF_BYPOSITION);
POINT Point;
GetCursorPos(&Point);
switch (TrackPopupMenu(Popup, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD, Point.x, Point.y, 0, GetActiveWindow(), NULL)) {
// ...
Primarily, LoadMenu(0, MAKEINTRESOURCE(IDR_NED_MENU)); is returning NULL, and I'm getting an error message that states that "The specified resource name cannot be found in the image file."
Now, IDR_NED_MENU is the ID of a menu I have in the .rc file, and I've included the corresponding .rc.h file in this .cpp file. The actual dialog window IDs contained in the same .rc file work perfectly. This code is further copied and pasted from another project where the LoadMenu call worked perfectly: I did recreate the IDR_NED_MENU from scratch, though, and the IDs are somewhat different (but they do match between the .rc file and the .cpp file that has the code snippet I've pasted here); originally I'd accidentally created the menu in a separate .rc file so I sought to rectify that here. I noticed that in Visual Studio's Resource View, the dialogs are contained in the Dialog folder, while this is contained in the Menu folder (sensible), but I'm not sure what, if any, difference that makes.
Why would I be getting this error? Why can't it find IDR_NED_MENU?
I'm using Visual Studio 2010, and this is not an MFC project. I'm not sure what, if any, other relevant details I should include; let me know in comments and I'll edit-update.
Thanks.
The first parameter to LoadMenu must be a handle to your executable image where the resource resides. The handle is the first HINSTANCE that you get in WinMain. Alternatively you can obtain it by a call to GetModuleHandle(0).

How can I set the default file type for a CFileDialog?

I am using CFileDialog for displaying the open file dialog. I have set the filter as follows:
static TCHAR BASED_CODE szFilter[] = _T("Chart Files (*.xlc)|*.xlc|")
_T("Worksheet Files (*.xls)|*.xls|Data Files (*.xlc;*.xls)|")
_T("*.xlc; *.xls|All Files (*.*)|*.*||");
I need to set the default file type to be "Worksheet Files" whenever I DoModal the dialog box. I am unable to figure out how to do it. MS Paint is doing, it selects the "All Picture files" when we open the open file dialog.
Please let me know how to do it.
You're looking for the SetDefExt function. This allows you to specify the default file extension for an open/save file dialog box. Remember that the string you specify should not contain a period (.).
Of course, you could also just specify this in the constructor. The second parameter is the default extension (lpszDefExt).
You should read and write
This code will do the job during the run time of your program. To be able to display the last used selection next time you run your program, you can store the value of LastIndex in the Registry.
// A dialog box with several filters for various media file types
static int LastIndex = -1; // Holds the last used filter. You can store it in the Registry to use it during next run.
const TCHAR szFilter[] = _T("Video Files (*.mpg, *.mov, *.mp4)|*.mpg;*.mov;*.mp4|Audio Files (*.wav, *.mp3, *.m4a, *.flac)|*.wav;*.mp3;*.m4a;*.flac|MXF Files (*.mxf)|*.mxf|All Files (*.*)|*.*||");
CFileDialog dlg(TRUE, _T("Select Media File"), NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, szFilter, this);
if(LastIndex != -1) dlg.m_ofn.nFilterIndex = LastIndex; // restore last used index
// from last time
if (dlg.DoModal() == IDOK)
{
LastIndex = dlg.m_ofn.nFilterIndex; // Store last used index for next time
CString sFilePath = dlg.GetPathName();
}