You are on page 1of 15

Chapter 5

The Sender parameter and substitution

Main concepts
Substitution The Sender parameter Typecasting Class and instance operators RTTI: run time type information

Chapter contents
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Example 5.1 The Sender parameter in event handlers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Ex 5.1 step 1 The OnClick event handler, version 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Ex 5.1 step 2 Using the Sender parameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Ex 5.1 step 3 Linking in the other SpeedButtons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Ex 5.1 step 4 Testing the Sender parameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Ex 5.1 step 5 Working through a superclass . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Ex 5.1 step 6 An alternative typecasting operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 The Sender parameter and substitution (26 Jun 2006) Chapter 5, Page 1

Ex 5.1 step 7 The equality operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 A diagrammatic view of substitution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 Example 5.2 Run time type information (RTTI) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Some final comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Chapter summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Problem 5.1 Study Chapter 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Problem 5.2 RTTI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Problem 5.3 Centralised control of several traffic lights . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

Introduction
An important consequence of an inheritances hierarchy is the concept of substitution, where an instance of a subclass object can substitute for a variable declared as any of its supertypes. In this brief chapter we introduce the concept of substitution by investigating the Sender parameter that is part of the parameter list of every event handler. Different events have different parameter lists but the first in the list for all events is Sender: TObject, which identifies the component that initiated the event. Identifying the event initiator is a useful idea, but not completely straightforward. The difficulty is that every parameter must have a type, and so Sender must be declared as a specific type. But what type should Sender be? Every component is different. As well see in the example that follows, Delphi uses the inheritance hierarchy to solve this problem.

Example 5.1 The Sender parameter in event handlers


The example well use to illustrate the Sender parameter has six SpeedButtons, two panels, a label and a form (figures 1&2). The goal of this program is to display an appropriate message in response to a click on any of these components. One way of doing this is to write ten event handlers, one for each component, each displaying the required message. But this leads to a lot of effort and, worse, the repeated code introduces many opportunities for future errors. What would be better would be to write just one procedure, to route all the OnClick events through this code, and then use the Sender parameter to generate a message identifying which component has been clicked.

Chapter 5, Page 2

Object orientation with Delphi (all rights reserved)

Figure 1 User interface for Example 5.1

Figure 2 Structure of the user interface

Ex 5.1 step 1 The OnClick event handler, version 1


Well start with some simple code and then develop it into the complete program. Create an OnClick event handler for spdOne to display the value of spdOnes Caption and then test this first version of the program.
28 procedure TfrmSender.spdOneClick(Sender: TObject); 29 begin 30 pnlReport.Caption := 'You clicked on ' + spdOne.Name; 31 end; // procedure TfrmSender.spdOneClick

Ex 5.1 step 2 Using the Sender parameter


Now well modify this event handler to use the Sender parameter. One possibility is to change spdOne in the assignment statement to Sender (line 30 below): The Sender parameter and substitution (26 Jun 2006) Chapter 5, Page 3

28 procedure TfrmSender.spdOneClick(Sender: TObject); 29 begin 30 pnlReport.Caption := 'You clicked on ' + Sender.Name; 31 end; // procedure TfrmSender.spdOneClick

If you try this youll see that it does not work. The compiler returns an Undeclared identifier error. Why is this? In the parameter list in the header of this OnClick event handler, and all other event handlers, Senders type is defined as TObject, the root class of the hierarchy, and not as TSpeedButton (line 28). The TObject class does not have a Name property, and so, when the compiler reaches Sender.Name in line 30, it generates an error. Because Sender is a completely general parameter it is declared as type TObject. However, in this case we know that Sender is a TSpeedButton and so we must instruct Delphi to treat Sender as a TSpeedButton in other words we must typecast Sender as a TSpeedButton. There are different ways to do this. Here we use the as class operator as follows (line 31):
28 procedure TfrmSender.spdOneClick(Sender: TObject); 29 begin 30 pnlReport.Caption := 'You clicked on ' + 31 (Sender as TSpeedButton).Name; 32 end; // procedure TfrmSender.spdOneClick

