The first core class of the Delphi library we'll look at is TPersistent, which is quite a strange class: It has very little code and almost no direct use, but it provides a foundation for the entire idea of visual programming. You can see the definition of the class in Listing 4.1.
Listing 4.1: The Definition of the TPersistent Class, from the Classes Unit
{$M+}
TPersistent = class(TObject) private
procedure AssignError(Source: TPersistent);
protected
procedure AssignTo(Dest: TPersistent); virtual;
procedure DefineProperties(Filer: TFiler); virtual;
function GetOwner: TPersistent; dynamic;
public
destructor Destroy; override;
procedure Assign(Source: TPersistent); virtual;
function GetNamePath: string; dynamic;
end;
As the name implies, this class handles persistency—that is, saving the value of an object to a file to be used later to re-create the object in the same state and with the same data. Persistency is a key element of visual programming. In fact (as you saw in Chapter 1, "Delphi 7 and Its IDE"), at design time in Delphi you manipulate actual objects, which are saved to DFM files and re-created at run time when the specific component container— form or data module—is created.
Note Everything I say about DFM files also applies to XFM files, the file format used by CLX applications. The format is identical. The extension difference is relevant because Delphi uses it to determine whether the form is based on CLX/Qt or on VCL/Windows. In Kylix, every form is a CLX/Qt form, regardless of which extension is used; so, the XFM/ DFM file extension in Kylix really doesn't matter.
Streaming support is not embedded in the TPersistent class but is provided by other classes, which target TPersistent and its descendants. In other words, you can "persist" with Delphi default streaming-only objects of classes inheriting from TPersistent. One of the reasons for this behavior lies in the fact that the class is compiled with a special option turned on, {$M+}. This flag activates the generation of extended RTTI information for the published portion of the class.
Delphi's streaming system doesn't try to save the in-memory data of an object, which would be complex because of the many pointers to other memory locations and totally meaningless when the object was reloaded. Instead, Delphi saves objects by listing the values of all properties in the published section of the class. When a property refers to another object, Delphi saves the name of the object or the entire object (with the same mechanism) depending on its type and relationship with the main object. For a comparison with other approaches, see the sidebar "Object Streaming versus Code Generation."
The only method of the TPersistent class that you'll generally use is the Assign procedure, which can be used to copy the actual value of an object. In the library, this method is implemented by many noncomponent classes but by very few components. Most subclasses reimplement the virtual protected AssignTo method, called by the default implementation of Assign.
Other methods include DefineProperties, used for customizing the streaming system and adding extra information (pseudo-properties); and the GetOwner and GetNamePath methods, used by collections and other special classes to identify themselves to the Object Inspector.
Object Streaming versus Code Generation
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
The approach used by Delphi (and Kylix) is different from the approach used by other visual development tools and languages. For example, in Java, the effect of the definition of a form inside an IDE is the generation of the Java source code used to create the components and set their properties. Setting properties in an inspector affects the source code. Something similar happens in C#, although properties in this language are closer to the notion of properties in Delphi. You've already seen that in Delphi; you can write code to generate the
components instead of relying on streaming, but because there is no specific support in the IDE you'll have to write that code manually.
Each of the two approaches has advantages and disadvantages. When generating source code, you have more control over what goes on and the exact sequence of creation and initialization. Delphi reloads objects and their properties but delays some assignments until a later fix-up phase, to avoid the problems of references to not-yet-initialized objects. This process is more complex, but it is so hidden that it becomes simpler for the programmer.
The Java language allows a tool like JBuilder to recompile a form class and load it in a running program for every change. In a compiled system like Delphi, this approach would be more complex (Delphi uses a fake version, technically called a proxy, of your form at design time, not the actual form).
One advantage of the approach used by Delphi is that the DFM files can be translated into different languages without affecting the source code; this is one reason Java is offering XML persistence of forms. Another difference is that Delphi embeds the component's graphic in the DFM file, instead of referring to external files.
Doing so simplifies deployment (because everything ends up in the executable file) but can also make the executable much bigger.
The published Keyword
Delphi has four directives specifying data access: public, protected, private, and published. I've covered the first three in Chapter 2, "The Delphi Programming Language," so now it's time to look at what published means. For any published field, property, or method, the compiler generates extended RTTI information, so that Delphi's run-time environment or a program can query a class for its published interface. For example, every Delphi component has a published interface that is used by the IDE, in particular the Object Inspector. A proper use of published items is important when you write components. Usually, the published part of a component contains no fields or methods, just properties and events.
When Delphi generates a form or data module, it places the definitions of its components and methods (the event handlers) in the first portion of its definition, before the public and private keywords. These fields and methods in the initial portion of the class are published. The default is published when no special keyword is added before an element of a component class.
To be more precise, published is the default keyword only if the class was compiled with the $M+ compiler directive or is descended from a class compiled with $M+. This directive is used in the TPersistent class, so most classes of the VCL and all the component classes default to published. However, noncomponent classes in Delphi (such as TStream and TList) are compiled with $M- and default to public visibility.
The methods used to handle events in the IDE (and in DFM files) should be published, and the fields corresponding to your components in the form should be published to be automatically connected with the objects described in the DFM file and created along with the form. (Later in this chapter I'll discuss the details of this situation and the problems it generates.)
Accessing Published Fields and Methods
As I've mentioned, three different declarations make sense in the published section of a class: fields, methods, and properties. In your code, you'll generally refer to published items like you refer to public ones, that is, by referring to the corresponding identifiers in the code. In some special cases, though, it is possible to access published items at runtime by name. I'll discuss dynamic access to properties in the section "Accessing Properties by Name;" here I'll introduce possible ways of interacting at runtime with fields and methods. The
TObject class has three interesting methods for this area: MethodAddress, MethodName, and FieldAddress. The first function, MethodAddress, returns the memory address of the compiled code (a sort of function pointer) of the method passed as parameter in a string. By assigning this method address to the Code field of a TMethod structure and assigning an object to the Data field, you can obtain a complete method pointer. At this point, to call the method you must cast it to the proper method pointer type. Here is a code fragment highlighting the key points of this technique:
var
Method: TMethod;
Evt: TNotifyEvent;
begin
Method.Code := MethodAddress ('Button1Click');
Method.Data := Self;
Evt := TNotifyEvent(Method);
Evt (Sender); // call the method end;
Delphi uses similar code to assign an event handler when it loads a DFM file, because these files store the name of the methods used to handle the events, whereas the components store the method pointer. The second method, MethodName, does the opposite transformation, returning the name of the method at a given memory address. This method can be used to obtain the name of an event handler, given its value, something Delphi does when streaming a component into a DFM file.
Finally, the FieldAddress method of TObject returns the memory location of a published field, given its name.
Delphi uses this method to connect components created from the DFM files with the fields of their owner (for example, a form) having the same name.
Note that these three methods are seldom used in "normal" programs but play a key role in making Delphi work.
They are strictly related to the streaming system. You'll need to use these methods only when writing extremely dynamic programs, special-purpose wizards, or other Delphi extensions.
Accessing Properties by Name
The Object Inspector displays a list of an object's published properties, even for components you've written. To do this, it relies on the RTTI information generated for published properties. Using some advanced techniques, an application can retrieve a list of an object's published properties and use them.
Although this capability is not very well known, in Delphi it is possible to access properties by name simply by using the string with the name of the property and then retrieving its value. Access to the RTTI information of properties is provided through a group of undocumented subroutines, part of the TypInfo unit.
Warning These subroutines have always been undocumented in past versions of Delphi, so that Borland remained free to change them. However, from Delphi 1 to Delphi 7, changes were very limited and related only to supporting new features, with a high level of backward compatibility. In Delphi 5, Borland added many more goodies and a few "helper" routines that are officially promoted (even if still not fully documented in the Help file but explained only with comments provided in the unit).
Rather than explore the entire TypInfo unit here, we will look at only the minimal code required to access properties by name. Prior to Delphi 5, it was necessary to use the GetPropInfo function to retrieve a pointer to some internal property information and then apply one of the access functions, such as GetStrProp, to this pointer. You also had to check for the existence and the type of the property.
Now you can use a new set of TypInfo routines, including the handy GetPropValue, which returns a variant with the value of the property and raises an exception if the property doesn't exist. To avoid the exception, you can call the IsPublishedProp function first. You simply pass to these functions the object and a string with the property name. A further optional parameter of GetPropValue allows you to choose the format for returning values of properties of any set type (either a string or the numeric value for the set). For example, you can call
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
ShowMessage (GetPropValue (Button1, 'Caption'));
This call has the same effect as calling ShowMessage, passing as parameter Button1.Caption. The only real difference is that this version of the code is much slower, because the compiler generally resolves normal access to properties in a more efficient way. The advantage of the run-time access is that you can make it very flexible, as in the following RunProp example.
This program displays in a list box the value of a property of any type for each component of a form. The name of the property you are looking for is provided in an edit box. Being able to type the property name in the edit box makes the program very flexible. In addition to the edit box and the list box, the form has a button to generate the output and some other components added only to test their properties. When you click the button, the following code is executed:
uses TypInfo;
procedure TForm1.Button1Click(Sender: TObject);
var I: Integer;
Value: Variant;
begin
ListBox1.Clear;
for I := 0 to ComponentCount -1 do begin
if IsPublishedProp (Components[I], Edit1.Text) then begin
Value := GetPropValue (Components[I], Edit1.Text);
ListBox1.Items.Add (Components[I].Name + '.' + Edit1.Text + ' = ' + string (Value));
end else
ListBox1.Items.Add ('No ' + Components[I].Name + '.' + Edit1.Text);
end;
end;
Figure 4.2 shows the effect of clicking the Fill List button while using the default Caption value in the edit box. You can try it with any other property name. Numbers will be converted to strings by the variant conversion. Objects (such as the value of the Font property) will be displayed as memory addresses.
Figure 4.2: The output of the RunProp example, which accesses properties by name at run time
Warning Do not use regularly the TypInfo unit instead of polymorphism and other property-access techniques. Use base-class property access first, or use the safe as typecast when required, and reserve RTTI access to properties as a last resort. Using TypInfo techniques makes your code slower, more complex, and more prone to human error; in fact, it skips the compile-time type-checking.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.