Properties

Top  Previous  Next

A property, like a field, defines an attribute of an object. But while a field is merely a storage location whose contents can be examined and changed, a property associates specific actions with reading or modifying its data. Properties provide control over access to an object's attributes, and they allow attributes to be computed.

Properties allow to encapsulate with a field-like syntax what can actually be methods (getter/setter).

The declaration of a property specifies a name and a type, and includes at least one access specifier. The syntax of a property declaration is:

property Name[args : Type] : Type read Getter write Setter; default;

 

·a property can optionally have arguments/indexes, such properties can optionally be marked as default
·the Getter can be a method returning, a field, another property, or an expression (enclosed between brackets)
·the Setter can be a method, a field, another property or an expression (enclosed between brackets, with Value being the pseudo-variable that receives the value assigned to the property)

The following form property Name : Type; declares a property backed by a hidden (inaccessible) field. The field can optionally be initialized.

The following form property Name; can be used to promote the visibility of a property without altering it in any other way, for instance to make public a property that was previously protected.

Properties can also be prefixed by 'class' in which case they will be restricted to class variables, class methods and class properties, but can then be used on the class type (metaclass) and not just on instance.

 

Property Access

 

Every property has a read specifier, a write specifier, or both.

For example, given the declaration:

 property Color: TColor read GetColor write SetColor;

 

the GetColor method must be declared as:

 

 function GetColor: TColor;

 

 

and the SetColor method must be declared as one of these:

 procedure SetColor(Value: TColor);

 procedure SetColor(const Value: TColor);

 

 

(The name of SetColor's parameter, of course, doesn't have to be Value.)

When a property is referenced in an expression, its value is read using the field or method listed in the read specifier. When a property is referenced in an assignment statement, its value is written using the field or method listed in the write specifier.

The example below declares a class called TCompass with a published property called Heading. The value of Heading is read through the FHeading field and written through the SetHeading procedure:

 type

    THeading = 0..359;

    TCompass = class(TControl)

      private

         FHeading: THeading;

         procedure SetHeading(Value: THeading);

      published

         property Heading: THeading read FHeading write SetHeading;

         ...

     end;

 

Given this declaration, the statements:

 if Compass.Heading = 180 then GoingSouth;

 Compass.Heading := 135;

 

correspond to:

 if Compass.FHeading = 180 then GoingSouth;

 Compass.SetHeading(135);

 

 

In the TCompass class, no action is associated with reading the Heading property; the read operation consists of retrieving the value stored in the FHeading field. On the other hand, assigning a value to the Heading property translates into a call to the SetHeading method, which, presumably, stores the new value in the FHeading field as well as performing other actions. For example, SetHeading might be implemented like this:

 procedure TCompass.SetHeading(Value: THeading);

 begin

   if FHeading <> Value then

   begin

     FHeading := Value;

     Repaint;    // update user interface to reflect new value     

   end;

 end;

 

 

A property whose declaration includes only a read specifier is a read-only property, and one whose declaration includes only a write specifier is a write-only property. It is an error to assign a value to a read-only property or use a write-only property in an expression.

 

Array Properties

 

Array properties are indexed properties. They can represent things like items in a list, child controls of a control, and pixels of a bitmap.

The declaration of an array property includes a parameter list that specifies the names and types of the indexes. For example:

 property Objects[Index: Integer]: TObject read GetObject write SetObject;

 property Pixels[X, Y: Integer]: TColor read GetPixel write SetPixel;

 property Values[const Namestring]: string read GetValue write SetValue;

 

 

The format of an index parameter list is the same as that of a procedure's or function's parameter list, except that the parameter declarations are enclosed in brackets instead of parentheses. Unlike arrays, which can use only ordinal-type indexes, array properties allow indexes of any type.

 

For array properties, access specifiers must list methods rather than fields. The method in a read specifier must be a function that takes the number and type of parameters listed in the property's index parameter list, in the same order, and whose result type is identical to the property's type. The method in a write specifier must be a procedure that takes the number and type of parameters listed in the property's index parameter list, in the same order, plus an additional value or const parameter of the same type as the property.

 