This program compiles and works like the previous version, so we now seem to be using the Sender parameter correctly. We have seen the as typecasting operator in passing in example 2.1 step 2 line 17, and here we will look at it more closely. This typecasting informs Delphi that although Sender is declared in the parameter list as a TObject because it could be any one of a number of different classes, in this particular case Delphi must treat it as a TSpeedButton (line 31 above).

Ex 5.1 step 3 Linking in the other SpeedButtons


None of the other Buttons display a message yet and so we must now pay attention to linking them in. Close the program, select spdOne and then select the Events Tab in the Object Inspector. To make things less confusing, change the name of the OnClick handler from spdOneClick to GeneralClick. Delphi will now automatically make this change in the program code. (Dont change the program directly.) Now link the other SpeedButtons to this event handler. Selecting each SpeedButton in turn, on the Events Tab of the Object Inspector select GeneralClick from the drop-down

Chapter 5, Page 4

Object orientation with Delphi (all rights reserved)

box alongside OnClick (figure 3). This routes each SpeedButtons event to the single General Click event handler. Save this version of the program and run it. Check that the text in the panel gives the correct event handler name for each of the SpeedButtons.
Figure 3 Linking the different components to the same event handler

Ex 5.1 step 4 Testing the Sender parameter


What happens if an event handler receives events from more than one type of component? For instance, what happens if we link the event handler GeneralClick to respond to clicks on the Panels or the Form as well as to clicks on the SpeedButtons? How do we then decide whether to typecast the Sender parameter as a TSpeedButton, as a TPanel or as a TForm? Delphi has a special operator, the is class operator, that determines what class of component Sender refers to (lines 30, 33 & 35).
28 procedure TfrmSender.GeneralClick(Sender: TObject); 29 begin 30 if Sender is TSpeedButton then 31 pnlReport.Caption := 'You clicked on ' + 32 (Sender as TSpeedButton).Name 33 34

else if Sender is TForm then pnlReport.Caption := 'You clicked on ' + (Sender as TForm).Name

35 else if Sender is TPanel then 36 pnlReport.Caption := 'You clicked on ' +(Sender as TPanel).Name; 37 end; // end TfrmSender.GeneralClick

Using the events tab of the Object Inspector, link the OnClick events for pnlControl, pnlReport, lblInheritance and frmSender to the GeneralClick event handler. (We use a condition test even in the last else clause to ensure that it will respond only to TPanels. The Sender parameter and substitution (26 Jun 2006) Chapter 5, Page 5

Anything except a TSpeedButton, a TForm or a TPanel is ignored.) Run and test this version of the program and check that it responds correctly to clicks on the Panels and the Form as well as on the SpeedButtons. It does not respond to a click on lblInheritance even though we have linked in GeneralClick as its event handler since GeneralClick does not have an If condition to test for a TLabel.

Ex 5.1 step 5 Working through a superclass


The program in step 4 above is very interesting. It shows us that a variable of one type, in this case parameter Sender of type TObject, can represent any of its descendants, in this case TSpeedButton, TForm or TPanel. We can even take this concept one step further and in the process reduce the number of If statements in step 4 from three to one. When we were discussing the concept of a class hierarchy in chapter 2, we mentioned that TComponent introduces the Name property. TSpeedButton, TForm and TPanel are all descendants of TComponent, thats why they all have the Name property, and TComponent is in turn a descendant of TObject. We can take advantage of this in our program by working at the level of TComponent instead of the individual classes (lines 3032 below):
28 procedure TfrmSender.GeneralClick(Sender: TObject); 29 begin 30 if Sender is TComponent then 31 pnlReport.Caption := 'You clicked on ' + 32 (Sender as TComponent).Name 33 end; // end TfrmSender.GeneralClick

