C++/Win32: Working With Files
March 2, 2008
If you’ve made it this far, you’ve been introduced to some pretty hectic stuff. The nice thing about that is that by comparison, working with files is going to seem ridiculously easy. And part of that is because Microsoft has done a lot of the hard work for you. And why shouldn’t they? Almost every program you use works with files in one way or another.
Our Port Scanner will have an option to allow us to save our results to disk. There are a few common dialogs we can use for this.
File Dialogs
To open or save a file, first we need a filename. And to get it, we can use two useful functions: GetOpenFileName() and GetSaveFileName(). We just need to pass in a OPENFILENAME structure to each. Guess what they do? They present the user with a dialog asking where they want to open/save from/to, and return the full path to the file the user selected. You’ve seen these dialogs at least, oh, one or two times. Or 5 million, something like that.
Here’s how we use them:
OPENFILENAME ofn;
char szFileName[80] = "";
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = "Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0";
ofn.lpstrFile = szFileName;
ofn.nMaxFile = 80;
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
ofn.lpstrDefExt = "txt";
if (GetOpenFileName(&ofn)) {
// use the szFileName field of ofn to do something
}
We’ve seen the delare struct, pass struct, use struct in 10 different places now, so this shouldn’t surprise you. What we are doing is passing in a HWND (our main app window, probably), a filter list, a default file name, a length limit for the string we can receive, some flags, and a default extension. The coolest of these, and probably the only one that needs explaining, is the lpstrFilter field, in which we specify a filter list.
The filter is that little drop down box in the standard file dialog that lets you limit the files you see in the current drive to relevant results. For example, if you choose Reason Project Files (*.rsn), you’ll expect to see only files with the .rsn extension. The way we do this is by concatenating pairs of strings, with each string terminated by a \0. The first string is a descriptor — what the user sees — and the second string is the limiter — what Window’s uses to actually filter results. So if we want to only provide the option to find .rsn files, we might use the following:
ofn.lpstrFilter = "Reason Project Files (*.rsn)\0*.rsn\0";
Easy enough? After filling in the struct, pass it to GetOpenFileName and then analyze the szFileName field. Use it to get a handle to the file.
There are several flags you can use in the Flags field, and since this lesson will be incredibly short, I’ll list them all here. Some of these flags are set by you, and some are set by the GetSaveFileName function prior to returning. This list is simply taken from the Win32 docs:
OFN_ALLOWMULTISELECT
Specifies that the File Name list box allows multiple selections. If you also set the OFN_EXPLORER flag, the
dialog box uses the Explorer-style user interface; otherwise, it uses the old-style user interface.
If the user selects more than one file, the lpstrFile buffer returns the path to the current directory followed by
the file names of the selected files. The nFileOffset member is the offset, in bytes or characters, to the first
file name, and the nFileExtension member is not used. For Explorer-style dialog boxes, the directory and file name
strings are NULL separated, with an extra NULL character after the last file name. This format enables the
Explorer-style dialog boxes to return long file names that include spaces. For old-style dialog boxes, the
directory and file name strings are separated by spaces and the function uses short file names for file names with
spaces. You can use the FindFirstFile function to convert between long and short file names.
If you specify a custom template for an old-style dialog box, the definition of the File Name list box must
contain the LBS_EXTENDEDSEL value.
OFN_CREATEPROMPT
If the user specifies a file that does not exist, this flag causes the dialog box to prompt the user for
permission to create the file. If the user chooses to create the file, the dialog box closes and the function
returns the specified name; otherwise, the dialog box remains open. If you use this flag with the
OFN_ALLOWMULTISELECT flag, the dialog box allows the user to specify only one nonexistent file.
OFN_DONTADDTORECENT
Windows 2000/XP: Prevents the system from adding a link to the selected file in the file system directory that
contains the user’s most recently used documents. To retrieve the location of this directory, call the
SHGetSpecialFolderLocation function with the CSIDL_RECENT flag.
OFN_ENABLEHOOK
Enables the hook function specified in the lpfnHook member.
OFN_ENABLEINCLUDENOTIFY
Windows 2000/XP: Causes the dialog box to send CDN_INCLUDEITEM notification messages to your OFNHookProc hook
procedure when the user opens a folder. The dialog box sends a notification for each item in the newly opened
folder. These messages enable you to control which items the dialog box displays in the folder’s item list.
OFN_ENABLESIZING
Windows 2000/XP, Windows 98/Me: Enables the Explorer-style dialog box to be resized using either the mouse or the
keyboard. By default, the Explorer-style Open and Save As dialog boxes allow the dialog box to be resized
regardless of whether this flag is set. This flag is necessary only if you provide a hook procedure or custom
template. The old-style dialog box does not permit resizing.
OFN_ENABLETEMPLATE
Indicates that the lpTemplateName member is a pointer to the name of a dialog template resource in the module
identified by the hInstance member. If the OFN_EXPLORER flag is set, the system uses the specified template to
create a dialog box that is a child of the default Explorer-style dialog box. If the OFN_EXPLORER flag is not set,
the system uses the template to create an old-style dialog box that replaces the default dialog box.
OFN_ENABLETEMPLATEHANDLE
Indicates that the hInstance member identifies a data block that contains a preloaded dialog box template. The
system ignores lpTemplateName if this flag is specified. If the OFN_EXPLORER flag is set, the system uses the
specified template to create a dialog box that is a child of the default Explorer-style dialog box. If the
OFN_EXPLORER flag is not set, the system uses the template to create an old-style dialog box that replaces the
default dialog box.
OFN_EXPLORER
Indicates that any customizations made to the Open or Save As dialog box use the new Explorer-style customization
methods. For more information, see Explorer-Style Hook Procedures and Explorer-Style Custom Templates.
By default, the Open and Save As dialog boxes use the Explorer-style user interface regardless of whether this
flag is set. This flag is necessary only if you provide a hook procedure or custom template, or set the
OFN_ALLOWMULTISELECT flag.
If you want the old-style user interface, omit the OFN_EXPLORER flag and provide a replacement old-style template
or hook procedure. If you want the old style but do not need a custom template or hook procedure, simply provide a
hook procedure that always returns FALSE.
OFN_EXTENSIONDIFFERENT
Specifies that the user typed a file name extension that differs from the extension specified by lpstrDefExt. The
function does not use this flag if lpstrDefExt is NULL.
OFN_FILEMUSTEXIST
Specifies that the user can type only names of existing files in the File Name entry field. If this flag is
specified and the user enters an invalid name, the dialog box procedure displays a warning in a message box. If
this flag is specified, the OFN_PATHMUSTEXIST flag is also used.
OFN_FORCESHOWHIDDEN
Windows 2000/XP: Forces the showing of system and hidden files, thus overriding the user setting to show or not
show hidden files. However, a file that is marked both system and hidden is not shown.
OFN_HIDEREADONLY
Hides the Read Only check box.
OFN_LONGNAMES
For old-style dialog boxes, this flag causes the dialog box to use long file names. If this flag is not specified,
or if the OFN_ALLOWMULTISELECT flag is also set, old-style dialog boxes use short file names (8.3 format) for file
names with spaces. Explorer-style dialog boxes ignore this flag and always display long file names.
OFN_NOCHANGEDIR
Restores the current directory to its original value if the user changed the directory while searching for files.
Windows NT 4.0/2000/XP: This flag is ineffective for GetOpenFileName.
OFN_NODEREFERENCELINKS
Directs the dialog box to return the path and file name of the selected shortcut (.LNK) file. If this value is not
specified, the dialog box returns the path and file name of the file referenced by the shortcut.
OFN_NOLONGNAMES
For old-style dialog boxes, this flag causes the dialog box to use short file names (8.3 format). Explorer-style
dialog boxes ignore this flag and always display long file names.
OFN_NONETWORKBUTTON
Hides and disables the Network button.
OFN_NOREADONLYRETURN
Specifies that the returned file does not have the Read Only check box selected and is not in a write-protected
directory.
OFN_NOTESTFILECREATE
Specifies that the file is not created before the dialog box is closed. This flag should be specified if the
application saves the file on a create-nonmodify network share. When an application specifies this flag, the
library does not check for write protection, a full disk, an open drive door, or network protection. Applications
using this flag must perform file operations carefully, because a file cannot be reopened once it is closed.
OFN_NOVALIDATE
Specifies that the common dialog boxes allow invalid characters in the returned file name. Typically, the calling
application uses a hook procedure that checks the file name by using the FILEOKSTRING message. If the text box in
the edit control is empty or contains nothing but spaces, the lists of files and directories are updated. If the
text box in the edit control contains anything else, nFileOffset and nFileExtension are set to values generated by
parsing the text. No default extension is added to the text, nor is text copied to the buffer specified by
lpstrFileTitle. If the value specified by nFileOffset is less than zero, the file name is invalid. Otherwise, the
file name is valid, and nFileExtension and nFileOffset can be used as if the OFN_NOVALIDATE flag had not been
specified.
OFN_OVERWRITEPROMPT
Causes the Save As dialog box to generate a message box if the selected file already exists. The user must confirm
whether to overwrite the file.
OFN_PATHMUSTEXIST
Specifies that the user can type only valid paths and file names. If this flag is used and the user types an
invalid path and file name in the File Name entry field, the dialog box function displays a warning in a message
box.
OFN_READONLY
Causes the Read Only check box to be selected initially when the dialog box is created. This flag indicates the
state of the Read Only check box when the dialog box is closed.
OFN_SHAREAWARE
Specifies that if a call to the OpenFile function fails because of a network sharing violation, the error is
ignored and the dialog box returns the selected file name. If this flag is not set, the dialog box notifies your
hook procedure when a network sharing violation occurs for the file name specified by the user. If you set the
OFN_EXPLORER flag, the dialog box sends the CDN_SHAREVIOLATION message to the hook procedure. If you do not set
OFN_EXPLORER, the dialog box sends the SHAREVISTRING registered message to the hook procedure.
OFN_SHOWHELP
Causes the dialog box to display the Help button. The hwndOwner member must specify the window to receive the
HELPMSGSTRING registered messages that the dialog box sends when the user clicks the Help button. An
Explorer-style dialog box sends a CDN_HELP notification message to your hook procedure when the user clicks the
Help button.
Yeah, that’s a lot of flags. But you see that Microsoft is doing everything they can to preven you from ever having to write one of these yourself. Of course, that doesn’t mean you won’t ever have to… what if you want this dialog to match a skin?
So what we’ll do first in our app is add a Save feature, accessible from the menu in unskinned mode, and from a button in skinned mode. It will ask the user where he’d like to save, confirm overwrite if the file already exists, and then dump the contents of the text output edit control into the file. We’ll handle all of this functionality in a function called SaveOutput, which will be called from the message handler.
Here’s the function:
BOOL PortScannerWindow::SaveOutput() {
// popup dialog to get filename to save to
OPENFILENAME ofn;
char szFileName[100] = "";
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = m_hWnd;
ofn.lpstrFilter = "Glowdot Data Files (*.gdt)\0*.gdt\0Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0";
ofn.lpstrFile = szFileName;
ofn.nMaxFile = 100;
ofn.Flags = OFN_EXPLORER | OFN_HIDEREADONLY;
ofn.lpstrDefExt = "gdt";
if (GetSaveFileName(&ofn)) {
// We've got a file name. See if it exists
HANDLE hFile;
hFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
// First handle the case where the file already exists
if (hFile == INVALID_HANDLE_VALUE) {
// Ask permission to overwrite
if (MessageBox(m_hWnd,"The file you selected already exists. Overwrite?","File Exists",
MB_OKCANCEL) == IDOK)
hFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
// If permission denied, get out of here
else {
CloseHandle(hFile);
return FALSE;
}
}
// If we're still here, file should have been created
// Write contents of text output box to this file
// Double check that file was created
if(hFile != INVALID_HANDLE_VALUE) {
DWORD dwTextLength;
dwTextLength = GetWindowTextLength(m_hDisplayArea);
// Do nothing if box is empty
if(dwTextLength > 0) {
LPSTR pszText;
DWORD dwBufferSize = dwTextLength + 1;
pszText = (LPSTR)GlobalAlloc(GPTR, dwBufferSize);
if(pszText != NULL) {
if(GetWindowText(m_hDisplayArea, pszText, dwBufferSize)) {
DWORD dwWritten;
if(WriteFile(hFile, pszText, dwTextLength, &dwWritten, NULL))
MessageBox(m_hWnd,"Results Saved Successfully","Save",MB_OK);
else
MessageBox(m_hWnd,"A file error occurred!","Error!",MB_OK);
}
GlobalFree(pszText);
}
}
CloseHandle(hFile);
return TRUE;
} else {
MessageBox(m_hWnd,"Write operation failed.","File Error!",MB_OK);
return FALSE;
}
} // Else no file selected
}
You’ll notice there’s a lot more error checking here than I usually do, and that’s because you have to be especially smart with files, lest you go overwriting important stuff. What I’ve done here is try to open the file using the CREATE_NEW flag. CreateFile will fail if the file exists with this flag set, so we’ll know to ask the user if he wants to overwrite. If not, we’ll exit the program gracefully, but if so, we’ll attempt CreateFile again with the CREATE_ALWAYS flag set. Then we check the handle to make sure it is valid, and if so, grab the text from our edit control, and write it to the file with a call to WriteFile. Quite simple, really. Opening files, of course, will be the same, only we’ll get the file name to open with a call to GetOpenFileName. I won’t list it here, because it really is exactly the same, only in reverse. See the source for the function listing.
I will also add an accelerator for the save function, and a file icon button for the skinned mode. Since we’ve already covered accelerators and icon buttons, I won’t go over them again.
Next: ListViews.
Source Code:
win32tut_part16.zip [3.42MB zipped]
Additional Information:
· TheForger’s Win32 API Tutorial - Files
Further Reading:
· Nitty Gritty Windows Programming with C++ by Henning Hansen.
· Thinking in C++ by Bruce Eckel.






how will I modify the code if I want it to save directly. When I open a file and edit it, I want it to save and not prompt the “save as” dialog box. thanks.
Nice post.Thank you for taking the time to publish this information very useful!
I’m still waiting for some interesting thoughts from your side in your next post thanks