their development environ- ment as they would use Visual creates a default global object for every form class you define. This is certainly handy for newcomers, way better to use a name describ- ing the effect of the method, not the attached component. For Basic [Editor throws his hands up in but can turn into a bad habit. example, the OnClick event of the horror at the mere thought!], with- btnAdd button can be named out realising and taking advantage Rule 1: One Class, One Unit AddToList. This makes the code of the power they have at their Always remember that the private more readable, particularly when hands. Delphi is based on an object and protected portions of a class you call the event handler from oriented architecture, which does are hidden only to classes and pro- another method of the class, and not only impact the VCL structure cedures in other units. Therefore, helps developers attach the same but also each and every Delphi if you want to have an effective method to multiple events or to application. encapsulation you should use a different components, although I In this article I don’t want to different unit for every class. For have to say that using Actions is cover the theory of OOP, but just simple classes, inheriting one from currently my preferred choice for suggest some simple rules which the other, you can actually use a non-trivial programs. might help you improve the struc- shared unit, but only if the number ture of your programs. These rules of classes is limited: Don’t place a Rule 4: Use Form Methods of thumb should be considered as 20-classes complex hierarchy in a If forms are classes their code is suggestions, to be applied or not single unit, even if Borland does it collected in methods. Besides the depending on the actual type of in the VCL source code... event handlers, which play a spe- application you are building. My If you think about forms, Delphi cial role but can still be called as suggestion is simply to keep them follows the ‘one class, one unit’ other methods, it is often useful to in mind. principle by default, which is add custom methods to form The key principle I want to certainly handy. When adding classes. You can add methods per- underline is encapsulation. We non-form classes to a project, forming actions and accessing to want to create flexible and robust create new separate units. the status of the form. It is much classes, which will allow us to better to add a public method to a change the implementation later Rule 2: Name Components form than to let other forms on without affecting the rest of the It is very important to give a mean- operate on its components program. This is not the only ingful name to each form and each directly. criterion for good OOP, but it unit. Unluckily the two names must represents the foundation, so if I be different, although I tend to use Rule 5: actually over-stress it in this article similar names for the two, such as Add Form Constructors I have some good reasons to do so. AboutForm and About.pas. A secondary form created at Finally, to underline the fact that It’s important to use descriptive runtime can provide other specific these principles should be used in names for components too. The constructors beside the default our daily work by all of us Delphi most common notation is to use a one (inherited form the TComponent programmers, I’m going to focus few lower case initial letters for the class). If you don’t need compati- mainly on the development of class type, followed by the role of bility with versions of Delphi prior forms, even if some of the rules the component, as in btnAdd or to 4, my suggestion is to overload equally apply to the development editName. There are actually many the Create method, adding the of components. Those who write similar notations following this required initialisation parameters. components must consider OOP style and there is really no reason Listing 1 gives an example. and classes as a central element. to say any one of them is best, it’s Those who use components at up to your personal taste. Rule 6: times forget about OOP: this article Avoid Global Variables can be considered as a reminder. Rule 3: Name Events Global variables (that is, variables It is even more important to give declared in the interface portion Part 1: A Form Is A Class proper names to event handling of a unit) should be avoided. Here Programmers usually treat forms methods. If you name the compo- are a few suggestions to help you as objects, while in fact they are nents properly, the default name do this. classes. The difference is that you of Button1Click, for example, If you need extra data storage for can have multiple form objects becomes btnAddClick. Although we a form, add some private fields to based on the same form class. The can guess what the method does it. In this case each form instance confusing thing is that Delphi from the button name, I think it is will have its own copy of the data.
8 The Delphi Magazine Issue 47
You might use unit variables public constructor Create (Text: string); reintroduce; overload; (declared in the implementation constructor TFormDialog.Create(Text: string); portion of the unit) for data shared begin inherited Create (Application); among multiple instances of the Edit1.Text := Text; end; form class. If you need data shared among ➤ Listing 1 forms of different types, you can share them by placing the data in the main form, or in a global object, private and use methods or properties to function GetText: String; procedure SetText(const Value: String); access the data. public property Text: String read GetText write SetText; Rule 7: function TFormDialog.GetText: String; begin Never Use Form1 In TForm1 Result := Edit1.Text; end; You should never refer to a specific procedure TFormDialog.SetText(const Value: String); object in a method of the class of begin Edit1.Text := Value; that object. In other words, never end; refer to Form1 in a method of the TForm1 class. If you need to refer to ➤ Listing 2: You can add a property to a form to expose a property of the current object, use the self a component. keyword. Keep in mind that most of the time this is not needed, as you can refer directly to methods and and the global object anymore. In Suppose you now change the data of the current object. fact, after the global object has user interface, replacing the com- If you don’t follow this rule, been removed, any reference to it ponent with another one. All you you’ll get into trouble when you will result in an error. have to do is fix the Get and Set create multiple instances of the methods related with the prop- form. Rule 10: Add Form Properties erty, you won’t have to check and As I’ve already mentioned, when modify the source code of all the Rule 8: Seldom Use you need data for a form, add a pri- forms and classes which might Form1 In Other Forms vate field. If you need to access this refer to that component. You can Even in the code of other forms, try data from other classes, then add see an example in Listing 2. to avoid direct references to global properties to the form. With this objects, such as Form1. It is much approach you will be able to Rule 12: Array Properties better to declare local variables or change the code of the form and its If you need to handle a series of private fields to refer to other data (including its user interface) values in a form, you can declare forms. without having to change the code an array property. In case this is an For example, the main form of a of other forms or classes. important information for the form program can have a private field You should also use properties you can make it also the default referring to a dialog box. Obviously or methods to initialise a second- array property of the form, so that this rule becomes essential if you ary form or dialog box, and to read you can directly access its value by plan creating multiple instances of its final state. The initialisation writing SpecialForm[3]. the secondary form. You can keep can also be performed using a Listing 3 shows how you can a list in a field of the main form, or constructor, as I have already expose the items of a listbox as the simply use the Forms array of the described. default array property of the form global Screen object. hosting it. Rule 11: Expose Rule 9: Remove Form1 Components Properties Rule 13: Actually, my suggestion is to When you need to access the Use Side-Effects In Properties remove the global form object status of another form, you should Remember that one of the advan- which is automatically added by not refer directly to its compo- tages of using properties instead of Delphi to the program. This is pos- nents. This would bind the code of accessing global data is that you sible only if you disable the auto- other forms or classes to the user can cause side-effects when writ- matic creation of that form (again interface, which is one of the por- ing (or reading) the value of a added by Delphi), something tions of an application subject to property. which I suggest you should get rid most changes. Rather, declare a For example, you can draw of anyway. form property mapped to the com- directly on the form surface, set I think that removing the global ponent property: this is accom- the values of multiple properties, form object is very useful for plished with a Get method that call special methods, change the Delphi newcomers, who then won’t reads the component status and a status of multiple components at get confused between the class Set method that writes it. once, or fire an event, if available.
10 The Delphi Magazine Issue 47
type this call even if it is not required, as TFormDialog = class(TForm) an extra call to the RegisterClasses private ListItems: TListBox; method is harmless. The Register- function GetItems(Index: Integer): string; procedure SetItems(Index: Integer; const Value: string); Classes method is usually added to public the initialization section of the property Items[Index: Integer]: string read GetItems write SetItems; default; end; unit hosting the form: function TFormDialog.GetItems(Index: Integer): string; begin if Index >= ListItems.Items.Count then RegisterClasses([TEdit]); raise Exception.Create('TFormDialog: Out of Range'); Result := ListItems.Items [Index]; end; Rule 15: procedure TFormDialog.SetItems(Index: Integer; const Value: string); begin The OOP Form Wizard if Index >= ListItems.Items.Count then raise Exception.Create('TFormDialog: Out of Range'); Repeating the two operations ListItems.Items [Index] := Value; above for every component of end; every form is certainly boring and time consuming. To avoid this ➤ Listing 3: The definition of a default array property in a form. excessive burden, I’ve written a simple wizard which generates the procedure TComponent.SetReference(Enable: Boolean); lines of code to add to the program var in a small window. You’ll need to Field: ^TComponent; begin do two simple copy and paste if FOwner <> nil then begin Field := FOwner.FieldAddress(FName); operations for each form. if Field <> nil then if Enable then The wizard doesn’t automati- Field^ := Self cally place the source code in the else Field^ := nil; proper location: I’m working to fix end; end; this and you can check my website (www.marcocantu.com) for an updated version. ➤ Listing 4: The VCL code used to hook a component to its reference in the owner form. Part 2: Inheritance After a first set of rules devoted to Rule 14: Hide Components Delphi uses RTTI and TObject classes, and particularly form Too often I hear OOP purists com- methods to perform this. classes, here comes another short plaining because Delphi forms If you want to understand the list of suggestions and tips related include the list of the components details, refer to Listing 4, which has to inheritance and visual form in the published section, an the code of the SetReference inheritance. approach that doesn’t conform to method of the TComponent class, the principle of encapsulation. which is called by InsertComponent, Rule 16: They are actually pointing out an RemoveComponent and SetName. Visual Form Inheritance important issue, but most of them Once you know this, you realise This is a powerful mechanism, if seem to be unaware that the solu- that by moving the component ref- used properly. From my experi- tion is at hand without rewriting erences from the published to the ence, its value grows with the size Delphi or changing the language. private section you lose this auto- of the project. In a complex pro- The component references matic behaviour. To fix the prob- gram you can use the hierarchical which Delphi adds to a form can be lem, simply make it manual, by relationship among forms to moved to the private portion, so adding the following code for each operate on groups of forms with that they won’t be accessible by component in the OnCreate event polymorphism. other forms. This way you can handler of the form: Visual form inheritance allows make compulsory the use of prop- you to share the common behav- erties mapped to the components Edit1 := FindComponent(‘Edit1’) iour of multiple forms: you can (see Rule 11 above) to access their as TEdit; have common methods, proper- status. ties, event handlers, components, If Delphi places all the compo- The second operation you have to component properties, compo- nents in the published section, this do is register the component nent event handlers, and so on. is because of the way these fields classes in the system, so that their are bound to the components cre- RTTI information is included in the Rule 17: Limit Protected Data ated from the .DFM file. When you compiled program and made avail- When building a hierarchy of set a component’s name the VCL able to the system. This is needed classes, some programmers tend automatically attaches the compo- only once for every component to use mainly protected fields, as nent object to its reference in the class, and only if you move all the private fields are not accessible by form. This is possible only if the component references of this type subclasses. I won’t say this is reference is published, because to the private section. You can add always wrong, but it is certainly
14 The Delphi Magazine Issue 47
against encapsulation. The imple- methods you can call from the the form. Otherwise the initialis- mentation of protected data is external classes to obtain ation code in the form constructor shared among all inherited forms, polymorphism. If this is a common (which uses the components) will and you might have to update all of approach, it is less frequent to see be executed before the OnCreate them in case the original definition protected virtual methods, called method of the form, which con- of the data changes. by other public methods. This is an nects the references to the actual Notice that if you follow the rule important technique, as you can components. of hiding components (Rule 14) the customise the virtual method in a On the disk you’ll also find the inherited forms can’t possibly derived class, modifying the compiled package of a first draft access the private components of behaviour of the objects. version of the OOP Form Wizard, the base class. In an inherited form, but you should (hopefully) be able code such as Edit1.Text := ‘’; will Rule 20: Virtual to find a more complete version on not be compiled anymore. I can see Methods For Properties my website. this might not be terribly handy, Even property access methods can but at least in theory it should be be declared as virtual, so that a Conclusion regarded as a positive thing, not derived class can change the Programming in Delphi according negative. If you feel this is too behaviour of the property without to good OOP principles is far from much of a concession to encapsu- having to redefine it. This obvious, as some of the rules I’ve lation, declare the component ref- approach is seldom used by the listed highlight. I don’t think that erences in the protected section of VCL but is very flexible and power- you should consider all of my rules the base form. ful. To accomplish this, simply compulsory, as some of them declare as virtual the Get and Set might stretch your patience. The Rule 18: methods of Rule 11. The base form rules should be applied in the Protected Access Methods will have the code of Listing 5. proper context, and become more It is much better, instead, to keep In the inherited form you can and more important as the size of the component references in the now override the virtual method the application grows, along with private section and add access SetText, to add some extra the number of programmers work- functions to their properties to the behaviour: ing on it. Even for smaller pro- base class. If these access func- grams, however, keeping in mind tions are used only internally and procedure TFormInherit.SetText( the OOP principles underlying my are not part of the class interface, const Value: String); rules (encapsulation before all you should declare them as pro- begin others) can really help. tected. For example, the GetText inherited SetText (Value); There are certainly many other and SetText form methods if Value = ‘’ then rules of thumb you can come up described in Rule 11 can become Button1.Enabled := False; with, as I haven’t tried to get into protected and we could access the end; memory handling and RTTI issues, edit text by calling: which are so complex to deserve The Code specific articles. SetText(‘’); All the code fragments in this arti- My conclusion is that following cle can be found in the OopDemo the rules I’ve highlighted has a Actually, as the method was example project, included on this cost, in terms of extra code: it is mapped to a property, we can month’s disk. You should check in the price you have to pay to obtain simply write: particular the secondary form (in a more flexible and robust pro- the frm2 unit) and the derived one gram. It is the price of object ori- Text := ‘’; (in the inher unit). Notice that in ented programming. Let’s hope order to use, at the same time, a that future Delphi versions help us Rule 19: custom constructor with initialis- reduce that price. Protected Virtual Methods ation code and the private compo- Another key point to have a flexible nent references, it is necessary to hierarchy is to declare virtual set the OldCreateOrder property of Marco Cantù is the author of the Mastering Delphi series, Delphi Developer’s Handbook, and of ➤ Listing 5: A form with properties implemented with virtual methods. the free online book Essential type Pascal. He teaches classes on TFormDialog = class(TForm) Delphi foundations and advanced procedure FormCreate(Sender: TObject); private topics. Check his website at Edit1: TEdit; protected www.marcocantu.com for more function GetText: String; virtual; procedure SetText(const Value: String); virtual; information. You can reach him public on his public newsgroups: see the constructor Create (Text: string); reintroduce; overload; property Text: String read GetText write SetText; website for details. end;