As mentioned earlier, a key element of the System unit is the definition of the TObject class, which is the mother of all Delphi classes. Every class in the system inherits from the TObject class, either directly (if you specify TObject as the base class), implicitly (if you indicate no base class), or indirectly (when you specify another class as the ancestor). The entire hierarchy of classes in an Object Pascal program has a single root. So, you can use the TObject data type as a replacement for the data type
of any class type in the system, according to the type compatibility rules covered in Chapter 2 in the section "Inheritance and Type Compatibility."
For example, components' event handlers usually have a Sender parameter of type TObject. This simply means that the Sender object can be of any class, because every class is ultimately derived
from TObject. The typical drawback of such an approach is that to work on the object, you need to know its data type. In fact, when you have a variable or a parameter of the TObject type, you can apply to it only the methods and properties defined by the TObject class itself. If this variable or parameter happens to refer to an object of the TButton type, for example, you cannot directly access its Caption property. The solution to this problem lies in the use of the safe down-casting or run-time type information (RTTI) operators (is and as) discussed in Chapter 2.
You can also use another approach. For any object, you can call the methods defined in the TObject class. For example, the ClassName method returns a string with the name of the class. Because it is a class method (see Chapter 2 for details), you can apply it both to an object and to a class. Suppose you have defined a TButton class and a Button1 object of that class. Then the following statements have the same effect:
Text := Button1.ClassName; Text := TButton.ClassName;
On some occasions you need to use the name of a class, but it can also be useful to retrieve a class reference to the class itself or to its base class. The class reference allows you to operate on the class at run time (as you saw in the preceding chapter), whereas the class name is just a string. You can get these class references with the ClassType and ClassParent methods. The first returns a class reference to the class of the object; the second returns a class reference to the object's base class. Once you have a class reference, you can apply to it any class methods of TObject—for example, to call the ClassName method.
Another method that might be useful is InstanceSize, which returns the run-time size of an object. Although you might think that the SizeOf global function provides this information, that function actually returns the size of an object reference—a pointer, which is invariably four bytes—instead of the size of the object itself.
In Listing 3.1, you can find the complete definition of the TObject class, extracted from the System unit. In addition to the methods I've already mentioned, notice InheritsFrom, which provides a test that's similar to the is operator but that can also be applied to classes and class references (the first argument of is must be an object).
type TObject = class constructor Create; procedure Free; class function InitInstance(Instance: Pointer): TObject; procedure CleanupInstance; function ClassType: TClass; class function ClassName: ShortString; class function ClassNameIs( const Name: string): Boolean; class function ClassParent: TClass; class function ClassInfo: Pointer; class function InstanceSize: Longint; class function InheritsFrom(AClass: TClass): Boolean; class function MethodAddress(const Name: ShortString): Pointer; class function MethodName(Address: Pointer): ShortString; function FieldAddress(const Name: ShortString): Pointer; function GetInterface(const IID: TGUID;out Obj): Boolean; class function GetInterfaceEntry( const IID: TGUID): PInterfaceEntry; class function GetInterfaceTable: PInterfaceTable; function SafeCallException(ExceptObject: TObject; ExceptAddr: Pointer): HResult; virtual; procedure AfterConstruction; virtual; procedure BeforeDestruction; virtual; procedure Dispatch(var Message); virtual; procedure DefaultHandler(var Message); virtual; class function NewInstance: TObject; virtual; procedure FreeInstance; virtual; destructor Destroy; virtual; end;
The ClassInfo method returns a pointer to the internal run-time type information (RTTI) of the class, introduced in the next chapter.
These methods of TObject are available for objects of every class, because TObject is the common ancestor class of every class. Here is how you can use these methods to access class information:
procedure TSenderForm.ShowSender(Sender: TObject); begin Memo1.Lines.Add ('Class Name: ' + Sender.ClassName); if Sender.ClassParent <> nil then Memo1.Lines.Add ('Parent Class: ' + Sender.ClassParent.ClassName); Memo1.Lines.Add ('Instance Size: ' + IntToStr (Sender.InstanceSize)); end;
The code checks to see whether the ClassParent is nil, in case you are using an instance of the TObject type, which has no base type.
This ShowSender method is part of the IfSender example. The method is connected with the OnClick event of several controls: three buttons, a check box, and an edit box. When you click each control, the ShowSender method is invoked with the corresponding control as sender (more on events in Chapter 4). One of the buttons is a Bitmap button, an object of a TButton subclass. You can see an example of the output of this program at run time in Figure 3.7.
You can use other methods to perform tests. For example, you can check whether the Sender object is of a specific type with the following code:
if Sender.ClassType = TButton then ...
You can also check whether the Sender parameter corresponds to a given object, with this test:
if Sender = Button1 then...
Instead of checking for a particular class or object, you'll generally need to test the type compatibility of an object with a given class; that is, you'll need to check whether the class of the object is a given class or one of its subclasses. Doing so lets you know whether you can operate on the object with the methods defined for the class. This test can be accomplished using the InheritsFrom method, which is also called when you use the is operator. The following two tests are equivalent:
if Sender.InheritsFrom (TButton) then ... if Sender is TButton then ...
I've extended the IfSender example to show a complete list of base classes of a given object or class. Once you have a class reference you can add all of its base classes to the ListParent list box with the following code:
with ListParent.Items do begin Clear; while MyClass.ClassParent <> nil do begin MyClass := MyClass.ClassParent; Add (MyClass.ClassName); end; end;
You'll notice that I use a class reference at the heart of the while loop, which tests for the absence of a parent class (so that the current class is TObject). Alternatively, I could have written the while statement in either of the following ways:
while not MyClass.ClassNameIs ('TObject') do... while MyClass <> TObject do...
The code in the with statement referring to the ListParent list box is part of the ClassInfo example, which displays the list of parent classes and some other information about a few components of the VCL (basically those on the Standard page of the Component Palette). These components are manually added to a dynamic array holding classes and declared as
private ClassArray: array of TClass;
When the program starts, the array is used to show all the class names in a list box. Selecting an item from the list box triggers the visual presentation of its details and its base classes, as you can see in the program output in Figure 3.8.
As a further extension of this example, you can create a tree with all the base classes of the various components in a hierarchy. To do that, I've created the VclHierarchy wizard, discussed in Appendix A ("Extra Delphi Tools by the Author").