Save and run this version of the program. It gives the same messages as step 4 whether we click on a SpeedButton, a panel or the form, and now also responds to a click on lblInheritance. Here we gain a further benefit from inheritance. Because TSpeedButton, TPanel, TLabel and TForm are all descendants of TComponent, we can test for TComponent (line 30) and cast as TComponent (line 32) instead of testing and casting for each one separately as in step 4. Where we are using a property (or method) that subclasses inherit from a superclass, we can work with the superclass instead of each subclass individually. This saves programming since we dont have to cater separately for each subclass class and so we can avoid situations like step 4 where we have a separate If statement for each subclass. It also means that if in future we introduce a further subclass we dont have to search through the entire program to cater specifically for it. Chapter 5, Page 6 Object orientation with Delphi (all rights reserved)

What we see in this step is important. Sender, of type TObject, is cast as a descendant, TComponent, which in turn is an ancestor of several other objects used here (TSpeedButton, TPanel and TForm). Since these objects all inherit the Name property from their ancestor TComponent, we cast Sender as their ancestor TComponent. This allows us to work with them as a group rather than with each one individually. Although we are working with a TComponent in lines 30 & 32, any of its descendants (here TSpeedButton, TPanel or TForm) can substitute for it, behaving as though it is a TComponent. This is an example of substitution.

Ex 5.1 step 6 An alternative typecasting operator


Step 5 is the main focus of this example. But there are still two points to make before completing the example. The first is that the as operator is part of Delphi Pascal but was not part of the original Pascal. It (as) has the advantage of doing strict error-checking and throwing an exception if one tries to typecast wrongly. Originally Pascal typecast through brackets, and Delphi recognises this too. So we can change line 32 of the program above to:
28 procedure TfrmSender.GeneralClick(Sender: TObject); 29 begin 30 if Sender is TComponent then 31 pnlReport.Caption := 'You clicked on ' + 32 TComponent(Sender).Name 33 end; // end TfrmSender.GeneralClick

The bracket notation looks a bit like a subroutine call and does not perform any error checking. So, although it is slightly less efficient, many programmers prefer the as operator for typecasting since it is more robust and easier to read.

Ex 5.1 step 7 The equality operator


We have used the Sender parameter with the is and as class operators. The first, is, determines the class of an object and often forms the conditional part of an If statement. So in step 6 above we used is to determine what class Sender represents. However, if we want to find out which specific component or object Sender represents we can use the equality operator = . For example, lets assume that the Forms colour should be aqua if spdFive is clicked and green if any other component is clicked. We can do this as follows (lines 3336):
28 procedure TfrmSender.GeneralClick(Sender: TObject);

The Sender parameter and substitution (26 Jun 2006)

Chapter 5, Page 7

29 begin 30 if Sender is TComponent then 31 pnlReport.Caption := 'You clicked on ' + 32 (Sender as TComponent).Name; 33 if Sender = spdFive then 34 Color := clAqua 35 else 36 Color := clGreen; 37 end; // end TfrmSender.GeneralClick

Example 5.1 Summary: The crucial point of this example is to introduce the concept of substitution. The Sender parameter, which is part of every event, tells the event handler which component initiated the event. Many different components can initiate events and so the Sender parameter is of type TObject, the most general type of object that there is in Delphi. The is and the = operators allow us to test whether Sender represents either a particular class or a particular object. To use Sender in a program we need to typecast it appropriately and we can use either as (with error checking) or () (without checking) for the typecasting. In this example we saw that a property that is available to a TComponent object is also available to TSpeedButton, TForm and TPanel objects because all three descend from TComponent. Because of the inheritance hierarchy, a subtype inherits all the functionality of the supertype as a starting point, and so a subtype can take on the role of any of its ancestors. However an ancestor, which is typically more restricted than the subtype, cannot take on all the roles of the subtype and trying to substitute an ancestor for a subtype will lead to a compile error.

A diagrammatic view of substitution


To consolidate the concept of substitution, it might be helpful to ask how it is possible for a subtype to substitute for an ancestor, and to answer this by looking at how an object is (conceptually) represented in memory. Assume that there are four classes, with Class A the ancestor of Classes B and C, and class D a subclass of class B (figure 4).

