__property

From RAD Studio
Jump to: navigation, search

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.

__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 of read, write, stored, index, default (or nondefault), or implements 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
Note: member can be a field or method, usually private or protected but you can locate it in any visibility.

When using the readspecifier 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 and write 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.

Warning: Delphi default index properties conflict with the subscript operator of C++. Therefore all default index properties from Delphi are exposed as a member operator [](int index) that forwards to the underlying property

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.

Note: You cannot use the ordinal value -2147483648 as a default value. This value is used internally to represent 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).

Warning: 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.

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.

See Also