![]() |
Programming the OS/2 File Dialog in C++Written by Stephane Bessette |
IntroductionThe purpose of a file dialog is to select files to open or the filename (and path) in which to store a document. From a user's standpoint, this type of dialog is fairly easy to use: select a drive, select a directory, select or type in a filename, and then click on Ok. However, the situation of a C programmer is not as rosy; there's quite a lot of repetitious work to undertake, and a few details to take into consideration. So a C++ approach will be taken in order to greatly simplify the use of the file dialogs. First There Was CThe API function that handles the file dialog is WinFileDlg: WinFileDlg( HWND hwndP, (1) HWND hwndO, (2) PFILEDLG pfild) (3)
Other members of the FILEDLG structure are used to return information from the file dialog. For example, lReturn contains the ID of the button pressed (DID_OK or DID_CANCEL), szFullFile contains the filename in the entry field, papszFQFilename contains an array of selected filenames (when more than one can be selected), and ulFQFCount contains the number of selected filenames. One word of caution is in order when ulFQFCount reports that one file has selected. The selected file could be only in the entry field, in which case papszFQFilename would be NULL. Or the selected file could be from the listbox, in which case both the first (and only) element of papszFQFilename and szFullName will contain the same file selection. The first step in the creation of a file dialog, is to initialize the FILEDLG structure: memset( &FileDialog, 0, sizeof( FILEDLG ) ); (1) FileDialog.cbSize = sizeof( FILEDLG ); (2)
And now we're ready to launch the dialog with WinFileDlg(). If the user presses the OK button, we have to retrieve the information. If only one filename was selected, we simply take it from the szFullFile member. This is a string containing the fully qualified name of the file: drive, directory, filename, and extension. But if more than one filenames were selected, then we have to retrieve them from the papszFQFilename member. This array of filenames also contains fully qualified names. This wasn't too bad. But there are still some loose ends to take care of. What if during an open operation the user types in the name of a file that does not exist? And what if during a save as operation the user types in or selects an already existing filename? This requires additional code. And every time you'd implement file open or file save as dialogs, you'd have to retype all this code. But if you're lazy (as I am), you'll save yourself some typing and copy/paste those lines of code (yuck!!). Or you could create functions containing this code and then simply call these functions. Well that's one of the aspects of C++. And Then There Was C++I've written four classes to encapsulate the C code to handle a file dialog. I believe that the code is easy to understand, so I'll only write about the use of the classes. I've put everything in a single .hpp file so that it could be placed in an include directory and then not have to link additional files to a project (my laziness shows again). The first class, Class_FileDialog, provides some actions that are common to the three other classes: setting some variables (the FILEDLG structure), functions to activate and retrieve information from the file dialogs, and a function to change the text on the titlebar of the file dialogs. This class is abstract: you cannot create instances from it. Class_FileOpen is the first functional class. As you may guess from its name, it creates a file open dialog. Its constructor has an optional parameter. When specified, it will be parsed so that the specified drive, directory, and filename or mask will be used in the file open dialog. If it is not specified, then the dialog will display all the files in the current drive and the current directory. (The constructor of the three functional classes behave in the same way.) Here's how to create an instance that will display all the text files from c:\mmos2 Class_FileOpen Dlg( "c:\\mmos2\\*.txt" );Once an instance has been created, and all the modifications have been completed (such as changing the text on the titlebar), the file open dialog can be launched: // char szFilename[CCHMAXPATH] (1) if( Dlg.Do( szFilename, FALSE ) ) (2) // sz now contains the specified filename
The second parameter, which is optional and defaults to TRUE, specifies whether the selected filename must exist. When set to TRUE, the existence of the file is verified. If it does not exist, the user is warned and offered the option of creating the file. If the user answers YES, then the file is created, the function returns TRUE and the selected filename in szFilename. If the user answers NO, then the function returns FALSE and szFilename is unchanged. As you can see, only three lines of codes were required to use a file dialog that retrieved a filename. Not bad. And it also verified the existence of that selected file. Nice. Let's move ahead with the second functional class. Class_FileOpenMultiple retrieves multiple selections. It uses a lot of the functionality of Class_FileOpen (reuse of existing code). Some code has been added so that filenames can be retrieved by the same means whether papszFQFilename is NULL or if it contains an array of filenames. But this is all hidden from the programmers (users of the class). Here's how the code to create an instance and retrieve all the selected files looks: Class_FileOpenMultiple Dlg( "*.txt" ); (1) PAPSZ psz; (2) int count = Dlg.Do( psz ); (3) for( int n = 0; n < count; n++ ) (4) // *psz[n] (5)
The third functional class implements a file save as dialog: Class_FileSaveAs. Let's look at it: // char szFilename[CCHMAXPATH] (1) Class_FileSaveAs( szFilename ); (2) if( Dlg.Do( szFilename ) ) (3) { // save the file }
Although these classes are adequate for most purposes, they lack support for extended attributes. But since I haven't touched on those yet, this will have to wait. Also, it should be possible to provide support for a custom dialog procedure. Again, that's put on hold until such a need arises. TipIf you get an error message to the effect that a constructor cannot execute, then you need to enclose that portion of the code within braces: case ID_MENU_FILE_OPEN: { Class_FileOpen Dlg( "c:\\mmos2\\*.txt" ); if( Dlg.Do( szFilename, FALSE ) ) // perform the read action to open the file break; } |