The DataCAD Developer Network (DDN) is an online resource for information and support for DCAL® (DataCAD Applications Language) developers as well as anyone interested in creating fonts, toolbars, hatch patterns, or linetypes for use in DataCAD.
#72579 by Jsosnowski
Mon Oct 30, 2017 5:22 pm
PART SEVEN - A D4D PRIMER - Delphi Language and Forms

In addition to standard function key driven macro functionality, D4D macros provide the opportunity to expand functionality using Delphi forms and the Delphi programming language features. The Delphi programming environment includes Delphi form applications and graphic components that can be incorporated in Datacad macros to provide more complex presentations of data and functionality than DCAL Macros could. It also includes the expansive Delphi programming language allowing a much broader range of data organization and manipulation than was previously available.

As mentioned in Part 1, the DCAL language is a relative of the Pascal Language with similar structure and terms. Translation between them is relatively straightforward. Delphi provides an extension to the Pascal programming language creating an object oriented programming (OOP) language environment. It is not necessary to master the details of OOP to use it effectively in your D4D macro although a it is helpful to understand a few basic concepts. A key concept in OOP is that in addition to traditional Pascal code, programmatic 'objects' can be created. These 'objects' are created from 'Classes which define a collection of features regarding an object in a single whole. For instance, in DCal their are many variables and methods used to create and manipulate drawing entities such as 'entlin'. All are independent and operated independently in the macro. An OOP class called 'LineEnt' might contain variables for the entity such as color, location points, weight, linetype etc, as well methods for moving, copying, and stretching the line, changing its attributes, or interacting with other entities all within one package. Create an object from the class and all of its features are accessible. There are many organizational advantages to such structures, but that discussion would fill books. The key points for our discussion is that these objects co-exist with standard code, but have certain rules for their implementation which must be followed. Some of the posts in this section will use objects and the process for implementing them properly will be included in the examples.

This Part Seven section will also provide examples on how to implement Delphi Forms in D4D macros. Forms are basically program 'windows' with graphic components such as buttons, check boxes, and editing boxes to display and input data and invoke commands. Like the OOP conmcept, forms are fairly complex (they are in fact class objects themselves) in that there are an incredible number of features and components available and mastering their usage cam be challenging. Fortunately there are plenty of resources and online resources to help you advance your skills. Postings below will address some of the basics on how to incorporate forms and use some fundamental features get things up and running quicker.

Postings in this section include:

1. Adding a Form to a <name>_doMenu Procedure.
2. Linking Macro Data to the Form.
3. Saving Form Data Between Uses ('*.ini' files).
4. Saving Data to a File
5. Delphi Menus - A File Management Example
6. File Management Example - Extended
Last edited by Jsosnowski on Mon Nov 27, 2017 3:12 pm, edited 7 times in total.
#72580 by Jsosnowski
Mon Oct 30, 2017 5:35 pm
A Simple D4D Form

Step One: Create the form:
1. Create a new VCL project in the Delphi IDE and place a tbutton on the form.
2. Select the button and on the Delphi Object Inspector select the Events tab and double click the onClick item to create a code procedure entitled 'Button1Click'.
3. Save the project as 'PopUpForm1.dproj' and the code file as 'PopUpForm_U.pas'.
4. Go to the Delphi Object Inspector and change the name of the form to ' PopUpForm1'.
5. Return to the 'Button1Click' procedure and enter the following code:

Code: Select allprocedure PopUpForm1.Button1Click (Sender: TObject);
begin
  if Button1.Caption = 'Clicked' then
      Button1.caption := 'Unclicked'
    else
      Button1.caption := 'Clicked';
end;

6. Compile and run the project to confirm it works.

Step Two: Create the macro.
1. Select Delphi projects/Add New Project/DLL Application and save the project as 'PopUpSample1'
2. In the Delphi Project/Add to Project/ menu select the file 'popUpForm_u.pas' to add it to your macro project.
3. Place the following code in the project file. Remember to change '<name>' to an name suitable for your project.

Code: Select all library PopUpSample1;
//Set Host application to DCADWin.exe in Delphi-Projects/Options/Debugger/HostApplication
//Set Delphi-Projects/options/Output Directory to correct Datacad macros directory
uses
  System.SysUtils,
  System.Classes,
  UConstants,
  UDCLibrary',
  UInterfaces',
  UInterfacesRecords',
  URecords,
  UVariables,
  popupForm_u; //PopupForm

