Delphi is based on OOP concepts, and in particular on the definition of new class types. The use of OOP is partially enforced by the visual development environment, because for every new form defined at design time, Delphi automatically defines a new class. In addition, every component visually placed on a form is an object of a class type available in or added to the system library.
The terms class and object are commonly used and often misused, so let's be sure we agree on their definitions. A class is a user-defined data type, which has a state (its representation or internal data) and some operations (its behavior or its methods). An object is an instance of a class, or a variable of the data type defined by the class. Objects are actual entities. When the program runs, objects take up some memory for their internal representation. The relationship between object and class is the same as the one between variable and type.
As in most other modern OOP languages (including Java and C#), in Delphi a class-type variable doesn't provide the storage for the object, but is only a pointer or reference to the object in memory. Before you use the object, you must allocate memory for it by creating a new instance or by assigning an existing instance to the variable:
var Obj1, Obj2: TMyClass; begin // assign a newly created object Obj1 := TMyClass.Create; // assign to an existing object Obj2 := ExistingObject;
The call to Create invokes a default constructor available for every class, unless the class redefines it (as described later). To declare a new class data type in Delphi, with some local data fields and some methods, use the following syntax:
type TDate = class Month, Day, Year: Integer; procedure SetValue (m, d, y: Integer); function LeapYear: Boolean; end;
The convention in Delphi is to use the letter T as a prefix for the name of every class you write and every other type (T stands for Type). This is just a convention—to the compiler, T is just a letter like any other—but it is so common that following it will make your code easier for other developers to understand.
A method is defined with the function or procedure keyword, depending on whether it has a return value. Inside the class definition, methods can only be declared; they must be then defined in the implementation portion of the same unit. In this case, you prefix each method name with the name of the class it belongs to, using dot notation:
procedure TDate.SetValue (m, d, y: Integer); begin Month := m; Day := d; Year := y; end; function TDate.LeapYear: Boolean; begin // call IsLeapYear in SysUtils.pas Result := IsLeapYear (Year); end;
If you press Ctrl+Shift+C while the cursor is within the class definition, the Class Completion feature of the Delphi editor will generate the skeleton of the definition of the methods declared in a class.
This is how you can use an object of the previously defined class:
var ADay: TDate; begin // create an object ADay := TDate.Create; try // use the object ADay.SetValue (1, 1, 2000); if ADay.LeapYear then ShowMessage ('Leap year: ' + IntToStr (ADay.Year)); finally // destroy the object ADay.Free; end; end;
Notice that ADay.LeapYear is an expression similar to ADay.Year, although the first is a function call and the second a direct data access. You can optionally add parentheses after the call of a function with no parameters. You can find the previous code snippets in the source code of the Dates1 example; the only difference is that the program creates a date based on the year provided in an edit box.
The code snippet above uses a try/finally block to ensure the destruction of the object even in the case of exceptions in the code. You can find an introduction to the topic of exceptions at the end of this chapter.
There is a lot more to say about methods. Here are some short notes about the features available in Delphi:
Delphi supports method overloading. This means you can have two methods with the same name, provided that you mark the methods with the overload keyword and that the parameter lists of the two methods are sufficiently different. By checking the parameters, the compiler can determine which version you want to call.
Methods can have one or more parameters with default values. If these parameters are omitted in the method call, they will be assigned the default value.
Within a method, you can use the Self keyword to access the current object. When you refer to local data of the object, the reference to self is implicit. For example, in the SetValue method of the TDate class listed earlier, you use Month to refer to a field of the current object, and the compiler translates Month into Self.Month.
You can define class methods, marked by the class keyword. A class method doesn't have an object instance to act upon, because it can be applied to an object of the class or to the class as a whole. Delphi doesn't (currently) have a way to define class data, but you can mimic this functionality by adding global data in the implementation portion of the unit defining the class.
By default, methods use the register calling convention: (simple) parameters and return values are passed from the calling code to the function and back using CPU registers, instead of the stack. This process makes method calls much faster.
To emphasize the fact that Delphi components aren't much different from other objects (and also to demonstrate the use of the Self keyword), I've written the CreateComps example. This program has a form with no components and a handler for its OnMouseDown event, which I've chosen because it receives as a parameter the position of the mouse click (unlike the OnClick event). I need this information to create a button component in that position. Here is the method's code:
procedure TForm1.FormMouseDown (Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var Btn: TButton; begin Btn := TButton.Create (Self); Btn.Parent := Self; Btn.Left := X; Btn.Top := Y; Btn.Width := Btn.Width + 50; Btn.Caption := Format ('Button at %d, %d', [X, Y]); end;
The effect of this code is to create buttons at mouse-click positions, as you can see in Figure 2.1. In the code, notice in particular the use of the Self keyword as both the parameter of the Create method (to specify the component's owner) and the value of the Parent property. I'll discuss these two elements (ownership and the Parent property) in Chapter 4, "Core Library Classes."
When writing code like this, you might be tempted to use the Form1 variable instead of Self. In this specific example, that change wouldn't make any practical difference; but if there are multiple instances of a form, using Form1 would be an error. In fact, if the Form1 variable refers to the first form of that type being created, then by clicking in another form of the same type, the new button will always be displayed in the first form. The button's Owner and Parent will be Form1, not the form the user has clicked. In general, referring to a particular instance of a class when the current object is required is bad OOP practice.