For example, the access methods for the array properties above might be declared as:

 function GetObject(Index: Integer): TObject;

 function GetPixel(X, Y: Integer): TColor;

 function GetValue(const Namestring): string;

 procedure SetObject(Index: Integer; Value: TObject);

 procedure SetPixel(X, Y: Integer; Value: TColor);

 procedure SetValue(const Name, Value: string);

 

 

An array property is accessed by indexing the property identifier. For example, the statements:

 if Collection.Objects[0] = nil then Exit;

 Canvas.Pixels[1020] := clRed;

 Params.Values['PATH'] := 'C:\BIN';

 

correspond to:

 if Collection.GetObject(0) = nil then Exit;

 Canvas.SetPixel(1020, clRed);

 Params.SetValue('PATH''C:\BIN');

 

 

The definition of an array property can be followed by the default directive, in which case the array property becomes the default property of the class. For example:

 type

    TStringArray = class

     public

        property Strings[Index: Integer]: string ...; default;

           ...

     end;

 

 

If a class has a default property, you can access that property with the abbreviation object[index], which is equivalent to object.property[index]. For example, given the declaration above, StringArray.Strings[7] can be abbreviated to StringArray[7]. A class can have only one default property with a given signature (array parameter list), but it is possible to overload the default property. Changing or hiding the default property in descendent classes may lead to unexpected behavior, since the compiler always binds to properties statically.

 

 

Index Specifiers

 