//Delphi compiler instructions {3}
{$E dmx}  //dmx is the extension for DCAL Dll's
{$R *.res}

const {5}
//dcalstate constants
   XMain = XDcalStateBegin;  {Currently set as 8100 by uconstants.pas}
  //add an incremented dcalstate constant for each <name>_doMenu or Datacad menu call function included inthe 'Main' function below.
  //   X<name> = XMain +1;

type
tMain_LR = record  //looprecord
  state: asint;
  case byte of  // typical options included
  0: (getp: getpointArg);
  1: (gete: getescarg);
  3: (getd: getdisarg);
  4: (geta: getangarg);
  5: (geti: getintarg);
  6: (getc: getclrarg);
  7: (gets: DgetstrArg);
  8: (getr: getrealArg);
end;
pMain_LR = ^tMain_LR;

t<name>_DR = record  // not used, but necessaryfor D4D macros
  str : shortstring;
end;
p<name>_DR = ^t<name>_DR;

var
  theData : t<name>_DR;  //actual data record instance at unit level

function Main_doMenu(act : action; p_LR, p_DR : Pointer) : wantType;
var
   i, retval : asint;
   lr : pMain_LR;
   dr : p<name>_DR;
begin
   lr := pMain_LR(p_LR);
   dr := addr(theData); //use in first menu

                                              //START SECTION:
   case act of
                                                  //SIZING STEP
     alsize:
       begin
         SetLocalSize(sizeof(lr^));
       end;
                                                  //INITIALIZE MACRO STEP
     afirst:
       begin
         lr.state := 1;
         theData.str := 'This is a test string.';
       end;
                                                  //ROUTER STEP
     aagain:
       begin
         case lr.state of
         1 :
           begin

             case lr.gete.key of
               f1 : lr.state := 2; //2
               s0 : lr.state :=  0; //
             end
           end;
         2 : lr.state := 1; //add cases to match function key assignments
         else
           lr.state := 0;
         end;
       end;
                                                  //SPECIAL EXIT STEP
     alast:
       begin
           retval := Xdone;
       end;
   end;
                                              //EXECUTE SECTION
   if act <> alsize then begin
     case lr.state of
     1 :                                      //DISPLAY STEP
       begin
         wrtlvl('Getesc');
         lblsinit;
         lblset (1, 'Show Form');   //2
         lblset (20, 'Exit');   //0
         lblson;

         wrtmsg ('Select Function key.');
         getesc (lr.gete, retval);             //INPUT STEP
       end;
                                                  //EXECUTE STEPS
     2 :  //enter action for each case as required
       begin //f1 - Form On
         PopUp := TPopupForm.create(popUp);
         popUp.showmodal;
         popUp.close;
         callNone(retval);
       end;
     else
       begin
         retval := XDone; //Don't go back to Aagain
         popup.free;
       end;
     end;
   end;
   Result := retval;
end; //Lvl2_doMenu


function Main(dcalstate : asint; act : action; p_LR, p_DR : Pointer) : wantType; stdcall;
begin
   case dcalstate of
      XMain : Result := Main_doMenu(act, p_LR, p_DR);
      //add dcalstate cases with menu calls as required
      else
         Result := XDone;//Necessary
   end;
end;

exports
  Main;

begin
end.

4. When compiled, this macro will now present a menu where function key F1 will call the PopUp form.

Note that your 'PopUp' form is declared as a variable in the Main_doMenu procedure. The actual Popup form object is implemented in Case lr.state of 2: in the EXECUTE STEP. The first line creates the form. This code creates an OOP Object called 'PopUp' from the definition of its class 'TPopupForm' using the argument (Popup) which makes it responsible for managing its own memory. The second line popUp.showmodal tells the form to show itself and receive user input. Control of the macro will not return to menu until the form is closed. Once the user chooses to close the form control returns to the next line of code PopUp.close which hides the form. The next line callNone(retval) directs the Datacad macro dispatcher to return to this menu in the next loop iteration. Finally, when the user elects to exit the macro the popup's life is ended using the command PopUp.free which deletes the Popup and releases the memory it used to the operating system.

That's it, your first Delphi Form in a macro. Next we will build on this macro to demonstrate other requirements forms should perform in your macro.
Last edited by Jsosnowski on Wed Nov 01, 2017 11:51 am, edited 4 times in total.
#72581 by Jsosnowski
Mon Oct 30, 2017 5:41 pm
Linking Macro Data to a Form

