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
Related
There are many similar questions, but mine is a bit different. Mine involves a Windows GUI. I am using an open file dialog or an "OPENFILENAME". I want to get the dialog result as OK when the user clicks the OK button and then open a text encoded file. I have done it in Java, but the UI looks weird. So I am not sure whether or not people would like it. I want to learn C++ as well, so I need some help. I have a text box called "hWndEdit" in the WM_COMMAND message inside my program. After opening, the text in the file is supposed to be displayed in the textBox I have specified. I have defined the function in a header file with the following code:
#include <iostream>
#include <fstream>
#include <windows.h>
using namespace std;
void OpenFile()
{
OPENFILENAME ofn; // common dialog box structure
HWND hwnd = nullptr; // owner window
HANDLE hf; // file handle
// Initialize OPENFILENAME
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
// Set lpstrFile[0] to '\0' so that GetOpenFileName does not
// use the contents of szFile to initialize itself.
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = L"NoteRecorder Notes\0(*.recnote)\0Text\0(*.txt)\0";
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
// Display the Open dialog box.
if (GetOpenFileName(&ofn) == TRUE)
{
hf = CreateFile(ofn.lpstrFile,
GENERIC_READ,
0,
(LPSECURITY_ATTRIBUTES)NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
(HANDLE)NULL);
}
}
Then I call it from my WM_COMMAND message:
case WM_COMMAND:
switch (wParam)
{
case 12:
OpenFile();
break;
}
But when the user presses the OK button in the OPENFILENAME, it doesn't read any text from the file. How can I accomplish this?
And I want to write files with a save file dialog. Tell me how to do that as well.
The main purpose of the functions GetOpenFileName and GetSaveFileName is to provide you with the filename that the user selected. However, doing the actual File Input and Output is a different issue. You can use the standard C/C++ library for that or you can use the Windows API functions, such as CreateFile, ReadFile and WriteFile.
Since you seem to already be using the function CreateFile, it would make sense to call ReadFile after verifying that the function call to CreateFile succeeded (i.e. that it did not return INVALID_HANDLE_VALUE). After reading the data, you can add a terminating null character to the data (you should make sure that the memory buffer is large enough) and then pass it to the SetDlgItemText function (if the text box is in a dialog box) or SetWindowText function.
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.
I followed all the instructions that is referred here :
http://help.adobe.com/en_US/robohelp/robohtml/WS5b3ccc516d4fbf351e63e3d11aff59c571-7f43.html
My CMainFrame::HtmlHelp overiden handler looks like this :
void CMainFrame::HtmlHelp(DWORD_PTR dwData, UINT nCmd)
{
// TODO: Add your specialized code here and/or call the base class
CWaitCursor wait;
// Get the path to the Help system
CWinApp* pApp = AfxGetApp();
ASSERT_VALID(pApp);
// Set the path to server-based help
CString csOnlineHelpPath = _T("C:\\Help\\Final\\index.htm");
PrepareForHelp();
// must use top level parent (for the case where m_hWnd is in DLL)
CWnd* pWnd = GetTopLevelParent();
// finally, run the RoboHelp Help engine
if (!RH_ShowHelp(pWnd->m_hWnd, csOnlineHelpPath, nCmd, dwData))
AfxMessageBox(AFX_IDP_FAILED_TO_LAUNCH_HELP);
}
The problem is that the help is never opened. I tried to debug the RoboHelp_CSH.cpp file and I found out that at the line #3267 with the code
MultiByteToWideChar(CP_ACP, MB_USEGLYPHCHARS, szTempFile, (int)uLen, bstr, uLen+1);
the bstr buffer have an extra char at the end that makes the following code
hr=s_pstBrowser->Navigate(bstr, &vFlags, &vTargetFrameName, &vPostData, &vHeaders);
HWND hWnd;
hr=s_pstBrowser->get_HWND((long*)&hWnd);
if (SUCCEEDED(hr))
{
::SetForegroundWindow(hWnd);
}
::SysFreeString(bstr);
}
to fail. The original szTempFile has the data below
C:\Users\sdancer\AppData\Local\Temp\robohelp_csh.htm
and the bstr the the following (the DC2 is the symbol I show inside notepad++, unside VS2008 I see an up and down arrow).
C:\Users\sdancer\AppData\Local\Temp\robohelp_csh.htmDC2
What am I doing wrong here ?
I have a windows application written in C++.
The application generates certain configuration files in a hidden directory.
I want to give user an option to open that directory from my application.
Clicking that option should open a windows explorer like dialog with an input directory location.
I spend time searching for a similar api, but end up with certain dialogs like "DlgDirListComboBoxW" or "GetOpenFileName" or "GetSaveFileName".
I am looking for an api to open normal Windows explorer like Dialog with an input directory location.
It would be really helpful if the api belongs to CommonDialogs section.
You can use the SHBrowseForFolder
It shows a dialog similar to this:
This is a example for how to use it:
BOOL GetFolder(LPCSTR folderpath,
LPCSTR szCaption,
HWND hOwner /*= NULL*/)
{
BOOL retVal = FALSE;
// The BROWSEINFO struct tells the shell
// how it should display the dialog.
BROWSEINFO bi;
memset(&bi, 0, sizeof(bi));
bi.ulFlags = BIF_USENEWUI;
bi.hwndOwner = hOwner;
bi.lpszTitle = szCaption;
// must call this if using BIF_USENEWUI
::OleInitialize(NULL);
// Show the dialog and get the itemIDList for the
// selected folder.
LPITEMIDLIST pIDL = ::SHBrowseForFolder(&bi);
if(pIDL != NULL)
{
// Create a buffer to store the path, then
// get the path.
char buffer[_MAX_PATH] = {'\0'};
if(::SHGetPathFromIDList(pIDL, buffer) != 0)
{
// Set the string value.
folderpath = buffer;
retVal = TRUE;
}
// free the item id list
CoTaskMemFree(pIDL);
}
::OleUninitialize();
return retVal;
}
How about:
HWND hWndOwner = NULL;
ShellExecute(
hWndOwner,
_T("explore"),
_T("c:\\some\\path"),
NULL,
NULL,
SW_SHOWNORMAL);
You can set hWndOwner to your main window handle if you're so inclined and can choose from a variety of other options.
For more information and usage details, check out the MSDN page on ShellExecute.
I've created a program to identify existing maximized windows and log their locations using EnumWindows and GetWindowPlacement.
Assuming I know the location of a desired EXE, I can open it just by calling the external process caller. But what is the best way to identify the newly opened window and set its location? It's safe to assume that a program with the same name may already be open (two instances of cmd, for instance).
I believe once the window is identified I can set its location with SetWindowPos.
Is this the right question to be asking? Is there a way to open a program and receive a HWND handle back instead?
In the Event Hook vein, I have this code:
HWINEVENTHOOK hook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_CREATE, NULL, proc, 0, 0, WINEVENT_OUTOFCONTEXT );
BOOL result = CreateProcess(NULL, szPath, &saProcess, &saThread, FALSE, 0, NULL, NULL, &si, &piProcessC);
if (hook) {
UnhookWinEvent(hook);
}
Which creates a hook and then creates a process (I'm opening up Notepad++).
The proc function it calls is:
void CALLBACK proc(HWINEVENTHOOK hook, DWORD event, HWND hWnd, LONG obj, LONG child, DWORD thr, DWORD time) {
if (IsWindow(hWnd)){
WINDOWPLACEMENT *wp = new WINDOWPLACEMENT();
wp->length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(hWnd, wp);
wp->rcNormalPosition.top = (long) 363;
wp->rcNormalPosition.bottom = (long) 1021;
wp->rcNormalPosition.left = (long) 1444;
wp->rcNormalPosition.right = (long) 2551;
BOOL tmp = SetWindowPlacement(hWnd, wp);
cout << "FOUND IT\n";
}
}
The proc function does not appear to get called, so is the hook not catching anything?
Oh, and I'm not sure the WINEVENT_OUTOFCONTEXT is right, this is just a simple EXE doing the calling.