File Management Example - ExtendedEdit: 12/1/17 Changed New1Click code line to save1Click in procedures Close1Click & Exit1Click.Edit 3/7/18 Added code line ' filedata.changed := false;' to Save1Click procedure.Edit 3/16/18 Added FileData.saveAborted code to TfileData, FormCreate, and menu events for New1Click and SaveAs1Click.NOTE: This example is written to be run as an independent Delphi VCL demonstration program to illustrate the logic for file manipulation in a macro, but requires editing to be included in a macro. To keep things simpler, it does not use the DCAL (uconstants, uvariables, etc.) although your macros will. Several code snippets addressing file paths are written for the independent program with suggested macro code presented as comments. This post is an extension of the PART SEVEN post (
Delphi Menus - A File Management Example). Although planned as an extension to the original post, there are several important changes to the code implementation. Rather than describe adds and deletes to the original this post includes a complete rewrite of the code. The important differences and additions to the original will be discussed in detail here. The goal of this example is to add a 'Recent Files' option to the menu that displays a list of previous files used in the program. In doing so we will see how menus can be changed programmatically and how to use the tstringlist class to manage list of text.
Before adding diving into the code we make three changes to the form design.
1. Add a tlabel component to the form using its default name 'Label1'. This label will be used to display the name of the current file name managed by our program.
2. Double click the MainMenu1 component to start the editor to insert one more line in the 'File' drop down list. Right click 'Open' and select insert to include a new menu options to be named 'RecentFiles1' and captioned as 'Recent Files'.
3. Add a tmemo component to the project and use the default name 'Memo1'. Size it to contain up to 10 lines of text.
Now we are ready to start inserting code to our previous example.
Code: Select allunit PopUpForm3_u;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, System.UITypes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus, Vcl.StdCtrls, inifiles; {1}
//Datacad
// UConstants, UInterfaces, UInterfacesRecords, URecords, UVariables, UDCLibrary;
type
tFileData = record
fileName : string;
isnew, // flag that a new file has not yet been saved
saveAborted,
changed : boolean; {flag that file data has been altered and requires saving before exit. Set this variable wherever data is changed in the macro.}
RecentFileCount : integer; {2}
RecentFiles : tstringlist; {2}
end;
pFileData = ^tFileData;
TPopUpForm = class(TForm)
MainMenu1: TMainMenu;
File1: TMenuItem;
New1: TMenuItem;
SaveAs1: TMenuItem;
Close1: TMenuItem;
Open1: TMenuItem;
Save1: TMenuItem;
Button1: TButton;
Exit1: TMenuItem;
RecentFiles1: TMenuItem; {3}
Label1: TLabel; {3}
Memo1: TMemo; {3}
procedure AddRecent2List (const fname : string); {4}
procedure Button1Click(Sender: TObject);
procedure Close1Click(Sender: TObject);
procedure Exit1Click(Sender: TObject);
procedure FormCreate(Sender: TObject); {5}
procedure MemoUpdate; {4}
procedure New1Click(Sender: TObject);
procedure Open1Click(Sender: TObject);
procedure Save1Click(Sender: TObject);
procedure SaveAs1Click(Sender: TObject);
procedure SelectRecentFile(Sender: TObject); {4}
procedure Shutdown; {4}
procedure Startup; {4}
procedure FormDestroy(Sender: TObject); {5}
procedure ReadDataFile (const fname:string);
procedure UpdateMenu; {4}
procedure WriteDataFile(const fname:string);
private
{ Private declarations }
public
{ Public declarations }
fileData : tFileData; {6}
file_ini : string; {6}
end;
var
PopUpForm: TPopUpForm;
implementation
{$R *.dfm}
// TPopUpForm procedures--------------------------------------------------
procedure TPopUpForm.AddRecent2List (const fname : string); {7}
var
i: integer;
begin
// Display the name file being passed to the form.
Label1.caption := fname;
//look for a duplicate file names and delete it from the list.
i := FileData.RecentFiles.indexOf (fname);
if i > -1 then begin
FileData.RecentFiles.Delete(i);
end;
//place the new file name in the first position
FileData.RecentFiles.Insert(0,fname);
//update memo1 to display current file list
memoUpdate;
//Update the Menu list
UpdateMenu;
end;
procedure TPopUpForm.Button1Click(Sender: TObject); {8}
begin
FileData.changed := true;
Button1.Caption := 'Data Changed';
end;
procedure TPopUpForm.Close1Click(Sender: TObject); {8}
begin
Save1Click(self);
end;
procedure TPopUpForm.Exit1Click(Sender: TObject); {8}
begin
Save1Click(self);
//next line used for closing popup in a macro
ModalResult:= mrOK;
//use next line to close a Delphi VCL program only - omit in macros.
Application.terminate;
end;
procedure TPopUpForm.FormCreate(Sender: TObject); {10}
var
i : integer;
NewItem: TMenuItem;
begin
//Define a name for the *.ini file used tosave data between uses.
File_ini := 'C:\DataCAD 19\Support Files\Sample.ini';
//create the file string list and limit its capacity
fileData.RecentFiles := tstringlist.create;
fileData.RecentFiles.capacity := 10;
//Inital settings for and new, unsaved project
FileData.isNew := true;
FileData.changed := false;
//recall any previously saved data
Startup;
//update the memo display of recent files - for demonstration
Label1.caption := FileData.fileName;
memoUpdate;
//Initialize the Recent Files submenu {10a}
for i := 0 to 9 do begin
NewItem := TMenuItem.create (self);
NewItem.caption := '';//FileData.RecentFiles [i-1];
NewItem.Tag := i;
NewItem.OnClick := SelectRecentFile;
NewItem.Visible := false;
RecentFiles1.insert (i, NewItem);
end;
//reload the submenu with the current file list.
updateMenu;
end;
procedure TPopUpForm.FormDestroy(Sender: TObject); {11}
begin
Shutdown;
fileData.RecentFiles.free;
end;
procedure TPopUpForm.MemoUpdate; {12}
var
i : integer;
begin
memo1.clear;
memo1.Lines := fileData.RecentFiles;
// for i := 0 to 9 do begin
// memo1.lines.add (FileData.RecentFiles [i]);
// end;
end;
procedure TPopUpForm.New1Click(Sender: TObject); {9}
var
selection :integer;
begin
//check if current file requires saving
if FileData.changed then begin
selection := MessageDlg('Save current project?', mtConfirmation, [mbYes,mbNO], 0);
if selection = mrYes then begin
if FileData.isnew then begin
SaveAs1Click(Self);
end
else begin
WriteDataFile(FileData.fileName); //save current file
end;
end;
end;
//Reset the data structure to accept new data
if not fileData.saveaborted then begin
//initialize data for new file
FileData.isNew := true;
FileData.changed := false;
end;
//for demo only
Button1.Caption := 'Change Data';
showmessage ('New File Initiated.');
end;
procedure TPopUpForm.Open1Click(Sender: TObject); {9}
var
openDialog : topendialog; // Open dialog variable
begin
//get file
// Create the open dialog object - assign to our open dialog variable
openDialog := TOpenDialog.Create(nil);
try
// Set up the starting directory to be the current one
openDialog.InitialDir := ExtractFileDir (FileData.fileName);
// Only allow existing files to be selected
openDialog.Options := [ofFileMustExist];
// Allow only .dpr and .pas files to be selected
openDialog.Filter := 'SampleData|*.myData';
// Select pascal files as the starting filter type
// openDialog.FilterIndex := 2;
// Display the open file dialog
if openDialog.Execute then begin
New1Click(self); //save existing file if desired
ReadDataFile (openDialog.FileName);
AddRecent2List (OpenDialog.FileName);
end
else begin
ShowMessage('Open file was cancelled');
end;
finally
openDialog.free;
end;
end;
procedure TPopUpForm.Save1Click(Sender: TObject); {8}
//automatic file save
begin
if FileData.isnew then begin
//prompt to save (saveas)
SaveAs1Click(self);
end
else if FileData.changed then begin
//save current file
writeDataFile (FileData.fileName);
FileData.isnew := false; //no longer an unsaved data structure
Button1.caption := 'Change Data';
filedata.changed := false;
end;
end;
procedure TPopUpForm.SaveAs1Click(Sender: TObject); {9}
//Prompted file save
var
saveDialog : tsavedialog;
begin
// Create the save dialog object - assign to our save dialog variable
saveDialog := TSaveDialog.Create(nil);
try
// Give the dialog a title
saveDialog.Title := 'Save the Sample File';
//Adjust these for Sample
// Set up the starting directory to be the current one
saveDialog.InitialDir := ExtractFileDir (FileData.fileName);
//set the default file name
saveDialog.FileName := ExtractFileName(FileData.fileName);
// Set the default extension
saveDialog.DefaultExt := 'myData';
// Display *.myData file types only
saveDialog.Filter := 'SampleData|*.myData';
//prompt to ovewrwrite if file exists.
saveDialog.Options := saveDialog.Options + [ofOverwritePrompt];
// Display the saveas file dialog
if saveDialog.Execute then begin
FileData.fileName := saveDialog.FileName;
WriteDataFile (saveDialog.FileName);
FileData.isnew := false; //no longer a new, unsaved data structure
FileData.changed := false;
Button1.caption := 'Change Data';
AddRecent2List (saveDialog.FileName);
end
else begin
//for example display only - not required
fileData.saveaborted := true;
ShowMessage('Save file was cancelled');
end;
finally
// Free up the dialog class object
saveDialog.Free;
end;
end;
procedure TPopUpForm.SelectRecentFile(Sender: TObject); {13}
begin
//Menu items start at zero, filelist starts at 1 so use tag + 1
FileData.fileName := FileData.RecentFiles [(sender as TMenuItem).tag];
AddRecent2List (FileData.FileName);
Label1.caption := FileData.fileName;
end;
procedure TPopupForm.Shutdown; {14}
var
i :integer;
iniFl: tIniFile;
begin
try
iniFl := TIniFile.Create(file_ini); //change the name to match your macro file names
iniFl.WriteString('POPUPFORM3', 'Active File', FileData.fileName);
iniFl.WriteInteger('POPUPFORM3', 'Count', FileData.RecentFileCount);
for i := 0 to 9 do begin
iniFl.WriteString('POPUPFORM3', 'Recent File ' + inttostr (i), FileData.RecentFiles [i]);
end;
finally
iniFl.free;
end;
end;
procedure TPopupForm.StartUp; {14}
var
i :integer;
iniFl: tIniFile;
begin
if fileexists(file_ini) then begin //change the name to match your macro file names
try
iniFl := TIniFile.Create(file_ini); //create the class object
FileData.fileName := iniFl.ReadString('POPUPFORM3', 'Active File', 'Error');
FileData.RecentFileCount := iniFl.ReadInteger('POPUPFORM3', 'Count', 0);
for i := 0 to 9 do begin
FileData.RecentFiles.add (iniFl.ReadString('POPUPFORM3', 'Recent File ' + inttostr (i), ''));
end;
finally
iniFl.free;
end;
end
else begin
//IF previous files not found
FileData.RecentFileCount := 0;
for i := 0 to 9 do begin
FileData.RecentFiles.Add ('');
end;
// FileData.filename := 'C:\DataCAD 19\Support Files\Project1.myData';
//Reset the data structure to accept new data
FileData.isNew := true;
FileData.changed := false;
end;
end;
procedure TPopUpForm.ReadDataFile (const fname:string); {8}
begin
//Example only code - insert read file code as required
ShowMessage('Read File : ' + FName);
end;
procedure TPopUpForm.UpdateMenu; {15}
var
i : integer;
begin
for i := 0 to 9 do begin
if not (fileData.RecentFiles[i]= '') then begin
RecentFiles1.Items[I].Caption := fileData.RecentFiles [i];
RecentFiles1.Items[I].visible := true;
end
else begin
RecentFiles1.Items[I].Caption := '';
RecentFiles1.Items[I].visible := false;
end;
end;
end;
Procedure TPopUpForm.WriteDataFile(const fname:string); {8}
var
FS: TFileStream;
w:twriter;
begin
FS := TFileStream.Create(Filedata.fileName, fmCreate or fmOpenWrite or fmShareDenyWrite);
w := TWriter.create(FS, $ff);
try
w.WriteString(FileData.filename);
ShowMessage(Fname + ' file written.');
finally
//free writer before filestream
w.free;
FS.free;
end;
end; //WriteDataFile
end.
{1} This example will save data to an '.ini' file (
see PART SEVEN Saving Form Data between uses ('*.ini' files)),so the inifiles unit must be added to the 'uses' statement. Unlike that example we will move not place the
<name>startup &
<name>Shutdown procedures in the initialization and finalization sections of the unit. There are two reasons for this. First the data in this unit, including a tstringlist class object, is is only used while the form is active and good design suggests the data is best encapsulated in the form. Second, since the tstringlist class object is included in the form, it cannot be created until the form is. IF the
<name>startup &
<name>Shutdown procedures are in the initialization and finalization sections they will be created before and destroyed after the the actual object is created and destroyed (initialization occurs before form creation & vice versa). Moving the procedures into the form gives us control over the sequence of events.
{2} Add the variables
RecentFiles: tstringlist to hold a list of file names in the public section of the form hold file names and
RecentFileCount : integer; to track the number of file names that have been written to the list. Note that this example is written to hold 10 file names.
{3} Note that the PopUpform now contains the Label1, Memo1 and RecentFiles1 components we placed on the form at the beginning of this post.
{4} Declare the six new procedures in form declaration and provide implementations for each one in the implementation section as written in this code example.
{5} Add the
FormCreate() procedure by selecting the form in the object inspector and double clicking on the 'OnCreate' event. It is entered this way so that Delphi can do other housekeeping tasks. Similarly generate a FormDestroy procedure by double clicking on the onDestroy event for the form.
{6} Add the variables
FileData : tfileData; to hold all the data in a record type and
file_ini : string to hold the name of the '*.ini' file used to store data between program uses.
{7} This is the code for the new procedure. It will check for any duplicates in the current list, delete them and add the new file name to the top of the list of files.
{8} These procedures are the same as in the original example found in
PART SEVEN Delphi Menus - A File Management Example. Explanations can be found there.
{9} These procedures are essentially the same as the original example found in
PART SEVEN Delphi Menus - A File Management Example, however code lines have been added to display the correct caption for Label1 and to add the name of currently selected files to the Recent File list
AddRecent2List ({i}<name of selected file>[/i]).
{10} As discussed at earlier, the
FormCreateprocedure is used to handle any initialization requirements for the program. When the Form is created, the code in this event will be executed to create additional menu items in a submenu under the 'RecentFiles' menu item (
see {10a}).
{10a} This section of the procedure defines the submenu that occurs under 'Recent Files'. The submenu is generated in a for/do statement and will have 10 items. Each
NewItem: tmenuitem is created and assigned a blank caption, a unique tag number for reference to the RecentFile list, an 'onclick' event
SelectRecentFile, and set to not visible. The separate procedure
UpdateMenu will assign the current list of 'Recent Files; to the menu items.
{11}
FormDestroy is used to perform any finalization steps included in the
ShutDown procedure and then frees the memory used to create the RecentFiles tstringlist. Note that the stringlist is used in the
ShutDown procedure and so is not released until after its final use.
{12}
MemoUpdate is fairly simple. First it clears the current display list and then it assigns the current list of files to the memo display.
{13}
SelectRecentFile(Sender: TObject) Recent Files' menu items we assigned a tag and an event handler
SelectRecentFile to each submenu item. WHenever a user selects a file from the menu, this onclick event is called. The 'Sender' argument is the specific menu item that is selected and so the code reads the tag number to determine which file in the FileData.RecentFiles stringlist has been selected. This file name is defined as the current file
FileData.FileName and added to the file list using
AddRecent2List where it will be placed in the first position.
{14} the
StartUp &
ShutDown procedures are similar to other examples (
see {1}).
{15}
UpdateMenu iterates through each submenu item and either sets the caption to the associated file name and displays it or erases the caption and makes the menu item invisible.