In several Parts of this series we have discussed the importance of passing data to various menus using pointers. In the sample code above the popup survives through multiple menu loops and so is subject to the risk that the user changes drawings between loops. The data for this macro instance therefore must be passed to Datacad in each iteration including to the PopUp Form. This is done by redefining the form.create function. There are several steps:

First you must create two pointer variables in the PopUp Form to hold the p_LR and p_DR pointer arguments that are handed back and forth between Datacad and the <name>_doMenu. In this example we use popLR & popDR which are defined as the same types as the arguments that hold the data in the library/unit file. Next you redefine the TPopupForm.create constructor function to receive the values for the two pointers.

Code: Select allunit popUpForm_u;
...
type
...
  TPopupForm = class(TForm)
    ...
  private
    { Private declarations }
    popLr : pMain_LR;
    popDr : p<name>_DR;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent;
        const loopR: pMain_LR; const DataR : pTable_DR);
  end;

Further down in the unit you define the new constructor code

Code: Select all 
...
implementation
...
constructor TPopupForm.Create(AOwner: TComponent;
        const loopR: pMain_LR; const Data : pTable_DR);
begin
  inherited Create(AOwner);
  popLr := loopR;
  popDr := DataR;
end;

Finally you change the the code in the Main_doMenu function that creates the popup to use the new constructor.

Code: Select all...
function Main_doMenu(act : action; p_LR, p_DR : Pointer) : wantType;
var
   retval : asint;
   lr : pMain_LR;
   dr : pMain_DR;
begin
  lr := p<name>_LR(p_LR);
  dr := addr(<name>Data); //use data record for macro
//above or below see D$D PRIMER - PART THREE
//   dr := p<name>_DR(p_DR); 
...
                                                  //EXECUTE STEPS
     2 :  //enter action for each case as required
       begin //f1 - Form On
         PopUp := TPopupForm.create(popUp, lr, dr); //<<<Revised constructor
         popUp.showmodal;
         popUp.close;
         callNone(retval);
       end;
...

That's it. The critical data is now accessible inside your popup under the variable names popLR and popDR.
Last edited by Jsosnowski on Wed Dec 27, 2017 12:41 pm, edited 4 times in total.
#72582 by Jsosnowski
Mon Oct 30, 2017 5:45 pm
Saving Form Data between uses ('*.ini' files)

So now you have an operating 'popUp' form in your macro and its time to do a little housekeeping. If you run your macro to display the popup and choose to move or resize the form on the screen, you may notice that the size and location are not preserved for the next time you load it. DCAL macros have the ability to retain variable values between uses, but Delphi does not. To retain this type of information it is necessary to save it to a file and as described in Part 6 '*.ini' files are an excellent means for doing so. Beginning with the 'PopUpSample' macro in the previous post, 'A Simple D4D Form', this post will demonstrate the steps for doing so.

The following code shows the completed PopUp Form unit incorporating the required additions:

Code: Select allunit popupForm2_u;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
{1} 
  iniFiles,
  UConstants,
  UDCLibrary,
  UInterfaces,
  UInterfacesRecords,
  URecords,
  UVariables;

type
  TPopupForm = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Popup: TPopupForm;
  popUpTop, popUpLeft, popUpHeight, popUpWidth : integer;  //form size and location  {2}

implementation
{$R *.dfm}

procedure PopUpShutDown;  {3}
var
  c : integer;
  path, fname : shortstring;
  iniFl: tIniFile;
begin
  //place the ini file in the Support directory of Datacad. use 'pathmcr' for macro directory.
  getpath (path, pathsup);
  fname := path + 'PopUpSample.ini';  //change the name for custom ini file.
  try
     iniFl :=  TIniFile.Create(fname);  //create the class object
    iniFl.writeinteger ('POPUP', 'Top', popUpTop);
    iniFl.writeinteger ('POPUP', 'Left', popUpLeft);
    iniFl.writeinteger ('POPUP', 'Width', popUpWidth);
    iniFl.writeinteger ('POPUP', 'Height', popUpHeight);

  finally
    iniFl.Free;
  end;

end;

procedure PopupStartUp;  {3}
var
  NDLen,
  c : integer;
  path, fname : str255;
  iniFl: tIniFile;
