Home |
14. Open and Save Dialogs System Dialog Boxes |
Home |
The Windows System has several common dialog boxes available from it's ComDlg32.DLL library, like the Open, Save, Color, Find, and Print. The Open and Save dialog boxes can be very useful and will be the subject of this lesson. In lesson number 8, about dialog boxes, you were told how to use templetes to make your own dialog boxes, and you could do the work and create your own folder search and file picker dialog box (open and save file). But since finding a file is a common task, you can call up a dialog box that the system already provides for finding and naming a File. The system Open and Save dialog boxes should be familar to many computer users, and have several options for the programmer to do some of the things they may need for a file search. There is even a creation option to place a child dialog on the these File dialogs, so you can add your own controls to customize it, but I can not talk about that in this lesson. |
Creating a System File Finder Dialog The system Open and Save file dialogs are like the "MessageBox" dialog because they are also modal, have a title, and have a window as their owner, but have many more options. The Open and Save dialogs are alike in their API methods for creation, use, and options. Although there are two different functions for the Open and Save dialog creation, GetOpenFileName( ) and GetSaveFileName( ), they both return a Bool value, and only take a single parameter, the TOpenFilename record. function GetOpenFileName(var OpenFile: TOpenFilename): Bool; function GetSaveFileName(var OpenFile: TOpenFilename): Bool; You can look up these functions in your API Help, but it does not give any info about the dialog options. I will not say much about these functions, except that they return "True" if they are successful and "False" if the user cancels the dialog or there is an error. All of the information about using these functions and changing the open dialog options is about the system OPENFILENAME or Delphi TOpenFilename record passed as the parameter. You set up how your file picker dialogs display and function by setting the TOpenFileName record. This TOpenFilename record has 20 elements (fields), and with this many elements, it can be confusing to you when you want to change dialog options (or just get it to work). You will need to read and review your Win32 API Help for index OPENFILENAME, hopefully you can get something about using these dialogs from all of the information they present.You may want to read the information at MSDN about this - Open and Save dialog boxes OPENFILENAME Structure The TOpenFilename record definition - type POpenFilename = ^TOpenFilename; tagOFNA = packed record lStructSize: DWORD; // Cardinal hWndOwner: HWND; hInstance: HINST; lpStrFilter: PChar; lpStrCustomFilter: PChar; nMaxCustFilter: DWORD; nFilterIndex: DWORD; lpStrFile: PChar; nMaxFile: DWORD; lpStrFileTitle: PChar; nMaxFileTitle: DWORD; lpStrInitialDir: PChar; lpStrTitle: PChar; Flags: DWORD; nFileOffset: Word; nFileExtension: Word; lpStrDefExt: PChar; lCustData: LPARAM; // Integer lpFnHook: function(Wnd: HWND; Msg: UINT; wParam, lParam: Integer): UINT stdcall; lpTemplateName: PChar; end; TOpenFilename = tagOFNA; OPENFILENAME = tagOFNA;There are eight text (PChar), eight DWORD (Cardinal), two Word, one Integer and one function (Pointer) element in this record. You will need to know something about setting the elements in this record before using it as a parameter. You should read through the API Help for OPENFILENAME to see what it says, but there is so many elements in this record, and so many flags and options for an open dialog, it is unlikely that you can understand what you need to set in these elements. Below I will give very short descriptions for the 20 elements in the TOpenFilename record, I can not begin to include much of the information that goes into using all of these elements and their interactions with other elements. Later I will present you with code examples that create an Open or Save Dialog, so you can read about what I did in the code to get the Dialog to work. The First example will be a "Basic" Open file dialog in the code for Basic Open Dialog below. In the element list below, you will see that the 9 elements used for the basic open dialog example code below are in a red color, the other 11 elements are not used and will just be zero and nil. List of the TOpenFilename record elements.
With 20 different elements, some with several various options, I can not try to give an explanation of how and why all of these may be used. You should be able to get some guidlines as to how and why these elements are set, when you read the API Help for OPENFILENAME, but they do not tell you which elements to set for a your specific dialog. It may be more usefull to look at the example codes below. To see which elements are set with which options. NOTE - In the Flag element options there is the OFN_EXPLORER flag bit, without this flag the dialog will use the old 16 bit windows open dialog interface. I will NOT have anything about creating or using the old 16 bit windows dialogs here, I will always use the OFN_EXPLORER flag for explorer (list view) style dialogs. First I will have some code that is used to show a typical "Basic", "Standard", non- modified, Open Dialog Box, in the DoOpenDlg function below. The DoOpenDlg function returns a String with the Open Dialog's pick of file path and name, or empty string if canceled. This code will set only nine of the elements in the TOpenFileName record, the other 11 elements are not needed for a basic Open Dlg and are set to zero or nil. I have included some comments in the code, as brief information about each TOpenFileName element, but you might need to read the Help for more information and other options. |
function DoOpenDlg: String; var OFName : TOpenFileName; FileName: Array[0..2047] of Char; { Fixed Length Text Buffer for file path result. You should use more than 256 chars for your text buffer, newer systems can have file paths and names with kilobytes of charaters. I mostly use 2 kilobytes} FolderName: String; begin ZeroMemory(@FileName, SizeOf(FileName)); ZeroMemory(@OFName, SizeOf(OFName)); // Set all record elements to Zero or Nil FolderName := 'C:/'; // this will be the first Folder shown in the Open Dialog with OFName do begin lStructSize := SizeOf(OFName); // you MUST ALWAYS set the lStructSize hWndOwner := hForm1; // Returns focus to the Owner window, usually main window lpStrFile := @FileName; {IMPORTANT - you MUST set some kind of memory block text buffer for the lpStrFile, This is where the choosen file name and path is returned} nMaxFile := SizeOf(FileName); // you MUST have the memory size of the lpStrFile lpStrInitialDir:= PChar(FolderName); {the system will try and set the Open Dlg first folder showing to this folder, if it exists, if it does not exist, or you have this as nil, it uses the current system folder, this is optional} Flags := OFN_EXPLORER or OFN_ENABLESIZING or OFN_HIDEREADONLY or OFN_FILEMUSTEXIST; {These Flags are some of the Usuall ones for an Open Dlg, their names give some indication of what option they set} lpStrFilter := 'Text files .TXT'#0'*.txt'#0'Delphi files, DPR,PAS,DOF'#0+ '*.dpr;*.pas;*.dof'#0'All files'#0'*.*'#0#0; // double null end {the lpStrFilter are #0 terminated sets of strings which tells the Open Dlg what files to show, there are two strings in each set, then first string is displayed in the combo box, the second string has the ; separated file extention filters, to show the end of these sets, use a double #0} nFilterIndex := 1; { set your filter index, usually to One. The filter index of zero if for the Custom Filter, which is not used in this code. The filter index of One uses the first filter in your lpStrFilter string above} lpStrTitle := 'Search for a File to Open'; // Title on the Open Dlg, optional end; if GetOpenFileName(OFName) then // GetOpenFileName is True if successful Result := FileName // FileName will have the file Path and Name in it else Result := ''; // Blank out Result string to show Failure of this function {I could have used the code - Result := OFName.lpStrFile; instead of the - Result := FileName; the OFName.lpStrFile and the FileName are the same thing} end; |
This basic open dialog is enough for many, if not most, find and open a file dialogs. The comments above should help you to understand why some of the code was included. For the "Flag", I use only 4 of the more than 24 flags availible. I will now say something about the "Flag" options I have set above. Flags in DoOpenDlg - OFN_ENABLESIZING - I always use the OFN_ENABLESIZING flag, this allows the user to resize the dialog box, which are origially to small (for me) on larger screen resolutions. OFN_HIDEREADONLY - I almost always use the OFN_HIDEREADONLY flag, which causes the dialog to NOT show a "Open as Read Only" check box at the bottom of the dialog. I have found few times to need to use this "Open as Read Only" check box, and wonder why it is shown as the default. . . . This Flag name is usually confusing, many think it has something to do with read-only files, but it does not. OFN_READONLY - This flag is not in the DoOpenDlg, but is related to the OFN_HIDEREADONLY. If you include this Flag then the Read-Only Check Box will be checked OFN_FILEMUSTEXIST - In an Open Dialog (not in a Save dialog) I usually use the OFN_FILEMUSTEXIST, this flag causes the system to check for the existance of the file and give an error message if it does not exist. I use this flag in Open Dialogs, because you can not "Open" and use a file that does not exist. Your open dialog may not require the file to exist, so you would leave out ths flag. File Filter String- 'Text files .TXT'#0'*.txt'#0 The text before the first #0 will be shown in the combo list- Text files .TXT -in this case. Between the first and second #0 is- *.txt the File Filter Index, nFilterIndex Change DoOpenDlg( ) to a Save Dialog if GetOpenFileName(OFName) then You could use the same Flags, but it would not be a good set of flags for a save dialog, it seems better for a Save dialog to use - Flags := OFN_EXPLORER or OFN_ENABLESIZING or Save Flags - OFN_OVERWRITEPROMPT - Another useful Flag bit for a Save Dialog, which will cause the system to "Prompt" the user with a message box if that file name exists on the disk. To make sure they really want to replace it (OverWrite). Some Other Flags NOTE - You may have noticed (or not) that if you pick a system LINK file (with .lnk file extention) with the DoOpenDlg function, the Open dialog will NOT return the file path to that LINK, but the file path to the file that LINK is linked to. You can turn that off with the next Flag. OFN_NODEREFERENCELINKS - If you want the path to the LINK file and not the reference file of the link, then you will need to include the OFN_NODEREFERENCELINKS Flag. OFN_CREATEPROMPT - If that file does not exist, a dialog will be shown asking the user if he wants to create that file. This may be used to when a file name that exists is expected. OFN_NOCHANGEDIR - Normally the system will change the current working directory to where ever the user opens a folder in his search with an Open-Save dialog. If you use this Flag, then the system will restore the current working directory to its original value if the user changed the directory.
DlgOpenSave Unit Next is some code for a unit that has functions to display Open and Save dialogs. Some parts of this DlgOpenSave unit may be useful to use as a system Open-Save dialog unit for your programs that need an Open-Save Dialog. There are three functions in this unit - function OpenSavDlg(hOwner: Cardinal; const iniDirPath, Filter: String; Open: BOOL): String; function OpenDlgOpt(var DlgSetUp: TDlgSetUp; FilterIndex: Cardinal = 1): String; function OpenMultiSel(hOwner: Cardinal; const iniDirPath, Filter, Title: String): TMultiResult; OpenSavDlg( ) - This function can do a simple open-save dialog, this returns a string with the choosen file path or an empty string if canceled or an error happens. It has four parameters - hOwner - Set to the window handle that get's focus on dialog close You can set the two string parameters (iniDirPath, Filter) to an empty string to get the default settings. If iniDirPath is an empty string, then it is set to your programs folder. If the Filter is an empty string, it is set to an "All Files" filter. You should be able to look at the function's code and see how it works. OpenDlgOpt( ) - This function uses a TDlgSetUp record as it's first parameter and an optional filter index as it's second parameter. It returns the file path of the choosen file if successful, or an empty string if canceled or an error. The TDlgSetUp record and TDlgOptions are defined as - TDlgSetUp = record hOwner: Cardinal; iniDirPath, iniFileName, Filter, Title, DefExt: String; Options: Set of TDlgOptions; end; TDlgOptions = (doSave, doReadOnlyCB, doCheckRead, doFileExist, doPathExist, doOverWrite, doCreatePompt, doLinks, doNoChangeDir, doTrackFolder, doCusFilter); In the TDlgSetUp record, you should be able to get the idea of what the record element is used for from it's name. There are five string elements, if you do not place any text in these string elements, then default strings will be used if needed. You should be able to look at the function code and figure out what these strings set in the TOpenFilename. The Options element is a Set of TDlgOptions. You can look in the DlgOpenSave unit code below for some short descriptions of each of the TDlgOptions, and look at the function code in the unit to see what it is doing for these options. All of the elements in the TDlgOptions record are optional, you do not need to have any elements set in order to use it. Next is a code example to use the OpenDlgOpt( ) function. var DlgSetUp1: TDlgSetUp; with DlgSetUp1 do begin hOwner := hForm1; iniDirPath := 'C:\Stuff'; iniFileName := 'NewFile1.txt'; Filter := 'Text files .TXT'#0'*.txt'#0'New Text files,+ ' NewFile.TXT'#0'NewFile?.txt;NewFile??.txt'#0+ 'All files'#0'*.*'#0#0; Title := 'Save Something Else'; DefExt := '.txt'; Options := [doSave,doPathExist,doOverWrite,doCusFilter]; end; fName := OpenDlgOpt(DlgSetUp1, 2); if fName <> '' then UseFName(fName); I tried to make this OpenDlgOpt( ) function easy to use and still have a good amount of options and factors availible for the dialogs. You should be able to take things out of the OpenDlgOpt( ) function that you do not use or add some option or setting you will use. OpenMultiSel( ) - This function will set the OFN_ALLOWMULTISELECT flag, so the Open dialog will be able to choose and return more than one file name. Because the returned file name string for mutiple files is different that the file name string returned for a single file, I have the result of this function as a TMultiResult record. This record has two elements, a fOffSet integer and a fNames string. For a mutiple file name result string, the system uses a #0 (null) delimited string, a #0 character will separate the folder path and each of the file names that follow it. The fOffSet integer will have the first file name character offset position or a -1 if user canceled or an error occured. You may not be used to using #0 delimited strings, like the ones returned for this function, so here is an example code procedure called DoMultiFile, to give you some ideas about how to separate each string segment. This example will use StrEnd( ) function to locate each of the #0 string delimters and place the file name string segment in a List Box. Code for the OpenMultiSel function - |
procedure DoMultiFile; var MultiRe: TMultiResult; filePath: String; pFileName: PChar; begin {the OpenMultiSel function uses a Multi-Selection dialog, the Result string is different than a normal open dialog, it has null #0 delimited file path and names} MultiRe := OpenMultiSel(hForm1, 'C:\Stuff', 'Text files .TXT'#0'*.txt'+ #0'All files'#0'*.*'#0#0,'Open more than One File, Multi-Select'); {a TMultiResult is the result form a OpenMultiSel, the fOffSet will be -1 if it fails, or the File-Name charater offset if it succeeds} if MultiRe.fOffSet <> -1 then begin // I list the file name in the hListBox1 List Box SendMessage(hListBox1, LB_RESETCONTENT, Zero, Zero); SetString(filePath, PChar(MultiRe.fNames), MultiRe.fOffSet-1); { the Folder path is in the first section of the MultiRe.fNames MultiRe.fNames is several #0 delimited only if more than one file I use SetString( ) with the MultiRe.fOffSet-1 number to know it's length if there is only ONE file name, the result string will not have #0 delimters, I test for the #0 delimter at MultiRe.fOffSet} if MultiRe.fNames[MultiRe.fOffSet] <> #0 then // true if single file name begin SendMessage(hListBox1, LB_ADDSTRING, Zero, Integer(@MultiRe.fNames[MultiRe.fOffSet+1])); // the list box will add single string above using the file name offset end else begin pFileName := StrEnd(PChar(MultiRe.fNames)); // StrEnd will get the #0 delimiter Inc(pFileName); // move to character after #0 while pFileName^ <> #0 do // loop until there are two #0 begin // add #0 terminated file name to list box SendMessage(hListBox1, LB_ADDSTRING, Zero, Integer(pFileName)); pFileName := StrEnd(pFileName); Inc(pFileName); end; end; end else filePath := 'User Canceled or Error'; // MultiRe.fOffSet = -1 end; |
Code for the DlgOpenSave unit
There are comments in this code that should give you information about what
that code block is used for. The OpenSavDlg( ) function is like the DoOpenDlg( )
function above, so I did not comment in it.
unit DlgOpenSave; {this Unit has three functions to create system Open-Save Dialogs} interface uses Windows; type { TDlgOptions are the 11 members which control the dialog creation Flags in the TDlgSetUp-record Options set} TDlgOptions = (doSave, doReadOnlyCB, doCheckRead, doFileExist, doPathExist, doOverWrite, doCreatePompt, doLinks, doNoChangeDir, doTrackFolder, doCusFilter); TDlgSetUp = record // used for dialog Settings in the OpenDlgOpt function hOwner: Cardinal; // replaced with the Filter Index when OpenDlgOpt returns iniDirPath, iniFileName, Filter, Title, DefExt: String; // the four strings above are optional Options: Set of TDlgOptions; end; {setting the Options Set to include a TDlgOptions - doSave - Makes a Save dialog, otherwize an Open dialog is shown doReadOnlyCB - will show the Read-Only check box at bottom of dialog doCheckRead - will check the read-Only check box, included in Option if user checks doFileExist - system checks to see if file name exists, then asks user doPathExist - system checks to see if file path exists, then asks user doOverWrite - if file name exists, asks user if OK to over-write doCreatePompt - if file name does not exist, asks if OK to create file doLinks - will return the link .lnk file name, instead of the file it is linked to doNoChangeDir - the current folder will not change with the dialog folder doTrackFolder - places the folder path in the OpenFolder string when dialog closes doCusFilter - Sets the CustomFilter array to save the user's custom filter} TMultiResult = record // result record of the OpenMultiSel function fOffSet: Integer; // File Name character off-set in fNames fNames: String; // #0 delimited string with multi-FileNames end; // the OpenSavDlg is a simple Open-Save dialog creation function function OpenSavDlg(hOwner: Cardinal; const iniDirPath, Filter: String; Open: BOOL): String; // the OpenDlgOpt can set many more dialog options with the DlgSetUp record function OpenDlgOpt(var DlgSetUp: TDlgSetUp; FilterIndex: Cardinal = 1): String; // the OpenMultiSel is a Multi-Selection Open dialog that returns a TMultiResult function OpenMultiSel(hOwner: Cardinal; const iniDirPath, Filter, Title: String): TMultiResult; var OpenFolder: String = 'C:'; // contains the Last Open Folder if doTrackFolder CustomFilter: Array[0..511] of Char; // records the user custom filter if doCusFilter implementation uses Messages, CommDlg, SmallUtils; const Zero = 0; One = 1; All_Files: PChar = 'All files'#0'*.*'#0; function OpenSavDlg(hOwner: Cardinal; const iniDirPath, Filter: String; Open: BOOL): String; var OFName : TOpenFileName; FileName: Array[Zero..2047] of Char; begin // basic open-save dialog creation ZeroMemory(@FileName, SizeOf(FileName)); ZeroMemory(@OFName, SizeOf(OFName)); with OFName do begin lStructSize := sizeof(ofName); hwndowner := hOwner; nMaxFile := SizeOf(FileName); lpstrFile := @FileName; nFilterIndex := One; if Length(iniDirPath) > One then lpstrInitialDir := PChar(iniDirPath) else lpstrInitialDir := PChar(GetFilePath(ParamStr(Zero))); if Length(Filter) < 4 then lpstrFilter := All_Files else lpstrFilter := PChar(Filter); if Open then Flags := OFN_EXPLORER or OFN_FILEMUSTEXIST or OFN_HIDEREADONLY else Flags := OFN_EXPLORER or OFN_PATHMUSTEXIST or OFN_OVERWRITEPROMPT or OFN_HIDEREADONLY; end; Result := ''; if Open then begin if GetOpenFileName(OFName) then Result := FileName; end else if GetSaveFileName(OFName) then Result := FileName; end; function OpenDlgOpt(var DlgSetUp: TDlgSetUp; FilterIndex: Cardinal = One): String; const // this FlagValues holds some of the constants for the OFName.Flags FlagValues: array[TDlgOptions] of Cardinal = (Zero, Zero, OFN_READONLY, OFN_FILEMUSTEXIST, OFN_PATHMUSTEXIST, OFN_OVERWRITEPROMPT, OFN_CREATEPROMPT, OFN_NODEREFERENCELINKS, OFN_NOCHANGEDIR, Zero, Zero); var OFName : TOpenFileName; FilePath: Array[Zero..2047] of Char; i: TDlgOptions; procedure SetResult; begin //this procedure is used to set Result for both the Open and Save dialogs Result := FilePath; DlgSetUp.hOwner := OFName.nFilterIndex; { place the current Filter Index into the hOwner incase you need to reset it to user's Index} { the doTrackFolder will record the folder path, so you can open the next open-save Dlg in the same folder as the last open save} if doTrackFolder in DlgSetUp.Options then OpenFolder := GetFilePath(Result); //the OFName.Flags will have the OFN_READONLY bit if the check box was checked if OFName.Flags and OFN_READONLY <> Zero then DlgSetUp.Options := [doCheckRead]; end; begin {this is a more flexable open dlg function, it has a TDlgSetUp record to change the dialog with Owner, Title, Filter and other Options} ZeroMemory(@FilePath, SizeOf(FilePath)); ZeroMemory(@OFname, SizeOf(OFName)); with OFName, DlgSetUp do begin lStructSize := sizeof(OFName); hwndOwner := hOwner; // the owner is set to DlgSetUp.hOwner nMaxFile := SizeOf(FilePath); lpstrFile := @FilePath; if Length(iniFileName) > One then StrCopy(lpstrFile, PChar(iniFileName)); // set first file name here if Length(iniDirPath) > One then lpstrInitialDir := PChar(iniDirPath) else // set default initial Folder to this programs folder lpstrInitialDir := PChar(GetFilePath(ParamStr(0))); if length(Filter) < 4 then lpstrFilter := All_Files else lpstrFilter := PChar(Filter); nFilterIndex := FilterIndex; if length(Title) > Zero then lpstrTitle := PChar(Title); // set dlg Title to OpenSet.Title if length(DefExt) > One then lpstrDefExt := PChar(DefExt);{lpstrDefExt will automatically add that file ext to a file name with no ext} Flags := OFN_EXPLORER or OFN_ENABLESIZING or OFN_HIDEREADONLY; // start with default Flags for Explorer, Sizing and no CheckBox if Options * [doCheckRead..doNoChangeDir] <> [] then for i := doCheckRead to doNoChangeDir do if i in Options then Flags := Flags or FlagValues[i]; // the for loop above will place flag values in FlagValues array // into the OFName.Flags if that option is in the set if doReadOnlyCB in Options then // take out the read only checkbox Flags := Flags and (not OFN_HIDEREADONLY); if doCusFilter in Options then begin // make a Custom Filter recorder availible lpstrCustomFilter := @CustomFilter; nMaxCustFilter := SizeOf(CustomFilter); end; Exclude(Options, doCheckRead); // Take out the Check Read option end; Result := ''; if doSave in DlgSetUp.Options then begin if GetSaveFileName(OFName) then SetResult; // places the file path into Result end else if GetOpenFileName(OFName) then SetResult; end; function OpenMultiSel(hOwner: Cardinal; const iniDirPath, Filter, Title: String): TMultiResult; var OFName : TOpenFileName; begin {this will create a Multi-Selection Open Dialg box, and the Result of this function is a TMultiResult record, which has the file-name OffSet as the fOffSet element, used to extract the file names from the #0 delimited result string in Result.fNames} SetLength(Result.fNames, 3070); // larger file name buffer for multi files ZeroMemory(@Result.fNames[One], Length(Result.fNames)); Result.fOffSet := -1; // set Result.fOffSet to an error result of -1 ZeroMemory(@OFName, SizeOf(OFName)); with OFName do begin lStructSize := sizeof(OFName); hwndowner := hOwner; hInstance := SysInit.hInstance; nMaxFile := Length(Result.fNames); lpstrFile := PChar(Result.fNames); // adding the OFN_ALLOWMULTISELECT flag bit will change the file-path string returned Flags := OFN_ALLOWMULTISELECT or OFN_EXPLORER or OFN_FILEMUSTEXIST or OFN_ENABLESIZING or OFN_HIDEREADONLY; if Length(Filter) < 4 then lpstrFilter := All_Files else lpstrFilter := PChar(Filter); lpstrTitle := PChar(Title); nFilterIndex := One; lpstrInitialDir:= PChar(iniDirPath); end; if GetOpenFileName(OFName) then Result.fOffSet := OFName.nFileOffset // set fOffSet to the file name offset else Result.fNames := ''; end; initialization CustomFilter := 'Your Filter'#0'*.*'#0#0; end. |
You should be able to create your own Open and Save dialog boxes,
and maybe a code unit for some open dialog functions.
Next Page
The next page shows you how to create and access Files on a Disk to Read and Write File Data.
15. Writing and Reading Files