This topic describes the following material:
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.
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 propertyName[indexes]: type index integerConstant specifiers;
Every property has a read specifier, a write specifier, or both. These are called access specifiers and they have the form
where fieldOrMethod is the name of a field or method declared in the same class as the property or in an ancestor class.
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;
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 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 Name: string]: 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 Name: string): 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 = nil then Exit; Canvas.Pixels[10, 20] := clRed; Params.Values['PATH'] := 'C:\BIN';
if Collection.GetObject(0) = nil then Exit; Canvas.SetPixel(10, 20, 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 can be abbreviated to StringArray. 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 descendant classes may lead to unexpected behavior, since the compiler always binds to properties statically.
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..3] of 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;
Rectangle.SetCoordinate(2, Rectangle.GetCoordinate(0) + 100);
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.
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 descendant 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 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:
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;
Copyright(C) 2009 Embarcadero Technologies, Inc. All Rights Reserved.
What do you think about this topic? Send feedback!