Top

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.

Comments

Got something to say?





Bottom