begin
  getpath (path, pathsup);  //places the ini file in the Support directory of Datacad. use pthmcr for macro directory.
  fname := path + 'PopUpSample.ini';  //change the name for custom ini file. cfilename declared in conTEXTUtil_u
  try
     iniFl :=  TIniFile.Create(fname);  //create the class object
    popUpTop := iniFl.ReadInteger('PopUp', 'Top', 100);
    popUpLeft := iniFl.ReadInteger('PopUp', 'Left', 100);
    popUpWidth := iniFl.ReadInteger('PopUp', 'Width', 600);
    popUpHeight := iniFl.ReadInteger('PopUp', 'Height', 300);
  finally
    iniFl.Free;
  end;
end;

procedure TPopupForm.Button1Click(Sender: TObject);
begin
  if Button1.Caption= 'Clicked'then
    Button1.caption := 'Unclicked'
  else
    Button1.caption := 'Clicked';

end;
initialization  {4}
  PopupStartUp;

finalization   {4}
  PopupShutdown;
end.

{1} Add the iniFiles unit to the 'uses' section of the unit code. In addition, make certain that the D4D standard units are included. Only uRecords is needed so far, but there is no penalty for including all of the units and as you add more features to the popup you will eventually need more of them.
{2} Add thefour variables listed to identify the location of the PopUp Form. These variables store current settings and will be updated at key points during operation of the macro as discussed below.
{3} Define a PopUpStartUp and PopUpShutdown procedure to write the current settings of the form to the 'PopUpSample.ini' file. Although a separate file is used in this sample, you can save them to a single macro 'ini' file along with any other settings needed to operate your macro.
{4} Enter the PopUpStartUp and PopUpShutdown procedures in the initialization and finalization sections of the unit to insure that they are loaded and saved at the start and end of the macro operation.

The preceding steps preserve settings in the four location variables defined in step {1]. The final step is to record those changes at the start and completion of the macro. That action is performed when the form is opened and closed in the PopUpSample1 Library unit. To do so the following code should be inserted in the Execute step Case 2.

Code: Select allfunction Main_doMenu(act : action; p_LR, p_DR : Pointer) : wantType;
    ...
                                                  //EXECUTE STEPS
     2 :  //enter action for each case as required
       begin //f1 - Form On
         PopUp := TPopupForm.create(popUp);

        //get previous popup location
         PopUp.Top := popUpTop;
         PopUp.Left := popUpLeft;
         PopUp.Width := popUpWidth;
         PopUp.Height := popUpHeight;

         popUp.showmodal;

         //store popup location
         popUpTop :=  popUp.Top;
         popUpLeft :=  popUp.Left;
         popUpWidth := popUp.width;
         popUpHeight := popUp.height;

         popUp.close;
         callNone(retval);
       end;
    ...   

Looking at one the first new line PopUp.Top := popUpTop; the form variable PopUp.top is set to value of the unit variable popUpTop located in the popUpForm_u.pas unit file. This value is is set to the value stored in the 'PopUpSample.ini' file when the macro is started as instructed in the procedure PopUpStartUp immediately after the form is created. The user can move and resize the form when it is shown during execution of the popUp.showmodal; line of code. When the user closes the form, execution moves to the next line of code which saves the values back into the unit variable popUp.Top. Finally, when the user closes the macro, the finalization code will execute the PopUpShutDown procedure saving the final form location settings to the 'PopUpSample.ini' file.
#72586 by Jsosnowski
Tue Oct 31, 2017 11:20 am
Saving Data to a File

Macros are often required to store data in files independent of Datacad. Previous posts have demonstrated how to place data in '*.ini' files. These files place the data in a text file that can be edited by the user independent of the macro. The Delphi class 'tiniFile' is very good at seeking specific values even even if the data is not provided in a specific order or is even missing (default values are substituted). However, for more complex data structures, the data must be placed in the file in a specific order and allowing the user to easily change values or order outside of the macro's designed interface is not acceptable. This post will demonstrate a simple methodology for reading and writing data files. This example uses a VCL program rather than a macro but the code used in the Button Click events can be applied within a macro as well.

Form Creation Steps:
1. Create a new Delphi VCL project and placing two tbuttons and a a tmemo on the form.
2. Change the caption of Button1 to 'Save' and button2 to 'Load'.
3. Create an onClick event for each button.
4. Enter the following code for the events:

