Topic Title

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.

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.

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:

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.

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.

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.

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:

{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.

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.

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:

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.

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:

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;

{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.

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.

//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);

{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.