Chapter 5, Page 8

Object orientation with Delphi (all rights reserved)

Figure 4 A simple hierarchy

The class diagram shows in Class B only the additional data fields declared in Class B, with the generalisation arrow from Class B to Class A indicating that Class B also has the data fields declared in Class A. However, an instance of Class B must have memory allocated for both Data Fields A and Data Fields B. (And similarly for the other classes.) Say we have an instance of Class A, two instances of Class B, an instance of Class C and an instance of Class D. We can represent this as follows:

Figure 5 Schematic of object structure

When an instance of Class B, C or D substitutes for an instance of Class A, the additional subclass structure (Data Fields B, C and/or D) are ignored. This is possible because of the overlap in structure between a superclass and all its descendants. However, Class B cannot substitute for Class C or the other way round because of the differences in their structures. An instance of Class D can substitute for instances of either Class B or Class A (but not for Class C). We have referred only to the objects data fields so far. A similar but more sophisticated concept applies to methods and, where we have run-time binding, makes polymorphism possible. Polymorphism is a intriguing concept, and we devote the next chapter to it.

The Sender parameter and substitution (26 Jun 2006)

Chapter 5, Page 9

Example 5.2 Run time type information (RTTI)


As we have seen from the is and as operators we used in example 5.1, Delphi can determine aspects of the class structure of an object at run time. In Win32, each object is associated with its run time type information, usually abbreviated as RTTI. There is more to RTTI than just is and as, though these may be the most commonly used. For example, the ClassName method returns a string, the InheritsFrom method returns a Boolean, and ClassType and ClassParent both return a TClass variable1 . You can modify the previous program to illustrate these RTTI methods:
28 procedure TfrmSender.GeneralClick(Sender: TObject); 29 begin 30 lblInheritance.Caption := 'Class Name: ' + Sender.ClassName; 31 pnlReport.Caption := 'TCustomControl descendant: ' + 32 BoolToStr(Sender.InheritsFrom(TCustomControl), True); 33 end; // procedure TfrmSender.GeneralClick

Figure 6 Using some RTTI methods

Some final comments


The concept of substitution is that when we declare a variable as a particular type, that variable can refer to an object of the declared type or of any descendant types. Syntactically, this means that we can use an object of a subclass whenever an object of any of its ancestor classes is specified. (If we override our superclass methods indiscriminately in our subclasses, or if we dont maintain meaningful generalisation in our hierarchy, we can damage the semantics of substitution. But this is the subject of the next chapter, and only arises through poor programming practice.) There is a relationship between generalisation (mentioned in chapter 2) and substitution, and it is worth returning briefly to the concept of generalisation. Generalisation is the

For more information on RTTI, try http://www8.pair.com/rmorris/rtti.htm

Chapter 5, Page 10

Object orientation with Delphi (all rights reserved)

process of identifying the commonality that exists between a set of classes and then moving this commonality into a superclass. (We may create a superclass specially for the generalisation.) Since the common functionality from a group of subclasses has moved into a superclass, it makes sense that any of the subclasses can substitute for the superclass (though not the other way round). This possibility of substitution is an important reason for introducing generalisation into the applications class structure wherever possible. In the VCL components, the Delphi developers have already designed the class hierarchy and produced several levels of generalisation. This is why we could use the substitution as we did in this chapter, with a SpeedButton, panel, label or form substituting for a TComponent. When we begin designing our own classes it will be well worth while to keep the concepts of generalisation and substitution in mind. This is a brief chapter that simply illustrates the concept of substitution to prepare the way for polymorphism. In the next chapter we explore generalisation and substitution further, this time with programmer-written classes, and develop the concept to include polymorphism. Polymorphism is fundamental to several of the patterns well investigate a little later and is one of the main reasons for the growth and development of object-oriented programming.

Chapter summary
Main points: 1. Substitution. 2. The Sender parameter. 3. Typecasting. 4. Class and instance operators. 5. RTTI: run time type information. Objects as derived entities: Substitution.