Code: Select allunit DataFileDemo_u;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject); //Save
var  {1}
  fs: TFileStream;
  w:twriter;
 
  arrayString: Array of String; {2}
  i : integer;
  f : double;
  b : boolean;
  Len1, c: Cardinal;
 
begin
  //assign variable values {1}
  SetLength(arrayString, 4); {2}
  arrayString[0] := 'First string in this Array'; {3}
  arrayString[1] := 'the Second Array string';
  arrayString[2] := 'String number three of this Array';
  arrayString[3] := 'this is the fourth String';
  i := 5;
  f := 3.141;
  b := false;

  //save data to the file  {4}
  fs := TFileStream.Create('C:\Streamtest.tst',    //change file location as required
                 fmCreate or fmOpenWrite or fmShareDenyWrite);
  w := TWriter.create(fs, $ff);
  try {5}
    //declare the array size  {6}
    c := Length(arrayString);
    w.WriteInteger(c);
    //write the members of the array
    FOR c := 0 to High(arrayString) do begin
      w.WriteString(arrayString[c]);
    end;
    //write the remainder of the variables  {7}
    w.WriteInteger(i);
    w.WriteFloat(f);
    w.WriteBoolean(b);
  finally  {5}
    w.free;
    fs.Free;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject); //Load
var
  fs: TFileStream;
  r: tReader;
  arrayString: Array of String;
  Len1, c: Cardinal;

  s : string;
 
begin
  fs := TFileStream.Create('C:\Streamtest.tst',  //change file location as required
                 fmOpenRead or fmShareDenyWrite);
  r := TReader.create (fs,$ff);
  setlength (arrayString, 1);
  try
    len1 := r.ReadInteger;
    if (Len1 = 0) or (Len1 > 1000) then Exit;
    SetLength(arrayString, Len1);
    Memo1.lines.clear;
    FOR c := 0 to Len1-1 do begin
      s := r.ReadString;
      arraystring [c] := s;
      memo1.lines.Add(s);
    end;
    memo1.lines.Add(inttoStr(r.ReadInteger));
    memo1.lines.Add(FloatToStr(r.ReadFloat));
    memo1.lines.Add(BoolToStr(r.ReadBoolean));
  finally
    r.free;
    fs.Free;
  end;
end;

The logic of this code is simple, but there are a lot of details to unpack. Lets begin with procedure Button1Click;.
{1} This example is demonstrating how to save a variety of variable types including strings and arrays, so the first step is to declare some variables and assign them values.
{2} One of the variables we have created is an array of strings and although, in this example, we know there will be four strings, in practice, the size of the array may not be known beforehand so the array is declared as a dynamic, or unsized array. Once the procedure begins, the size is established in the SetLength(arrayString, 4); code line.
{3} With the array sized, the string values are assigned to the array along with the other varaibles used in the demo.
{4} To perform the actual file operations we create two Delphi classes to handle file operations, tfilestream and twriter.
{5} Both fs: TFileStream; and w:twriter are Delphi class objects and as such require special handling to insure that memory assigned to them is also destroyed when they are no longer needed. This is accomplished using the Delphi language try/finally code structure. First, the objects are created and then the'try' section of the code acts upon the objects that were created. Afterwards, the code in the 'Finally' section will be executed regardless of the success of the code in the 'try' section. This insures that the memory for the class objects is always freed whether the code above executes or crashes. When memory for a class is not freed it is referred to as a 'memory leak'. Eventually these leaks will bring your computer to a halt as it runs low on available memory.
{6} The first step inside the 'try' code section is to write the array of strings. This is accomplished by first writing the size of the array, and then creating a loop to write each member of the array.
{7} After the array, each other variable is written to the file.

Reading the file is accomplished in the TForm1.Button2Click(Sender: TObject); //Load procedure. The sequence of events in this code mirrors the code in TForm1.Button1Click(Sender: TObject); //Save procedure except using a 'tReader' class instead of a 'tWriter'. For demonstration purposes, each value is loaded into 'memo1' to display the results.

There are also a few other things to consider when planning the structure for managing a data file:

1. When naming a file consider that the extension for the file is used to identify the correct file type. To avoid errors, select uncommon file extension names. Many file extensions are three letters long, but this is not a requirement. Choose a longer, unique, file extension for your data to help users find the correct file type.
2. In addition to a good file extension provide an identifier as the first entry in the file. A simple integer value with an arbitrary high value can be used. Then as a first step reading the file Read the integer from the value. If the value is not the same as your specific file type value, then post a message and do not read the file.

