More Language Rules for Properties
Beside the basic structure we have seen, property declarations allow you many alternatives. A fundamental idea is that properties have a data type, but are limited to a given set of data types (including most of the predefined types, strings, and classes).
Properties can be read-write as in the examples above, but they can also be read-only or write-only. What is common is to see read-only properties, that is values you can read but not change. An obvious example of a read-only property in the VCL (the Visual Component Library, Delphi’s class hierarchy used also by C++Builder) is the Handle property of window based controls. You might want to ask to a control its Windows handle, when you want to call Windows API functions directly, but you are not supposed to change the handle of a window. To define a read-only property you simply omit the write declaration:
__property int ReadValue = {
read = GetReadValue };
It is possible to declare array properties, that is properties with an index. In this case the required read and write functions have an extra parameter, the index itself. This index must also be used to access to the values, since you cannot access to the array as a whole. The array might not exist as an actual field of the class: when you read the Items of a list box you are actually asking to Windows the value of the item (which is not duplicated inside the TListBox object). It also possible to declare array properties with multiple indexes (see as an example the Pixels property of the TCanvas class of the VCL).
The Access Visibility of a Property
Properties can be declared using any of the access specifiers, including private (although this makes little sense), protected, and public, plus the two new specifiers: __published and __automated. Read-only properties cannot be published, by the way.
A published field or method is not only available at runtime (as a public element) , but also at design-time. The Borland C++Builder compiler generates Delphi-style Runtime Type Identification (RTTI) for published properties of class derived from TPersistent. This type information is used by the design time environment, starting with the Object Inspector, but it is also available to programmers who want to delve into the undocumented TypInfo unit.
The published keyword is generally used for properties or events, but forms generally have a published interface including also sub-components and event-handler methods. This information is automatically parsed by the development environment and made available in the Object Inspector even before you compile the program.
For example, while the published interface of a component is used by the Object Inspector to show and edit property values at design time, the published interface of a form is used by the Object Inspector to find components compatible with a given data type and member functions compatible with a given event.
There is a fifth access specifier, automated, which is used to define a public interface with corresponding OLE Automation type information, making it possible to create OLE Automation Servers. The __automated keyword is used in TAutoObject subclasses.
Keep also in mind that the visibility of a property can be extended in derived classes. A protected property, for example, can be re-declared as a published property. To accomplish this you don’t need to redefine the property, and only to re-declare it (Borland use the term “hoisted properties” to indicate this behavior). When re-declaring a property you can also change it, for example modifying its default value.
Closures and Events
When properties are of a “pointer to a member function” data type (also known as a closure) they are called events. But what is a closure? Another addition to the standard C++ language. A closure is a sort of member function pointer. Actually it associates a pointer to a member function with a pointer to a class instance, an object. The pointer to the class instance is used as the this pointer when calling the associated member function. This is the definition of a closure:
typedef void __fastcall (__closure *TNotifyEvent)(TObject* Sender);
Since in C++ member function pointers are available as well, but seldom used, you might wonder what do we need this awkward stuff for? Closures really matter in the Delphi model. In fact events are closures, holding the value of a member function of the form hosting the related component. For example, a button has a closure, named OnClick, and you can assign a member function of the form to it. When a user clicks on the button, this member function is executed, even if you have defined it inside another class (typically, in the form). Here is what you can write (and C++ Builder usually writes for you):
BtnBeep->OnClick = BtnHelloClick;
BtnHello->OnClick = BtnBeepClick;
The code above exchanges two event handlers at runtime. You can also explicitly declare a variable of a closure type, as the common TNotifyEvent type, and use it to exchange the handlers of two events:
TNotifyEvent event = BtnBeep->OnClick;
BtnBeep->OnClick = BtnHello->OnClick;
BtnHello->OnClick = event;
This means you can assign a member function (as BtnHelloClick) to a closure or event both at design time and at runtime.
Streaming Properties and Objects
The classes of the derived from TPersistent have another important features. You can save objects of these classes to a stream. The VCL doesn’t save the internal data of an object, but simply saves the values of all of its published properties (and events). When the object is loaded, the VCL first creates a new object, than re-assigns each of its properties (and events). The most obvious example of objects streaming is the usage of DFM files. These binary files store the properties and the components of a form, and I suggest you to load them in C++Builder editor to study their textual version (you can also use the command line CONVERT.EXE program to turn a DFM file into a TXT file or vice verse)
The streaming process can be customized in different ways: adding a default or a stored clause to the declaration of properties, or overriding the DefineProperties method. When you add a property to a component you generally add to the definition a default clause. If the value of a property matches its default value, the property is not saved to a stream along with the other properties of an object. The constructor of the class must initialize the property to the same default value, or this technique won’t work. The stored directive, instead, indicates if a property must be saved to file along with the object or not. The stored directive might be followed by a boolean value, or a member function returning a boolean result (so you can choose whether to save the value or not depending on the current status of the object). Finally, the DefineProperties method allows you to create pseudo-properties, adding extra values to the streamed object, and reloading them properly.
More RTTI (dynamic_cast and more)
Beside RTTI information generated by the __published keyword, each object has several methods you can use to query itself. These methods are part of the TObject class, the base class of every VCL-based object. You can see the list of the methods of the class TObject (including static member functions) in Table 1.