The Sender parameter and substitution (26 Jun 2006)

Chapter 5, Page 11

Problems
Problem 5.1 Study Chapter 5
Identify the appropriate example(s) or section(s) of the chapter to illustrate each comment made in the summary above.

Problem 5.2 RTTI


Consider the following program:
Figure 7 Start up screen for problem 6.1

Figure 8 Objects on the user interface

The event handlers for all the objects on the user interface are routed through the following event handler: Chapter 5, Page 12 Object orientation with Delphi (all rights reserved)

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

procedure TfrmSender.GeneralClick(Sender: TObject); var Class1, Class2: TClass; ClassStr: string; begin Class1 := Sender.ClassType; Class2 := Class1.ClassParent; while Class2 <> nil do begin ClassStr := Class1.ClassName + ' derived from ' + Class2.ClassName; lstClasses.Items.Add(ClassStr); Class1 := Class1.ClassParent; Class2 := Class1.ClassParent; end; lstClasses.Items.Add(''); end; // procedure TfrmSender.GeneralClick

40 end. // end SenderU

a) Explain how this event handler works and what it does, referring to the principles covered in chapter 5. b) Draw the UML class diagram for all the classes in this program on the basis of this programs operation.

Problem 5.3 Centralised control of several traffic lights


This questions develops out of the concepts and program covered in the earlier propertiesbased traffic light problem for chapter 4. For this problem, create a master control panel (figures 9 & 10) that controls three individual traffic light displays.
Figure 9 The traffic light control panel with the three independent displays

The Sender parameter and substitution (26 Jun 2006)

Chapter 5, Page 13

Figure 10 The traffic light control panel

This control panel provides independent control of three different traffic lights. Each traffic light can be set to automatic or manual via a CheckBox. When the CheckBox is checked the associated traffic light runs automatically (4 cycles red, 3 cycles green, 1 cycle yellow). When the CheckBox is unchecked, the Step button is enabled. Each click on the Step button moves the associated traffic light, independently of the other two, to the next colour in the sequence. The third control for each light, a SpinEdit, gives the cycle length in tenths of a second. So if a SpinEdit is set to 8, the light it controls spend 3.2 seconds on red, 2.4s on green and 0.8s on yellow. Any change of value in a SpinEdit takes effect from the next colour change. Code this program, incorporating the OO principles covered so far in this module as appropriate. Include simple UML documentation. There are different ways to tackle this problem. Less experienced programmers might find the following steps useful: 1. Write a simple low-level description / algorithm for each of the following cases: a. Enable a traffic light for automatic operation by checking a CheckBox. b. Disable automatic stepping and step manually through the sequence by clicking on a button. c. Check a CheckBox for automatic operation and then change the cycle time. 2. From these descriptions, decide what classes are needed and how they should be related. 3. Based on the classes in the class diagram, draw a sequence diagram for each of the cases above. 4. Implement each case in turn. Its probably easier not to solve the whole problem in one go. Instead, you may decide to start with just the controller and a single traffic light and to concentrate on switching that lights automatic operation on and off correctly. You would then have a choice. You could

Chapter 5, Page 14

Object orientation with Delphi (all rights reserved)

get three lights controlled independently. Then you could get the manual control working for one light and extend it to three, and finally program the adjustable cycle for one and then extend it to all three. Alternatively, you could stick with a single light and get the manual stepping to work, followed by the adjustable cycle, and then finally expand that to the independent control of first two lights and then three lights. Whichever approach you take will involve some iteration and each step forward is likely to involve changes to some of the code already written, and possibly some changes to the designs under points 1, 2 or 3 above. 5. Once the program is written, tested and working, use Visio to draw a UML class diagram showing the master control panel, the traffic light display(s), the traffic light class(es) and any other significant classes and the relationships between these classes, and sequence diagrams showing the operation for each of the cases. 6. Prepare a brief description ( to 1 types page) of how the program works and identify the OO principles you have included.

The Sender parameter and substitution (26 Jun 2006)

Chapter 5, Page 15

You might also like