Introduction
Welcome to Part II of our CPP Delegates article.
In Part_II we are going to illustrate how Borland's C++ Builder handles Windows Events using its proprietary keyword "__closure
" and the delegation model instead of Inheritance as happens in MFC library and Win32 API.
Background
I assume that the reader has read Part 1 of this article. Also I assume that he/she has intermediate to advanced knowledge of Borland's C++ Builder IDE.
Part II - Using Closures to Handle Events
What are Delegates?
When you want another person to replace you in a job, this is called delegating this person. We may see a manager of some company delegates one of the experienced employees to replace him for a period of time when he is not available.
In the programming world, this happens by asking some function in some class to do some job that our class cannot do. The most sensitive example for this model is the “Event Handling”. When you press a button, a message is sent to the operating system (namely Microsoft Windows) declaring that you have clicked your mouse's left button. This is called Event. The job that your software does as a response to this message or event is called Event Handling. The function which is responsible for handling this event (i.e. the code that is actually executed when you press a button) is called the Event Handler.
Borland’s Unique Way to Handle Events in C++
Borland C++ Builder uses closures to implement the delegation model and hence the “Event Handling story”. This happens by declaring a closure that can point to a sort of methods in a form like this:
-
__property OnClick;
-
__property Classes::TNotifyEvent OnClick = {read=FOnClick, write=FOnClick,
stored=IsOnClickStored};
-
Classes::TNotifyEvent FOnClick;
-
typedef void __fastcall (__closure *TNotifyEvent)
(System::TObject* Sender);
To understand what this is, let’s explain it from bottom to up:
- First of all, we have a closure type that can point to a function that takes a parameter of type
TObject*
and returns nothing, this should be the “Event Handler” that is going to be written by some programmer in some class. This closure type has assigned an alias called TNotifyEvent
using typedef
keyword. - This alias is used to declare a
private
closure in TControl
class to handle OnClick
event. - Since it is
private
, we cannot call it or assign a value to it, so Borland used its own method to change private
data as if they are public
. This happens using __property
keyword. Intuitively this property should be declared as protected
/public
/__published
not private
as well. - All above points are the infrastructure to the class that you are expected to use, which is
TButton
class. Hence we should raise the access level of our property (OnClick
closure) to be public
, but this will allow us to assign values to it in code only. What if we want to assign this value using our RAD Environment (the IDE)! Here is another new keyword added by Borland to the language. It is __published
keyword. In short, it gives you an access level equal to public
keyword, but it allows you to assign values using a few mouse clicks on Borland’s IDE with no need to add a single line to code.
That is it. To see what happens when you click a button, let’s reverse what we said.
Assume you have written some code in some function that finishes a certain job, and you named your function as OnClickHandler
.
Using the IDE, we may assign this function address to our closure (OnClick
) which is a member of class TButton
.
But, wait, OnClick
is not a variable in itself. It is just a gate to allow to you assign values to or retrieve them from real members (usually private
) using assignment operator (l-value
/r-value
) instead of using old-fashioned Set
/Get
methods. Our real closure that is going to be assigned this address is FOnClick
closure.
This assignment is called delegation.
TButton
objects are programmed to capture Click-Messages (events) sent by your operating system and then call FOnClick
closure as a response to this message (event), but did TButton
class handled the event? No, the real handler is your function which is written somewhere in some class. That is TButton
delegates your function to replace it in handling such events. By this way, each object of type TButton
has its own handler. This will not be the case if we defined the handler in the TButton
class, since it will be general for all TButton
objects. This means that all buttons would have the same job.
You may wonder, what if we used inheritance and virtual functions to implement a specific handler to each TButton
object. You are right. This is one solution by declaring a handler as a virtual function that has no body, and then you have to inherit from this class every time you want to add a button to your form, moreover you have to redefine your virtual function to suit your desired job.
Assume you are expected to program a scientific calculator that has more than 30 different buttons with very different jobs. Using Windows API, Borland’s OWL, or Microsoft’s MFC you have to do more than 30 inheritances. But with Borland’s C++ Builder, you have to make more than 30 assignment statements. Or even use mouse clicks in the IDE.
Redefining your virtual function is equal to defining your handlers, and there is no way to avoid this. Borland’s products are not smart enough to expect your button jobs.
Final Words
Just imagine your source-code file that contains 30 inheritances, and another that contains 30 assignments if any.
If you understood this article, you may be able to understand Charlie Calvert’s statement in his Borland C++ Builder Unleashed book.
“Delegation is an alternative to inheritance. It's a trick to allow you to receive the same benefits as inheritance but with less work.”
Thank you Borland.
About the Code
Code in part I and class traces in part II are tested against Borland's C++ Builder 6.0 Enterprise Edition.
Windows XP service pack 3.0 platform.
History
- 26th November, 2009: Initial post