Code: Select all...
procedure TForm1.Button1Click(Sender: TObject); //save
...var
  MacroID: integer
begin
  MacroID := 345,678;
  ...
 
  try
    w.WriteInteger(macroID);
   
    //Write the balance of the values to the file
  ...
end;

procedure TForm1.Button2Click(Sender: TObject); //Load
var
  MacroID : integer;
begin
  FileStream1 := TFileStream.Create('C:\Users\Joe\Documents\Delphi\Streamtest.tst',
                 fmOpenRead or fmShareDenyWrite);
  r := TReader.create (FileStream1,$ff); 
  MacroID := r.ReadInteger;
  If Not MacroID = 345,678 then begin
    showmessage ('Incorrect FileType Selected');
  end
  else begin
    try
      len1 := r.ReadInteger;
      if (Len1 = 0) or (Len1 > 1000) then Exit;
      SetLength(arrayString, Len1);
      Memo1.lines.clear;
      FOR c := 0 to Len1-1 do begin
        s := r.ReadString;
        arraystring [c] := s;
        memo1.lines.Add(s);
      end;
      memo1.lines.Add(inttoStr(r.ReadInteger));
      memo1.lines.Add(FloatToStr(r.ReadFloat));
      memo1.lines.Add(BoolToStr(r.ReadBoolean));
    finally
      r.free;
      FileStream1.Free;
    end;
  end;
end; 

Note that code in the try/finally section has been placed inside the test for the MacroID.
#72769 by Jsosnowski
Sun Nov 26, 2017 3:00 pm
Delphi Menus - A File Management Example

While D4D macros can display menu options in using the standard DCAL function key menu system, when a macro generates a Delphi form, the standard Datacad menu system is not easily accessible. This posting discusses how to generate menus in a Delphi form. While Delphi button components can be added to handle a few menu options, using Windows style menu bars are a better option for organizing many menu options for easy access. The Delphi tmenu class component provides a simple system for generating windows style menus with categories and drop down selection lists. One of the most familiar menu category is a 'File' dropdown containing options such as 'New', 'Open', 'Save', etc. This post will provide a sample program demonstrating how to create menus and manage typical file operation situations.

To begin this example create a new Delphi VCL form project and add a Tmenu and Tbutton component to the form. To define the menu items invoke the menu editor by double clicking on the Tmenu component. Delphi now displays a sample form with a blank menu bar. You begin adding menu items by selecting the dotted box to highlight it. If you check the Object Inspector you will find a blank TmenuItem. Enter the word 'File' for the Caption property to create your menu top level category item. Delphi will now focus on the drop down items and you can now add additional items as follows: &New, &Open, &Save, Save&As, &Close, and &Exit. Note the ampersands in each menu item word. The '&' tells Delphi to underscore the following letter in the menu and use it as a hot button key to invoke the menu action. The next step is to code the actions to be taken when each menu item is selected. Use the tmenu editor popup to select a menu option and select the Events tab in the Object Inspector. Next double click the 'OnClick' event to generate an action procedure for the menu item. Delphi will automatically create the declaration of a procedure to implement your action within.

The Tbutton is included in the program to simulate a change of status for a data file when it is modified. To set its initial condition select it and change the Caption to 'Change Data' and generate an onClick procedure for it as well.

After creating the form, components and onClick events edit your file to match the following code:
Code: Select allunit PopUpForm3_u;{1}

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;

