__property
Go Up to C++ Keyword Extensions
In this section, you will see C++ Builder’s support for the __property keyword, a key element in the property-method-event (PME) model used for UI creation in C++.
A property appears like a field in terms of syntax when is accessed in code, but reading or writing can be backed by a field or method. This allows reading and writing to have side effects, such as lazy evaluation when reading or invalidating data when writing.
Here is a simple example of a property, which reads from a field called mX
, and writes via a setX()
method:
int mX;
void setX(int value);
__property int X = {read = mX, write = setX};
When it is used in code, has field-like syntax:
int foo = myObject->X;
myObject->X = 5;
The first line, reading the property, reads from the mX
field. Because in this example the write access specifier uses a method, the second line assigning a value to the property invokes the setX
method passing the value to the method. It is up to the method to take action: that is, if you want the mX
field to truly be updated when setting (writing to) the property, the method must set the value of mX
.
Component developers usually use properties. Properties provide a convenient way to handle UI controls, both in terms of:
- Behavior: assigning values, where a UI control may need to perform an action when a value is assigned.
- Streaming: saving and restoring a set of UI controls and their values.
While you can use properties in any C++ code, you will most likely only interact with them through writing code that uses VCL or FMX user interfaces. For such codes, while it is good to understand how a property works, you can use them effectively by treating them as field-like access and considering that you cannot get the address of or reference to a property.
Properties are the key to UI design, and properties using the __published specifier are shown in the Object Inspector when building a user interface and can be set in the Object Inspector at design time or in code at runtime.
For UI design, properties with a closure type - an object-oriented method pointer - are used to hold events.
C++Builder’s properties have many other features, such as indexed access, default values when streaming, and more, making them a very powerful language feature.
Contents
__property Syntax
The declaration of the __property specifies a name and a type, also includes at least one access specifier. The syntax of a property declaration is the following:
__property type propertyName = { attributes };
or for an indexed property,
__property type propertyName [index1Type index1 ][indexNType indexN ] = { attributes };
where:
type
is a built-in or previously declared data type.propertyName
is any valid identifier.indexNType
is an built-in or previously declared data type.indexN
is the name of an index parameter
- Note: The indexN parameters in square brackets are optional. If present, they declare an array property.
attributes
is a comma-separated sequence ofread
,write
,stored
,index
,default
(ornondefault
), orimplements
specifiers.
- Note: Every property declaration must have at least one read or write specifier.
Let’s analyze the examples below, assuming the following fields and methods in a class:
private:
int readX;
void writeX(int Value);
double GetZ();
float cellValue(int row, int col);
void setCellValue(int row, int col, float Value);
int GetCoordinate(int Index);
void SetCoordinate(int Index, int Value);
The following property declarations are valid:
// Standard, common property declaration, reading from a field and writing via a method:
__property int X = {read = readX, write = writeX};
// A read-only property (note the absence of the write specifier.)
__property double Z = {read = GetZ};
// An indexed property - note two indices in use:
__property float Cells[int row][int col] = {read = cellValue,
write = setCellValue};
// Redeclaring a property declared in an ancestor class (used for redeclaring in a wider visibility scope, such as bringing an ancestor protected property to [[public]] or [[__published]] scope):
__property Foo;
// Wrapping an indexed method into a simple property:
__property int Left = { index = 0, read = GetCoordinate,
write = SetCoordinate };
// Another indexed method wrapped to a simple property, this time with specifiers used for streaming:
__property int Top = { read = GetCoordinate,
write = SetCoordinate, index = 1, stored = true, default = 5 };
There are some restrictions for properties keyword:
- You cannot get the property’s address.
- You cannot pass a property by reference.
Since it is a syntax feature that may be backed by either a field or method.
___property access
A __property declaration must have read, write, or both specifiers. These are called access specifiers and have the following form:
read = _ // member
write = _ // member
member
can be a field or method, usually private or protected but you can locate it in any visibility. When using the read
specifier with a method, the method returns the same type as property with no parameters.
When using a write
specifier with a method, the method returns void and it has a single parameter of the same type, plus parameters for any array indices.
Array Properties
Array properties are indexed properties. They can represent things like items in a list, child controls of 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.
__property type PropertyName[IndexType IndexName]
Multiple array declarations may follow each other for a multiply-indexed property:
__property type PropertyName[IndexTypeA IndexNameA][IndexTypeB IndexNameB]
Unlike arrays, array properties allow indexes of any type. For array properties, access specifiers must list methods rather than fields:
- The method in a
read
andwrite
specifiers must be a function that takes the number and type of the property's parameter list, in the same order. - When using the
read
specifier, the method’s result type is identical to the property's type. - When using the
write
specifier, you must add an additional value or const parameter of the same type as the property.
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. For example:
class TRectangle {
private:
float FCoordinates[4];
protected:
float GetCoordinate(int index){
return FCoordinates[index];
}
void SetCoordinate(int index, float value){
FCoordinates[index]=value;
}
public:
__property int Left = {read = GetCoordinate, write = SetCoordinate, index = 0};
__property int Top = {read = GetCoordinate, write = SetCoordinate, index = 1};
__property int Right = {read = GetCoordinate, write = SetCoordinate, index = 2};
__property int Bottom = {read = GetCoordinate, write = SetCoordinate, index = 3};
__property int Coordinates[int index] = {read = GetCoordinate, write = SetCoordinate};
};
If a property has an index specifier, its read
and write
specifiers must list methods rather than fields.
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, there 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.
Storage specifiers
The optional stored
, default
, and nodefault
directives, known as storage specifiers, do not affect program behavior but control whether or not to save the values of published properties in form files. Streaming VCL and FMX controls are managed by the RTL. Use these parameters when developing VCL or FMX components.
Stored
This directive is used by the form streaming system. By default, all properties are streamed to a .dfm file
. This allows you to conditionally or always omit a property from being streamed. You cannot force a property to be streamed by setting stored to true
, only cause it to not be streamed by setting it to false
.
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.
stored = [Boolean]
If a property has no stored directive, it is treated as True
by default.
Default and nodefault
The default
directive lets you specify the default value of a property. This is used by the Object Inspector to draw the property differently when it is non-default. Properties are also not streamed when their value matches the default value.
It is important to note that specifying the default value does not initialize the property. You must initialize fields (for properties backed by fields) in your class’s constructor, and if you change the value a field that backs a property is initialized to, you should also update the value used by the default keyword. There is no compiler warning to verify the value of this directive against a field used by the property.
The default
directive must be followed by a constant of the same type as the property.
default = _ (value of the same type)
nodefault
The nodefault
directive is used to override an inherited default
value without specifying a new one. If you do not specify a default value when specifying a property, it has no default; this is used for property redeclarations where the ancestor did specify a default and you want to reset that.
The default
and nodefault
directives support only ordinal types and set types, provided the upper and lower bounds of the set have ordinal values between 0 and 31. If a property is not declared as default
or nodefault
, it is treated as nodefault
. For floating point types, pointers, and strings there is an implicit default value of 0, nullptr, and (empty string), respectively.
nodefault
.stored and default/nodefault and streaming properties
These specifiers are used when streaming a class. If a property’s current value is different from its default
value (or if there is no default
value, either by not specifying default
or by specifying nodefault
), or if the stored specifier is true, then the property’s value is saved when streaming out. Otherwise, it is not saved (not written).
Implements attributes
C++Builder introduces the implements attribute for the __property
keyword. The implements attribute enables you to implement interfaces efficiently, without involving multiple inheritances.
The implements
attribute of the C++ __property
keyword allows you to implement an interface by specifying it as an attribute or field of a class. This implementation is analogous to how Delphi implements directives allowing a class to implement an interface by delegating to a property.
In a __property
statement, the implements attribute is placed last, similar to the nodefault
attribute placement
For more information about implements attributes, click here.
Property Overrides and Redeclarations
A property declaration that does not 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 example redeclares a Left property with __published
visibility. In the ancestor class, it would have had public or protected visibility:
__published
__property Left;
The following example redeclares a Left property, adding the stored specifier:
__property Left = { stored = true };
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 complete and must therefore include at least one access specifier.
__property Examples
The following example shows some simple property declarations:
class PropertyExample {
private:
int Fx, Fy;
float Fcells[100][100];
protected:
int readX() {
return (Fx);
}
void writeX(int newFx) {
Fx = newFx;
}
double computeZ() {
// Do some computation and return a floating-point value...
return (0.0);
}
float cellValue(int row, int col) {
return (Fcells[row][col]);
}
public:
__property int X = {read = readX, write = writeX};
__property int Y = {read = Fy};
__property double Z = {read = computeZ};
__property float Cells[int row][int col] = {read = cellValue};
};
This example shows several property declarations:
- Property X has read-write access through the member functions readX and writeX, respectively.
- Property Y corresponds directly to the member variable Fy, and is read-only.
- Property Z is a read-only value that is computed; it is not stored as a data member in the class.
- The Cells property demonstrates an array property with two indices.
The next example shows how you would access these properties in your code:
// Previous code goes here
int main(int argc, char * argv[]) {
PropertyExample myPropertyExample ;
myPropertyExample.X = 42; // Evaluates to: myPropertyExample.WriteX(42) ;
int myVal1 = myPropertyExample.Y; // Evaluates to: myVal1 = myPropertyExample.Fy ;
double myVal2 = myPropertyExample.Z; // Evaluates to: myVal2 = myPropertyExample.ComputeZ() ;
float cellVal = myPropertyExample.Cells[3][7]; // Evaluates to : cellVal = myPropertyExample.cellValue(3,7);
}
Compiler Support
Support for the __property
keyword varies based on the C++ compiler you use, clang (recommended), or classic. In particular, the __property
implementation in clang compilers handles some features that classic bcc32 does not.
The principal difference can be explained with the following example:
#include <string>
typedef std::string PROPTYPE;
class TTest {
PROPTYPE getProp();
void setProp(PROPTYPE);
public:
__property PROPTYPE Val = { read=getProp, write=setProp };
};
void test() {
TTest t;
t.Val += "hello"; // <<<<< **
}
PROPTYPE TTest::getProp() { printf("%s\n", __FUNCTION__); return PROPTYPE(); }
void TTest::setProp(PROPTYPE) { printf("%s\n", __FUNCTION__); }
int main() {
test();
}
In the code, while the classic bcc32 compiler invokes the getter and operates on the temporary returned, the clang compilers do the same but also call the setter with that result. That is, only the clang compiler will generate code that acts as a developer will intuitively expect the += line to behave.