Index specifiers allow several properties to share the same access method while representing different values. An index specifier consists of the directive index followed by an integer constant between -2147483647 and 2147483647. If a property has an index specifier, its read and write specifiers must list methods rather than fields. For example:

 

 type

    TRectangle = class

      private

        FCoordinates: array[0..3of Longint;

        function GetCoordinate(Index: Integer): Longint;

        procedure SetCoordinate(Index: Integer; Value: Longint);

      public

        property Left: Longint index 0  read GetCoordinate 

                                        write SetCoordinate;

        property Top: Longint index 1   read GetCoordinate 

                                        write SetCoordinate;

        property Right: Longint index 2 read GetCoordinate

                                        write SetCoordinate;

        property Bottom: Longint index 3 read GetCoordinate

                                         write SetCoordinate;

        property Coordinates[Index: Integer]: Longint 

                                        read GetCoordinate

                                        write SetCoordinate;

        ...

    end;

 

 

An access method for a property with an index specifier must take an extra value parameter of type Integer. For a read function, it must be the last parameter; for a write procedure, it must be the second-to-last parameter (preceding the parameter that specifies the property value). When a program accesses the property, the property's integer constant is automatically passed to the access method.

Given the declaration above, if Rectangle is of type TRectangle, then:

 

 Rectangle.Right := Rectangle.Left + 100;

 

corresponds to:

 Rectangle.SetCoordinate(2, Rectangle.GetCoordinate(0) + 100);

 

 

 

Storage Specifiers

 

The optional stored, default, and nodefault directives are called storage specifiers. They have no effect on program behavior, but control whether or not to save the values of published properties in form files.

The stored directive must be followed by True, False, the name of a Boolean field, or the name of a parameterless method that returns a Boolean value. For example:

 

 property Name: TComponentName read FName write SetName stored False;

 

 

If a property has no stored directive, it is treated as if stored True were specified.

The default directive must be followed by a constant of the same type as the property. For example:

 

 property Tag: Longint read FTag write FTag default 0;

 

To override an inherited default value without specifying a new one, use the nodefault directive. The default and nodefault directives are supported only for ordinal types and for set types, provided the upper and lower bounds of the set's base type have ordinal values between 0 and 31; if such a property is declared without default or nodefault, it is treated as if nodefault were specified. For reals, pointers, and strings, there is an implicit default value of 0, nil, and '' (the empty string), respectively.

 

Note: You can't use the ordinal value 2147483648 has a default value. This value is used internally to represent nodefault.

 

When saving a component's state, the storage specifiers of the component's published properties are checked. If a property's current value is different from its default value (or if there is no default value) and the stored specifier is True, then the property's value is saved. Otherwise, the property's value is not saved.

 

Note: Property values are not automatically initialized to the default value. That is, the default directive controls only when property values are saved to the form file, but not the initial value of the property on a newly created instance.

 

Storage specifiers are not supported for array properties. The default directive has a different meaning when used in an array property declaration. See Array Properties, above.

 

 

Property Overrides and Redeclarations

 

A property declaration that doesn't specify a type is called a property override. Property overrides allow you to change a property's inherited visibility or specifiers. The simplest override consists only of the reserved word property followed by an inherited property identifier; this form is used to change a property's visibility. For example, if an ancestor class declares a property as protected, a derived class can redeclare it in a public or published section of the class. Property overrides can include read, write,stored, default, and nodefault directives; any such directive overrides the corresponding inherited directive. An override can replace an inherited access specifier, add a missing specifier, or increase a property's visibility, but it cannot remove an access specifier or decrease a property's visibility. An override can include an implements directive, which adds to the list of implemented interfaces without removing inherited ones.

 

The following declarations illustrate the use of property overrides:

 type

    TAncestor = class

        ...

      protected

        property Size: Integer read FSize;

        property Text: string read GetText write SetText;

        property Color: TColor read FColor write SetColor stored False;

        ...

    end;

 

 type

 

    TDerived = class(TAncestor)

        ...

      protected

        property Size write SetSize;

      published

        property Text;

        property Color stored True default clBlue;

        ...

    end;

 

 

The override of Size adds a write specifier to allow the property to be modified. The overrides of Text and Color change the visibility of the properties from protected to published. The property override of Color also specifies that the property should be filed if its value isn't clBlue.

A redeclaration of a property that includes a type identifier hides the inherited property rather than overriding it. This means that a new property is created with the same name as the inherited one. Any property declaration that specifies a type must be a complete declaration, and must therefore include at least one access specifier.

Whether a property is hidden or overridden in a derived class, property look-up is always static. That is, the declared (compile-time) type of the variable used to identify an object determines the interpretation of its property identifiers. Hence, after the following code executes, reading or assigning a value to MyObject.Value invokes Method1 or Method2, even though MyObject holds an instance of TDescendant. But you can cast MyObject to TDescendant to access the descendent class's properties and their access specifiers:

 

 type

     TAncestor = class

       ...

       property Value: Integer read Method1 write Method2;

     end;

 

     TDescendant = class(TAncestor)

       ...

       property Value: Integer read Method3 write Method4;

     end;

 

  var MyObject: TAncestor;

       ...

      MyObject := TDescendant.Create;

 

 

 

Class Properties

 

Class properties can be accessed without an object reference. Class property accessors must themselves be declared as class static methods, or class fields. A class property is declared with the class property keywords. Class properties cannot be published, and cannot have stored or default value definitions.

 

You can introduce a block of class static fields within a class declaration by using the class var block declaration. All fields declared after class var have static storage attributes. A class var block is terminated by the following:

·Another class var declaration
·A procedure or function (i.e. method) declaration (including class procedures and class functions)
·A property declaration (including class properties)
·A constructor or destructor declaration
·A visibility scope specifier (public, private, protected, published, strict private, and strict protected)

 

For example:

 type

    TMyClass = class

      strict private

        class var         // Note fields must be declared as class fields

           FRed: Integer;

           FGreen: Integer;

           FBlue: Integer;

        public             // ends the class var block

           class property Red: Integer read FRed write FRed;

           class property Green: Integer read FGreen write FGreen;

           class property Blue: Integer read FBlue write FBlue;

    end;

 

You can access the above class properties with the code:

 

TMyClass.Red := 0;

 TMyClass.Blue := 0;

 TMyClass.Green := 0;