type
  tPopUpDR = record  {2}
    fileName : string;
    isnew,  // flag that a new file has not yet been saved
    changed : boolean;  {flag that file data has been altered and requires saving before exit. Set this variable wherever data is changed in the macro.}
  end;
  pPopUpDR = ^tPopUpDR;

  TPopUpForm = class(TForm)
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    New1: TMenuItem;
    SaveAs1: TMenuItem;
    Close1: TMenuItem;
    Open1: TMenuItem;
    Save1: TMenuItem;
    Button1: TButton;
    procedure New1Click(Sender: TObject);
    procedure Open1Click(Sender: TObject);
    procedure Save1Click(Sender: TObject);
    procedure SaveAs1Click(Sender: TObject);
    procedure Close1Click(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Exit1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  PopUpForm: TPopUpForm;
  Data :  tPopUpDR;

procedure ReadDataFile (const fname:string);  {3}
procedure WriteDataFile(const fname:string);  {3}
procedure InitData;  {4}

implementation
{$R *.dfm}

// TPopUpForm procedures
procedure TPopUpForm.New1Click(Sender: TObject);  {5}
var
  selection :integer;
begin
  //check if current file requires saving
  if Data.changed then begin  {5}
    selection := MessageDlg('Save current project?', mtConfirmation, [mbYes,mbNO], 0);  {6}
    if selection = mrYes then begin
      if Data.isnew then begin    {7}
        SaveAs1Click(Self);
      end
      else begin
        WriteDataFile(Data.fileName);  //save current file
      end;
    end;
  end;

  initData;  //Reset the data structure to accept new data
  Data.isNew := true;
  Data.changed := false;

  Button1.Caption := 'Change Data';  //for example demonstration only
  showmessage ('New File Initiated');  //for example demonstration only
end;

procedure TPopUpForm.Open1Click(Sender: TObject);  {8}
var
  openDialog : topendialog;    // Open dialog variable {8}
begin
  //get file
  // Create the open dialog object - assign to our open dialog variable
  openDialog := TOpenDialog.Create(nil);
  try  {9}
    // Set up the starting directory to be the current one
    openDialog.InitialDir := ExtractFileDir (Data.fileName);

    // Only allow existing files to be selected
    openDialog.Options := [ofFileMustExist];

    // Allow only .dpr and .pas files to be selected
    openDialog.Filter := 'SampleData|*.myData';

    // Display the open file dialog
    if openDialog.Execute then begin
      New1Click(self); //save existing file if desired
      ReadDataFile (openDialog.FileName);
    end
    else begin
      ShowMessage('Open file was cancelled');
    end;
  finally
    openDialog.free;
  end;
end;

procedure TPopUpForm.Save1Click(Sender: TObject);  {10}
//automatic file save
begin
  if Data.isnew then begin
    //prompt to save (saveas)
    SaveAs1Click(self);

  end
  else if Data.changed then begin
    //save current file
    writeDataFile (Data.fileName);
    Data.isnew := false;  //no longer an unsaved data structure
    Button1.caption := 'Change Data';
  end;
end;

procedure TPopUpForm.SaveAs1Click(Sender: TObject); {11}
//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';

    // Set up the starting directory to be the current one
    saveDialog.InitialDir := ExtractFileDir (Data.fileName);
    //set the default file name
    saveDialog.FileName := ExtractFileName(Data.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  {12}
      WriteDataFile (saveDialog.FileName);
      Data.isnew := false;  //no longer a new, unsaved data structure
      Data.changed := false;
      Button1.caption := 'Change Data';
    end
    else begin
      ShowMessage('Save file was cancelled'); //for example display only - not required
    end;
  finally
    // Free up the dialog class object
    saveDialog.Free;
  end;
end;

procedure TPopUpForm.Close1Click(Sender: TObject);  {13}
begin
  New1Click(self);
end;

procedure TPopUpForm.Exit1Click(Sender: TObject);  {14}
begin
  New1Click;
  ModalResult:= mrOK;
end;

procedure TPopUpForm.Button1Click(Sender: TObject);
begin
  Data.changed := true;
  Button1.Caption := 'Data Changed';
end;


//Unit Procedures
procedure InitData;
begin
  Data.filename := 'C:\DataCAD 19\Support Files\Project1.myData';
  Data.isNew := true;
  Data.changed := false;
end;

procedure ReadDataFile (const fname:string);
begin
  //Example only code - insert read file code as required
  ShowMessage('Read File : ' + FName);
end;

Procedure WriteDataFile(const fname:string);
var
  FS: TFileStream;
  w:twriter;
begin
  FS := TFileStream.Create(data.fileName, fmCreate or fmOpenWrite or fmShareDenyWrite);
  w := TWriter.create(FS, $ff);
  try
    w.WriteString(Data.filename);
    ShowMessage(Fname + ' file written.');
  finally
    //free writer before filestream
    w.free;
    FS.free;

  end;
end; //WriteDataFile

initialization
  initData;
end.


{1} This example named the project PopUpForm3 and the code unit PopUpForm3_u.pas.
{2} Three pieces of information are needed to manage the File operation system. including the file name and two boolean values to track the status of the file. Isnew is used to determine if the project is new and has never been saved. It will be set to true whenever a new file is created and to false as soon as the data has been saved once. Changed is used to determine if the data has been modified since the last time it has been saved. In this example Button1 is used to set the value to true. In your macro you would set the value of Changed to true each time the data is modified.
{3} In addition to code for the events on the form, your macro requires procedures to save your data to a file and retrieve it. Other answers in this posting demonstrate how manage this. The procedures hear serve as surrogates for actual data file operations.
{4} It is always good practice to create an initialization procedure for each unit in your macro that contains data values. This procedure is usually invoked using the initialization section of the unit file so that it is always executed before the macro runs. This same procedure can be used to initialize data at any time when a new project file is begin created as will be demonstrated in the onclick events used in this example.

NEW
{5} The first 'onClick' event procedure is used to create a New project and the first step in the procedure is to check if the Data in the project has 'changed'. If nothing has changed then it will do nothing. However, if a change has occcurred it will take steps to save the data.
{6} The first step in saving the data is to prompt the user whether to save or abandon the changes. This is performed using the standard Delphi MessageDlg popup form. (A comprehensive discussion of its capabilities can be found online at http://www.delphibasics.co.uk/RTL.asp?Name=messagedlg. In this case, the dialog's arguments instruct it to query the user to Save the data, sets the style of the dialog to Confirm user input, and provide two buttons: Yes and No. Note that to use this Message form the file Vcl.Dialogs must be declared in the Uses section of the unit.
{7} If the user selects the Yes button in the dialog, then the procedure checks to see if the Data has ever been saved before using the isnew variable. If
isnew is true, the data has never been saved and the SaveAs1Click procedure is called to prompt the user for a name for the file. If isnew is false, the data is saved to the current file without additional user input.

OPEN
{8} Delphi provides a standard class object to provide the windows style popup dialog form users are familiar with for opening a project file. The variable declaration openDialog : topendialog; identifies the object which is created in the first code line of the procedure.
{9} Following standard Delphi good practice for using Delphi classes, a Try/Finally code structure is used to define and operate the OpenDialog object and then finally to remove it from memory using OpenDialog.free;. Always insure that your class objects will be removed from memory when finished. Failure to do so will leave memory leaks, sections of memory that are not accessible for other programs. An accumulation of these locked memory segments can eventually lead to problems for the operating system, leading to the possibility of program errors or crashes. The code in the Try section of the Try/Finally statement is used to instruct the dialog on how what directory and file extensions use. A full explanation for operation this dialog can be found at http://www.delphibasics.co.uk/RTL.asp?Name=topendialog.

SAVE
{10} The save option is straightforward. If isnew is true then the data does not have an assigned file and the user is prompted to give one using the SaveAs1Click procedure. If false, the data is saved to the current filename. The final step is to change the isnew and changed values to false.

SAVEAS
{11} Similar to the OpenDialog operation, this procedure creates a tSaveDialog object to prompt the user for a file name and location to save the macro data. Like the OpenDialog example, it uses the Try/Finally statement to insure proper handling of memory/ The Try section code determines the file and location options for the dialog. A full explanation of options can be found at http://www.delphibasics.co.uk/RTL.asp?Name=TSaveDialog.
{12} The dialog is invoked using the SaveDialog.executefunction to return a boolean value. If the value is true, then the user selected a file which can be accessed in the SaveDialog.filename field.

CLOSE
{13} Drop down file menus often include a 'Close' menu option. Functionally it is similar to New and the menu action is to call the New1Click procedure.

EXIT
{14} This option function simlar to Close except that after addressing the data file the popup is closed. Closing a form is achieved by assigning a non-zero value to the PopupForm.modalresult field. Delphi assigns certain constant values for modal results as can be reviewed at [urlhttp://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/Controls_TModalResult.html[/url]. In addition,, you can create any other non zero integer to be used by your macro to determine its next action.
#72770 by Jsosnowski
Mon Nov 27, 2017 3:12 pm
File Management Example - Extended

Edit: 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.

Who is online

Users browsing this forum: Google [Bot] and 6 guests

About DataCAD Forum

The DataCAD Forum is a FREE online community we provide to enhance your experience with DataCAD.

We hope you'll visit often to get answers, share ideas, and interact with other DataCAD users around the world.

DataCAD

Software for Architects Since 1984