VCL versus CLX

Brian Long (www.blong.com)

Table of Contents

If you find this article useful then please consider making a donation. It will be appreciated however big or small it might be and will encourage Brian to continue researching and writing about interesting subjects in the future.


Introduction

This paper will look at the new CLX library (pronounced clicks) that ships with all versions of Kylix and also with Delphi 6 and later. Delphi developers are familiar with the VCL, the Windows-only component library. During this session, we will look at how the VCL and CLX libraries compare, and also how they differ.

Clearly, with the two libraries being as large as they are, we can not get a thorough and complete comparison, but we will focus on some of the more important aspects during this session.

You can find out more about this subject through the references in the Further Reading section at the end of this paper.

Click here to download the files associated with this paper.

What Is CLX?

CLX is Borland's first cross-platform class library, designed for the release of Kylix 1 in March 2001. It was introduced after analysis of the VCL (used in Delphi 1 through to 5, and also C++Builder 1 through to 5) indicated that a straight port would be very difficult due to the VCL's inherent marriage to the Windows API.

Rather than struggle trying to get the VCL in a shape where it could be ported to another platform, they decided it would be most advantageous to start again, from scratch. The result was the CLX library, which in many respects works just like the VCL, but also in many respects is very different from its Windows-only counterpart.

If you want to write code for Windows only, you develop applications using the VCL. If you want to write applications whose source code can be compiled into either a Windows application or a Linux application, you develop using CLX. CLX is truly a source-level, cross-platform class library.

When work first started on CLX, Delphi 5 and C++Builder 5 were already out. Consequently, the model for CLX 1 (which was to be shipped first with Kylix 1) was VCL 5. As many components, routines and features from VCL 5 have been incorporated into CLX 1 as possible, making the development experience in Kylix 1 very similar to that in Delphi 5. Obviously, non-portable features such as COM, ActiveX and the like have no place in CLX, but that notwithstanding, the CLX library is still a rich development library.

CLX first shipped with Kylix 1, and is also supplied with Delphi 6 (and later). Delphi 6 has VCL 6 and CLX 1 supplied with it. This can sometimes be a bit confusing, as VCL 6 has many new features that are not present in CLX 1 (these will be added, where possible, in CLX 2).

CLX has been subdivided into four separate parts, as described below.

BaseCLX

This term describes the basic run-time library along with a few other key units that were historically considered part of the VCL. Table 1 shows the common units that make up BaseCLX. However, on both Windows and Linux, BaseCLX also contains the local API import units (Libc, Xlib, KernelDefs, etc. on Linux and Windows, ShellApi, CommCtrl, etc. on Windows). You can see the Kylix BaseCLX package in Figure 1).

Table 1: The main BaseCLX units

Unit name Description
System Core RTL unit providing basic operational routines and TObject
SysInit Core RTL unit that implements startup and shutdown code
SysUtils Common utility routines and exception classes
SysConst String constants used in SysUtils
RTLConsts String constants used in various RTL units
Types Common type definitions
Variants Portable Variant implementation
VarUtils Platform-neutral Variant support routines
Math Mathematical routines
DateUtils Date manipulation utilities (in addition to those found in SysUtils)
TypInfo RTTI support routines and data structures
Classes Basic component definitions along with streaming support
IniFiles Support for INI file manipulation
SyncObjs Synchronisation objects (event and critical section)
Contnrs Container classes, such as stacks and queues
Masks Mask class for comparing strings containing wildcards to a file mask
MaskUtils Mask manipulation routines
HelpIntfs Interfaces for extending the help system (in your apps and in the IDE)

Figure 1: The Kylix BaseCLX package

As you can imagine, with the RTL files in BaseCLX being common to Kylix and Delphi, a fair amount of conditional compilation is involved to cater for API differences. This takes the form shown in Listing 1.

Listing 1: Conditional compilation for Linux and Windows

uses
{$IFDEF MSWINDOWS}
  Windows,
{$ENDIF}
{$IFDEF LINUX}
  Types, Libc,
{$ENDIF}
  SysConst;

The first thing to notice is the use of the new MSWINDOWS symbol, defined in Delphi 6 onwards when compiling for any Windows platform (Win32, Win64 or whatever). This symbol does not exist in earlier versions of Delphi, but it is easy enough to define yourself.

The second thing to notice is that two separate $IFDEF directives are being used. It is not safe to check for LINUX and, if it is not set, assume you are compiling for Windows. Similarly it is not safe to check for MSWINDOWS and, if it is not set, assume you are compiling for Linux. Other potential platforms may be introduced in the future making such assumptions invalid.

As well as all the conditional code, another thing to notice in the case of both CLX and the VCL in Delphi 6 is that there are no longer separate assembly files that need assembling and linking into the System unit. All of these files have been turned into inline assembly code in the required places. In some cases, the inline assembly code has also been translated into pure Pascal code. This is probably to allow easier debugging for those not so au fait with assembly programming.

Listing 2 shows an example of the bilingual representation of an RTL routine (UpCase in this case). Note that the PUREPASCAL symbol is not defined, but you can readily rebuild the RTL using the Borland-supplied make file, defining any symbols you choose.

Listing 2: Pure Pascal translations of older inline assembler available

function UpCase( ch : Char ) : Char;
{$IFDEF PUREPASCAL}
begin
  Result := ch;
  case Result of
    'a'..'z':  Dec(Result, Ord('a') - Ord('A'));
  end;
end;
{$ELSE}
asm
{ ->AL      Character       }
{ <-    AL      Result          }

        CMP     AL,'a'
        JB      @@exit
        CMP     AL,'z'
        JA      @@exit
        SUB     AL,'a' - 'A'
@@exit:
end;
{$ENDIF}

VisualCLX

This part of CLX represents the visual components that would reside in the TWinControl hierarchy in the VCL.

The VCL makes use of natively implemented Windows controls and provides classes that turn those controls into components. Any component representing a control that is implemented by Windows (and therefore has a window handle) will be inherited at some point from TWinControl. However, this approach only works where Windows controls exist, which means the VCL only works on Windows platforms.

The VisualCLX framework is a set of classes that represent visual controls but must work where possible on both Microsoft Windows and in X Windows in Linux (these are the currently supported platforms, where both must be running on Intel-compatible chips). The controls represented by the VisualCLX components are implemented by a C++ class library called Qt (produced cute), from a Norwegian development company called Trolltech.

The Qt library has a strong presence on Linux, where (for example) the people who develop the popular KDE desktop environment use it. Qt implements a number of controls for X Windows applications, many of which mirror those available in Windows (X Windows implements no controls of its own). Qt is also available on Windows where the same classes allow C++ applications to be ported between Linux and Windows reasonably easily.

Qt is an example of a control library, one of many that exist (others include GTK+ and Athena). These control libraries are generally referred to as widget libraries, where a widget is the common Linux term for a control (from a contraction of window gadget).

All controls in VisualCLX are made from Qt widgets, and consequently, the equivalent of the VCL TWinControl class is called TWidgetControl (although the type TWinControl is defined to be the same as TWidgetControl).

Qt is a C++ class library and, because of differences in C++ and Object Pascal implementation details, an Object Pascal program cannot directly manipulate Qt widgets. Instead, VisualCLX makes use of an additional library, typically referred to as the Qt interface library. This library is also written in C++, but it exports all the Qt functionality in a manner that is accessible to Object Pascal code.

The import unit for this interface library is called Qt.pas and is referred to as the CLXDisplay API. In this unit you can find all the Qt functionality from the original C++ classes exposed as flat methods (a method of a class that is declared as a standalone subroutine). This means that rather than being declared as classes, the Qt widget methods are all imported as functions. However, since at the C++ side they are indeed classes, almost every flat method takes one extra parameter, which is the reference to the Qt widget.

In a normal Object Pascal application, you call methods via object references, for example:

Button1.SetBounds(10, 10, 75, 25);
When a normal method is turned into a flat method, the object reference is passed as the first parameter so the method code knows which instance it should be working against. Here is a fictitious example flat method, which is equivalent to the method just used:
TButton_SetBounds(Button1, 10, 10, 75, 25);

In the made-up examples shown here, there is little point using the flat method, as an object reference like Button1 always allows you access to its members. In the real case of the CLXDisplay API, however, the object references cannot be used in this way (which is why the flat methods are provided in the first place).

Any Qt constructor (which again is declared as a flat method) returns a reference to the C++ Qt widget, but this cannot be used as such in a CLX application. The flat constructor returns a pointer (which is what an object reference is), which is usable as the first parameter to appropriate flat methods, but not for direct access to the pertinent methods. Such a pointer is referred to as an opaque reference to a Qt widget.

Rather than leave these opaque references as raw pointers, CLX defines an Object Pascal class hierarchy to mirror the Qt hierarchy, but which defines no methods or properties at all for any of its classes. These classes are representative only. They allow what would otherwise be raw pointers to be defined of appropriate types in a hierarchy, and thereby allow the compiler to validate the use of these values. The flat constructors return values defined in terms of classes from this hierarchy.

Again, the returned value is of little use on its own, but is useful when passed to an appropriate flat method. It is quite common for the term handle to be used for a value that has a use in an application, but has no real meaning to the programmer.

Take for example a window handle, or a file handle; these mean nothing to the programmer, other than a means to uniquely identify a given window or a given open file. Opaque Qt references, when defined in terms of classes in the aforementioned Object Pascal class hierarchy (a portion of which can be seen in Listing 3) are referred to as Qt widget handles, or simple Qt handles for similar reasons.

Listing 3: Part of the Object Pascal Qt handle hierarchy

type
  QtH = class(TObject) end;
    QObjectH = class(QtH) end;
      QApplicationH = class(QObjectH) end;
        QClxApplicationH = class(QApplicationH) end;
      QWidgetH = class(QObjectH) end;
        QOpenWidgetH = class(QWidgetH);
        QButtonH = class(QWidgetH) end;
          QPushButtonH = class(QButtonH) end;
            QClxBitBtnH = class(QPushButtonH) end;
        QComboBoxH = class(QWidgetH) end;
          QOpenComboBoxH = class(QComboBoxH) end;
      QFrameH = class(QWidgetH) end;
        QTableViewH = class(QFrameH) end;
          QMultiLineEditH = class(QTableViewH) end;
You can see that the flat methods make use of these handle types by looking at some of their declarations (see Listing 4). The flat constructor for the QPushButton widget returns a QPushButton handle (defined as type QPushButtonH). All the other flat methods require a QPushButton handle as their first argument.

Listing 4: Some of the flat methods of QButton

function QPushButton_create(parent: QWidgetH; name: PAnsiChar): QPushButtonH; overload; cdecl;
function QPushButton_create(text: PWideString; parent: QWidgetH; name: PAnsiChar): QPushButtonH; overload; cdecl;
procedure QPushButton_destroy(handle: QPushButtonH); cdecl;
procedure QPushButton_setGeometry(handle: QPushButtonH; x: Integer; y: Integer; w: Integer; h: Integer); overload; cdecl;
procedure QPushButton_setGeometry(handle: QPushButtonH; p1: PRect); overload; cdecl;

We will come back to the CLXDisplay API later as we see how to customise CLX components. Before moving on though, Listing 5 shows how you can create, manipulate and destroy a Qt widget solely using Qt handles and Qt flat methods. Notice that the QPushButton constructor is called, followed by a QPushButton method. The next call is to a QButton method that QPushButton inherits and the last call is to the QPushButton destructor.

Listing 5: Simple manipulation of a Qt widget using the CLXDisplay API

uses
  Qt, QTypes;
...
var
  Btn: QPushButtonH;
...
procedure TForm1.FormCreate(Sender: TObject);
var
  Msg: TCaption;
begin
  Btn := QPushButton_create(Handle, PChar('Btn'));
  QPushButton_setGeometry(Btn, 10, 10, 75, 25);
  Msg := 'Press me';
  QButton_setText(Btn, PWideString(@Msg));
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  QPushButton_destroy(Btn);
end;

Of course you would normally have no need to do this, as most commonly required widgets are already wrapped up in CLX component classes. However, familiarity with the mechanics of interacting with Qt is useful to get under your belt.

Also useful is to note that in the VCL, a visual component's Handle property is a window handle. In a Visual CLX component, the Handle property is a Qt widget handle.

DataCLX

Since the BDE is not going to be ported anywhere from Windows, we need to understand how a cross-platform library like CLX can provide data access. The answer to this question is in dbExpress.

dbExpress is designed to be a replacement for the BDE, or ADO data accessing technologies, which are Windows-specific. It is a cross-platform, database-independent layer that provides methods for dynamic SQL processing. It defines a common interface for accessing a variety of SQL servers (InterBase, MySQL, Oracle and DB2 in the initial release). Legacy data file formats such as Paradox and dBASE are not supported.

For each supported server, dbExpress provides a driver, which acts as an independent library implementing the common dbExpress interfaces for processing queries and stored procedures. dbExpress drivers are available for both the Windows platform (as dynamic-link libraries) and the Linux platform (as shared object files).

dbExpress has been designed from the outset to be fast, lightweight and easy to deploy. As such, it does very little data processing of its own, but mainly acts as a thin wrapper around the database server's client-side software. It provides the advantages of a common API without the overhead of a more elaborate database engine such as the BDE. For example, dbExpress drivers return only unidirectional cursors and perform no data or metadata caching. By keeping the core runtime data-access layer thin and simple, dbExpress provides high performance database connectivity that can be easily adapted to new data sources.

The dbExpress interfaces defined in the DBXpress unit are ISQLDriver, ISQLConnection, ISQLCommand, ISQLCursor and ISQLMetaData (all of which are documented in the online help). Note that it is expected that the InterBase and MySQL dbExpress drivers are to have their source code released to help stimulate the 3rd party driver market.

As with the other data access components, there is a dedicated connection component, TSQLConnection. This allows you to specify what type of database you are connecting to and allows the driver details to be set up. Driver and connection details are stored in very simple text files (Windows INI files) making setup much easier than with the BDE, for example.

Some TDataSet descendant classes are provided that map directly onto the dbExpress interfaces: TSQLDataSet, TSQLQuery, TSQLStoredProc and TSQLTable. Since these use unidirectional cursors, data-aware controls will not co-operate with these dataset components. However this problem can be overcome if data aware controls are important to you.

On top of the core interfaces, application developers can create a layer for caching data and thereby provide forward and backward scrolling on the result set. For example, components such as a client dataset can provide the support for caching, scrolling, indexing, and filtering dbExpress result sets. TSQLClientDataSet is a provided client dataset descendant that combines a TSQLDataSet and TDataSetProvider.

Whilst the initial release in Kylix operated solely on the basis of external driver files (shared objects), the Windows version allows the drivers to be compiled into the application, reducing the deployment complexity. So when developing a Windows-based dbExpress application, you can either link to the DLL driver (dbexpint.dll, dbexpora.dll, dbexpmys.dll, or dbexpdb2.dll) or alternatively you can link the driver into your application by using the relevant compiled unit (dbexptint.dcu, dbexpora.dcu, dbexpmys.dcu, or dbexpdb2.dcu).

Very much as with other TDataSet descendants, these components are straightforward to use. If you use the basic classes (TSQLDataSet, TSQLQuery, TSQLStoredProc and TSQLTable) you will have to dispense with data aware controls and display any necessary data yourself in normal controls, due to their unidirectional nature.

If you are happy to have a bit of caching done for you and you use the TSQLClientDataSet component, then you can use data aware controls as you might with BDE or ADO controls when using the VCL (see Figure 2).

Figure 2: Using the dbExpress wrapper component to make use of a data aware control

NetCLX

The NetCLX portion of the CLX library seems to be the part that has changed least. Apart from the differences of being able to support ISAPI, NSAPI and WinCGI on Windows platforms, the WebBroker principles from the VCL transfer across perfectly well. Of course database-driven Web server applications must use dbExpress instead of BDE or ADO components but, other than that, Web-related things are very much the same as they are in Delphi 5 VCL, with the addition of support for Apache Web server applications and shared modules.

NetCLX also includes the Internet Direct suite of components (known as Indy). These are freeware components that work in Delphi, C++Builder and Kylix, and work with both the VCL and CLX libraries (see Figure 3).

Figure 3: Some of the Indy Server components

What's Not In CLX

There are many things that we are used to from writing VCL applications that are simply not present in CLX. This may be due to them relying on platform-specific features, and so not being appropriate for a cross-platform library. It may also be that the Borland developers have either not had the chance to implement the features yet, or have decided the features are not valuable enough to warrant migrating. Here is a list of the missing features:

What's Not Cross-Compatible?

When writing CLX applications on the Windows platform, there will be a certain amount of the BaseCLX support and also some components which will work in a Windows CLX application, but will not port across to Linux. The same is true in the other direction as well. Some features available in a Kylix CLX application will not compile in a Windows CLX application.

When writing cross-platform applications you should be aware of these issues and try and steer clear of them wherever possible. Here is a list of some of the important cases:

What's New In CLX?

Most of the new things in CLX have already been mentioned to some degree or other. Here is a summary list of them:

How Do The VCL and CLX Libraries Compare?

The CLX library was designed from the outset to be as similar to the VCL as possible. In general, this principle was upheld, but where appropriate, differences have been introduced. These differences reflect the fact that CLX is not Windows-dependent (in fact in some cases, the changes reflect the fact that it is dependent on the Qt widget library). Here are some key points.

Styles

Whilst you can change the styles of controls on an individual basis, the starting point will be the default application style. The Application object has a Style property, which refers to a TApplicationStyle object. TApplicationStyle inherits from TStyle, both of which are defined in the QStyle unit.

There are a number of properties that affect individual control appearances. For example, ButtonShift dictates how many pixels a button's image moves when it is pressed down, and CheckSize controls how large a checkmark will be. However the most interesting property is DefaultStyle, which can be given any value from the TDefaultStyle enumerated type (see Listing 6).

Listing 6: The available default styles

TDefaultStyle = (dsWindows, dsMotif, dsMotifPlus, dsCDE, dsQtSGI, dsPlatinum, dsSystemDefault);

A simple project called AppStyle.dpr accompanies this paper; it has a number of controls on it and a menu that allows you to choose any of the available styles (visible in Figure 4). Figure 5 shows the application sporting the Platinum style and Figure 6 shows it with the SGI style. When you unzip the accompanying files, there is a VCL and a CLX directory. This project is in the CLX directory.

Figure 4: Choosing a new style

Figure 5: The Platinum style

Figure 6: The SGI style

As well as these properties, the style object has a multitude of events that allow you to customise various aspects of different types of controls. Listing 7 shows them all.

Note that the OnPolish event is the only event added by TApplicationStyle (all the others are inherited from TStyle). OnPolish can be used to tweak the Application object properties before any of the painting starts.

Listing 7: The mass of events on offer by a TApplicationStyle object

OnPolish
AfterDrawButton
AfterDrawItem
AfterDrawMenuItem
BeforeDrawButton
BeforeDrawItem
BeforeDrawMenuItem
ButtonRect
ComboButtonFocusRect
ComboButtonRect
DrawArrow
DrawButtonFrame
DrawButtonLabel
DrawButtonMask
DrawCheck
DrawCheckMask
DrawComboButton
DrawComboButtonMask
DrawFocusRect
DrawFrame
DrawHeaderSection
DrawHint
DrawMenuCheck
DrawMenuFrame
DrawRadio
DrawRadioMask
DrawScrollBar
DrawSplitter
DrawTab
DrawTabMask
DrawTrackBar
DrawTrackBarGroove
DrawTrackBarGrooveMask
DrawTrackBarMask
ExtraMenuItemWidth
GetItemRect
HeaderSectionRect
MenuItemHeight
OnChange
ScrollBarMetrics
SubmenuIndicatorWidth
TabMetrics

Individual widgets have their own style objects; their Style property returns a TWidgetStyle object. TWidgetStyle also adds an OnPolish event to allow widget properties to be set appropriately before any painting begins.

The Life And Death Of A Control

Some of the key methods in the life and death of a VCL windowed control (TWinControl descendant) are different in a VisualCLX component. Let's see what's changed.

When a VCL control is displayed, a window handle is needed and so the virtual CreateHandle method is called. CreateHandle calls the virtual CreateWnd method to get a window handle and then sets the control's z-order.

CreateWnd sets things up for the Windows API call that will ultimately be called. It sets up the window creation parameters through another virtual method, CreateParams, which itself may well call CreateSubClass to make use of an existing Windows control class.

After CreateParams returns and various things have been checked, the virtual CreateWindowHandle method is called to finish off the job. CreateWindowHandle calls the Windows API CreateWindowEx to create the underlying control and assigns the resultant window handle to the Handle property.

At the other end of the control's life, when it needs to be disposed of, the DestroyHandle virtual method is called. This calls DestroyHandle for any child controls and then calls the virtual DestroyWnd method. DestroyWnd is intended to destroy the underlying windowed control and it does so by calling the virtual DestroyWindowHandle method. DestroyWindowHandle finishes the job by calling the Windows API DestroyWindow.

In CLX, the corresponding TWidgetControl class also has a chain of virtual methods responsible for setting up and pulling down an underlying Qt widget. When the control is required, the CreateHandle method is called, however unlike in the VCL, CreateHandle is not virtual. CreateHandle calls the virtual CreateWidget method first, which creates the underlying widget, storing the widget handle in the Handle property. It then creates an appropriate hook object for the widget and assigns its handle to the Hooks property, so that the component can react to interesting things that happen to the widget.

After CreateWidget, CreateHandle then calls the virtual InitWidget, which initialises the freshly created widget settings. It then goes on to call HookEvents, which uses the hook object to connect Object Pascal methods to the widget signals.

When the widget is no longer required the non-virtual DestroyHandle method is called. This calls the virtual DestroyWidget which firstly calls the dynamic WidgetDestroyed method (which destroys the hook object) and then proceeds to destroy the widget itself.

Table 2 shows a comparison of these call chains.

Table 2: A comparison of creating and destroying controls in VCL and CLX

  VCL CLX
Control/widget creation
CreateHandle
CreateWnd
CreateParams
CreateSubClass
CreateWindowHandle
CreateWindowEx
CreateHandle
CreateWidget
widget constructor
InitWidget
HookEvents
Control/widget destruction
DestroyHandle
DestroyWnd
DestroyWindowHandle
DestroyWindow
DestroyHandle
DestroyWidget
WidgetDestroyed
widget destructor

Windows Messages vs Qt Events

In this section we will see how to intercept the miscellaneous system level messages that are constantly delivered to the underlying Windows controls and Qt widgets. Whilst this might appear to be a section dedicated to component writers, it is not. It is very common for application developers to handle certain messages that are delivered to their forms, or to customise the behaviour of some components by having them react differently to certain stimuli.

CLX applications have the potential of running under Microsoft Windows and X Windows (and maybe other platforms in the future). Windows messages are specific to the Microsoft Windows platforms, and so to promote cross-platform capabilities, CLX applications do not propagate Windows messages through to your component message handlers.

Clearly, as Windows programmers, we will need to spend some time acclimatising to this lack of Windows messages. More specifically, we should learn what mechanism is used by Qt in lieu of Windows messages. We need to understand what cross-platform solution is used by Qt to indicate that certain system events have happened (like a mouse being pressed, released or moved, or a key being pressed or released).

In a CLX application, the equivalent of a Windows message is a Qt event. Whilst a Windows message is typically represented by a record (such as TMessage) containing the message ID, and the two numeric values that accompany it, Qt events are represented by objects. There is a base QEvent class (and a corresponding QEventH handle class) which has numerous descendants such as QKeyEvent and QMouseEvent.

When a CLX program is running on the Windows platform, these Windows messages are picked up by the Qt event loop and turned into Qt events and sent on their way to the appropriate Qt widgets. Similarly, when running on X Windows, the low level X events are picked up by the Qt event loop and turned into Qt events before being passed to the appropriate Qt widgets.

If you were writing a VCL component (even just inheriting an existing component to make a small modification, or inheriting from a form), you could pick up Windows messages either in dedicated Windows message handling methods, or in an overridden version of the window procedure method, WndProc. WndProc is fed every message directed at the component, allowing you to perform appropriate actions in response to them if needed. The WndProc signature looks like this:

procedure WndProc(var Message: TMessage); virtual;
In a CLX application, Qt events do not have the equivalent of message handling methods, but they are all fed to a virtual method. However, the virtual method is called an event filter and has this signature:
function EventFilter(Sender: QObjectH; Event: QEventH): Boolean; virtual;

You override this in a CLX component and it receives all the events that are destined to reach the underlying Qt widget. The Sender parameter is the underlying Qt widget handle (the same as the value of your component's Handle property) and the Event parameter is a handle to the event object.

You can find out the type of the event by passing the event handle to the QEvent flat method QEvent_type. This returns a value from the QEventType enumerated type, which includes values such as QEventType_MouseButtonPress, QEventType_MouseButtonRelease, QEventType_MouseMove, QEventType_KeyPress and QEventType_KeyRelease.

Once you know the type of the event, you can typecast the event handle into one of the inherited handle types appropriate for the event, such as QMouseEventH or QKeyEventH. Each of these inherited classes has more flat methods. For example, the mouse event object has methods to find the current mouse co-ordinates and the key event object has a method for returning the pressed key.

Overriding The Virtual Handler In A New Component

VCL

To give a generic example of hooking a VCL control's window procedure, and what it picks up, I've written a new component called TButtonEx (a simple descendant of TButton). This class overrides WndProc and, for each message that arrives, it passes the message record along to a custom event call OnMessage. There's also a sample application in the VCL directory of the files that accompany this paper. The project is called WindowProcTrace.dpr and has a TButtonEx component placed on its form. An event handler for its OnMessage event adds a descriptive message into a list view for each message that arrives. To run the program you will need to add the component unit, ButtonEx, into a design-time package and install it. Figure 7 shows the program running. You can see the list view has information on a whole series of messages that were delivered to the button. You can also see a check box that enables or disables the message trace.

Figure 7: A WndProc method that traces messages

The window procedure method itself is quite straightforward (as shown in Listing 8). Note that if you do not wish for some messages to reach the button, then you can simply omit the call to the inherited window procedure for that message.

Listing 8: An overridden VCL WndProc method

type
  TMessageEvent = procedure(Sender: TComponent; var Msg: TMessage) of object;

  TButtonEx = class(TButton)
  private
    FOnMessage: TMessageEvent;
  protected
    procedure WndProc(var Message: TMessage); override;
  published
    property OnMessage: TMessageEvent read FOnMessage write FOnMessage;
  end;
...
procedure TButtonEx.WndProc(var Message: TMessage);
begin
  if Assigned(OnMessage) then
    OnMessage(Self, Message);
  //Do not call inherited window proc if you want to hide the message
  inherited;
end;

The actual event handler in the form unit is also quite simple. It calls a helper routine that looks at the message and writes the corresponding constant name, as well as the accompanying numeric values and the time, in the list view control.

CLX

The CLX directory in the accompanying files has an equivalent component and sample project that shows the CLX approach to this problem. The button descendant overrides the EventFilter method (as shown in Listing 9) and similarly passes the event handle to an event, simply so it is easy for the sample project to add descriptive information to the list view control.

Note that if you do not wish for some events to reach the button, then you can make the event filter return True.

Listing 9: An overridden CLX EventFilter method

type
  TQtEvent = procedure(Sender: TComponent; Widget: QObjectH; Event: QEventH) of object;

  TButtonEx = class(TButton)
  private
    FOnQtEvent: TQtEvent;
  protected
    function EventFilter(Sender: QObjectH; Event: QEventH): Boolean; override;
  published
    property OnQtEvent: TQtEvent read FOnQtEvent write FOnQtEvent;
  end;
...
function TButtonEx.EventFilter(Sender: QObjectH; Event: QEventH): Boolean;
begin
  if Assigned(OnQtEvent) then
    OnQtEvent(Self, Sender, Event);
  //Return True if you want to hide the event
  Result := inherited EventFilter(Sender, Event)
end;

You can see the program, EventFilterTrace.dpr, in action in Figure 8.

Figure 8: An event filter that traces Qt events

Injecting A New Handler In Another Component

Overriding the virtual WndProc or EventFilter method is fine if you are actually inheriting a new class. However, what if you want to intercept messages or events of an existing object? How do you intercept the window procedure or event filter without overriding the virtual methods?

VCL

In the VCL, this is catered for with the WindowProc property. WindowProc points to the control's window procedure, and it defaults to pointing at WndProc. Any other class can define a suitable method and assign it to WindowProc in order to intercept all messages.

The idea is to look at the message and, if it one you are after, you respond to it accordingly. If you wish to let the message continue on its path to the control, then you must call the original window procedure that you will have saved before setting up the new one. If you want to hide the message, do not call the original window procedure.

A sample VCL project is supplied with this paper, called WindowProcHook.dpr. This project looks much like the previous projects, but this time the check box either installs the new window procedure or restores the original one.

You can see the main code from the program in Listing 10. The window procedure is set or restored by the SetNewBtnWndProc method, which takes a Boolean parameter to indicate what to do. If a new window procedure is being set, the old one is stored first in a private data field (FOldBtnWndProc), otherwise it is restored from this data field (assuming a valid value is found).

As you can see, the replacement window procedure does little other than add a descriptive entry into the list view before calling the original window procedure. The program looks pretty similar to Figure 7 when running.

Listing 10: Intercepting a button's window procedure

type
  TForm1 = class(TForm)
    ...
  private
    FOldBtnWndProc: TWndMethod;
    procedure NewBtnWndProc(var Message: TMessage);
  public
    procedure SetNewBtnWndProc(Enable: Boolean);
  end;
...
procedure TForm1.chkWndProcHookedClick(Sender: TObject);
begin
  SetNewBtnWndProc(chkWndProcHooked.Checked)
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  SetNewBtnWndProc(False);
end;

procedure TForm1.SetNewBtnWndProc(Enable: Boolean);
begin
  if Enable then
  begin
    FOldBtnWndProc := Button1.WindowProc;
    Button1.WindowProc := NewBtnWndProc;
  end
  else
    if @FOldBtnWndProc <> nil then
    begin
      Button1.WindowProc := FOldBtnWndProc;
      FOldBtnWndProc := nil;
    end;
end;

procedure TForm1.NewBtnWndProc(var Message: TMessage);
var
  Item: TListItem;
begin
  //Make new list view item
  Item := Lst.Items.Add;
  //Set up new list view item with message description
  Item.Caption := MsgIDToStr(Message.Msg);
  Item.SubItems.Add(Format('$%x (%0:d)', [Message.WParam]));
  Item.SubItems.Add(Format('$%x (%0:d)', [Message.LParam]));
  Item.SubItems.Add(FormatDateTime('h:nn:ss.zzz', Time));
  Item.Selected := True;
  Item.Focused := True;
  Item.MakeVisible(False);

  //Do not call the original window procedure if you want to hide this message
  FOldBtnWndProc(Message);
end;
CLX

Having seen the VCL solution, we now need to see the CLX solution. To intercept some specified control's Qt events, we need to install a new event filter for the underlying Qt widget (as opposed to the CLX component). The event filter method we overrode earlier is installed by the CLX code, but we need to install a new one of our own for the specified component.

Unfortunately, installing an event filter for a widget is not a straightforward job. An Object Pascal method cannot be successfully passed to the Qt widget, so a hook object has to be used as an intermediary. Hook objects are implemented in the Qt interface library. Their job is to act as proxy objects for Object Pascal event filters and slots.

Whenever you need to install a new event filter, you pass it to an appropriate method of the hook object. The hook object installs its own C++ event filter, whose sole job is to call your event filter. There is a hierarchy of hook objects and the CLXDisplay API defines a number of handle classes in a manner that mirrors the real hierarchy. A portion of the hierarchy is shown in Listing 11.

Listing 11: Part of the Qt hook handle class hierarchy

type
  QObject_hookH = class(TObject) end;
    QApplication_hookH = class(QObject_hookH) end;
    QWidget_hookH = class(QObject_hookH) end;
      QButton_hookH = class(QWidget_hookH) end;
        QPushButton_hookH = class(QButton_hookH) end;
      QComboBox_hookH = class(QWidget_hookH) end;
      QFrame_hookH = class(QWidget_hookH) end;
        QTableView_hookH = class(QFrame_hookH) end;
          QMultiLineEdit_hookH = class(QTableView_hookH) end;

There are flat methods defined for these hook classes as we will see later, but for now, we need to know about only one flat method, which applies to all hook objects, and it is shown in Listing 12. Qt_hook_hook_events takes a hook handle (standard fare for a hook object flat method) and a reference to some method.

Listing 12: The common hook flat method for installing a new event filter

type
  //from QControls.pas
  TEventFilterMethod = function (Sender: QObjectH; Event: QEventH): Boolean of object cdecl;
  ...
  //from Qt.pas
  QHookH = TMethod;

procedure Qt_hook_hook_events(handle: QObject_hookH; hook: QHookH); cdecl;

The parameter definition for the method is a little imprecise, as QHookH (or TMethod) is a procedure method with no parameters and using the default register calling convention. The event filter method actually has specific parameter and calling convention requirements as defined in the TEventFilterMethod type (which comes from the QControls unit), so a typecast will be required to pass in an appropriate method.

A sample CLX project is supplied (EventFilterHook.dpr) that shows an event filter being installed. The code is quite similar to that of the WindowProcHook.dpr VCL project, apart from the event filter itself and the code to install it. That code can be seen in Listing 13, where NewBtnEventFilter (defined as per the TEventFilterMethod type) is installed using a typecast and the aforementioned flat method.

Listing 13: Installing a new event filter for a Qt widget

type
  TForm1 = class(TForm)
    ...
  private
    FBtnHook: QButton_hookH;
    function NewBtnEventFilter(Sender: QObjectH; Event: QEventH): Boolean; cdecl;
  public
    procedure SetNewBtnEventFilter(Enable: Boolean);
  end;
...
procedure TForm1.SetNewBtnEventFilter(Enable: Boolean);
var
  Method: TMethod;
begin
  if Enabled then
  begin
    FBtnHook := QButton_hook_create(ButtonEx1.Handle);
    TEventFilterMethod(Method) := NewBtnEventFilter;
    Qt_hook_hook_events(FBtnHook, Method);
  end
  else
    FBtnHook.Free
end;

function TForm1.NewBtnEventFilter(Sender: QObjectH;
  Event: QEventH): Boolean;
var
  Item: TListItem;
begin
  //Make new list view item
  Item := Lst.Items.Add;
  //Set up new list view item with message description
  Item.Caption := EventToStr(Event);
  Item.SubItems.Add(FormatDateTime('h:nn:ss.zzz', Time));
  Item.Selected := True;
  Item.Focused := True;
  Item.MakeVisible(False);

  //Return True if you want to hide the event
  Result := False;
end;

As you can see, this new event filter does nothing much other than add an entry to the list view. If you wish to stop an event getting to its intended recipient (the Qt widget) you should return True, otherwise, return False to let the event carry on its journey.

Notice that a private data field is used to hold the hook object handle whilst it exists, and that SetNewBtnEventFilter takes care of creating and destroying it. I should point out that if you are merely making a shallow derivative of an existing CLX component, there is typically no need to create and destroy your own hook object, as the ancestor class will already do this for you. However this program needs to be able to install and uninstall an event filter for some specified widget at will (through the checkbox on the form) and so a separate hook object is useful (destroying the hook object uninstalls the event filter).

A widget component's hook object is accessible through its public Hooks property. The only thing you need to check is that a hook object of an appropriate type is being created. You will find the pertinent code in the ancestor class's CreateWidget method. Listing 14 shows the CreateWidget method from TButton, creating a QPushButton widget and a QButton_hook hook object.

Listing 14: TButton creating a widget and a hook object

procedure TButton.CreateWidget;
begin
  FHandle := QPushButton_create(ParentWidget, nil);
  Hooks := QButton_hook_create(Handle);
end;

Again, the results of running this program are rather similar to the last CLX project (Figure 8).

Windows Notifications vs Qt Signals

We have now seen how to gain access to the general mass of messages/events that are directed to a given control or widget. However, it is common for individual controls to keep track of these messages themselves. Whenever a combination of messages/events arrives that indicate something of interest, they tend to make a special kind of notification for any interested parties.

This section will look into how we actually pick up these special notifications in case we face a situation where we need to provide custom responses to things. Again, component writers will need to do this sort of thing a lot, but normal application developers may also find themselves needing to customise component behaviour in this way.

For example, if a button control spots a mouse down message, followed by a mouse up message, this indicates the button has been clicked. The button will make this fact known to the application in some way. In the case of a Windows control, the button sends a notification message to its parent control (which may be a form or a panel, for example). A notification message is either a WM_COMMAND or WM_NOTIFY message accompanied by appropriate information.

For component programmers, this makes things tricky. The button has been clicked, but it is the parent that is informed. To help componentise things, the VCL spots the notification message being sent to the parent, and sends a customised version of the same message straight back to the button (or whatever control it is). To ensure no ambiguity, WM_COMMAND messages are turned into CN_COMMAND messages (the CN prefix stands for Component Notification).

The button component class can pick up this message either with a message handler or in an overridden WndProc method and respond accordingly. If you look back at Figure 7 you can see a CN_COMMAND message that indicates the button was pressed.

So, VCL programmers can pick up special interest notifications using the same approach as normal messages. By contrast, CLX programmers must use a different approach because Qt deals with special interest notifications differently.

When a Qt widget receives events that indicate something interesting has happened, it emits a signal. For example, a button has a clicked signal. The signal is connected to a slot in C++ Qt applications, where a slot is a compatible method for the signal. Again, due to issues with C++, Object Pascal methods cannot be used directly as slots.

This is the other reason we use hook objects, to connect an Object Pascal method indirectly to a Qt widget signal. The hook object has a method for each available signal, which takes the Object Pascal version of the slot. Whenever the signal is emitted, the hook object's slot is executed, which in turn calls the Object Pascal method.

To learn how the VCL and CLX libraries enable you to react to these special notifications, we will use a button being clicked as our example. Clearly, you would never need to use these approaches to react to a button being clicked, as both the VCL and CLX button components do this for you. However, it makes a good example as everyone can relate to what we are trying to achieve. The principles you observe here will serve you well with any other widget.

VCL

In the case of the VCL, the job is easy. It has already been mentioned that a button being clicked results in a special message being delivered to it. Therefore, to pick up this message either requires a dedicated message handler, or some code in WndProc.

A sample VCL project called WindowProcTraceAndNotificationResponse.dpr accompanies this paper and shows two things. Firstly it shows how to trace any messages that come along (as we did earlier, by adding descriptions to a list view). It also shows how to pick up a notification message and respond to it.

The project uses another custom button component, TButtonEx2, from the ButtonEx2.pas unit, whose WndProc method can be seen in Listing 15. You will need to install this component unit before you can open the project.

As you can see, it just checks the message is CN_COMMAND and then checks the high word of the WParam value to see if it was a click notification. If it is, it calls the OnClickNotification event, assuming it has an event handler. The sample project has an event handler for the OnClickNotification event that displays a simple message box to indicate it has been called.

Listing 15: Trapping a notification message in the VCL

procedure TButtonEx2.WndProc(var Message: TMessage);
begin
  if Assigned(OnMessage) then
    OnMessage(Self, Message);
  if (Message.Msg = CN_COMMAND) and (Message.WParamHi = BN_CLICKED) and
     Assigned(OnClickNotification) then
    OnClickNotification(Self);
  //Do not call inherited window proc if you want to hide the message
  inherited;
end;

Note that you can still prevent this message getting to the button, so you can prevent other responses to it occurring. However, the VCL TButton component actually generates its OnClick event after receiving a mouse down message followed by a mouse up message. It does not generate it in response to the notification message (for its own reasons). Consequently, by stifling this particular notification message, you could not stop the OnClick event from happening, but you could stop it by stopping some other message (mouse up) from getting to it.

CLX

In the case of a CLX application, things are a little more involved. We need to use a hook object to hook the clicked signal. A new CLX button descendant, TButtonEx2, is in the QButtonEx2 unit with the relevant code in it. Once you install this unit, you can open the sample CLX project EventFilterTraceAndSignalSlot.dpr.

The component has overridden CreateWidget and DestroyWidget methods which would be where the hook object would be created and destroyed (along with the button widget) were it not for the fact that we are inheriting from a class that already does this (as we saw in Listing 14). Because of this, these two methods contain nothing more than comments and a call to the inherited version.

Listing 16 shows the main parts of this new class (the event filter has been omitted as we have seen it before). You can see that the HookEvents method is being used to set up the response for the clicked signal, which is a C calling convention method called ClickedSlot. All this method does is call an event handler if one has been set up, and the sample project has such an event handler to display a simple message box.

Listing 16: Setting up an Object Pascal slot for a widget signal

type
  TQtEvent = procedure(Sender: TComponent; Widget: QObjectH; Event: QEventH) of object;

  TButtonEx2 = class(TButton)
  private
    FOnClickSignal: TNotifyEvent;
    FOnQtEvent: TQtEvent;
  protected
    procedure ClickedSlot; cdecl;
    procedure CreateWidget; override;
    procedure DestroyWidget; override;
    procedure HookEvents; override;
  published
    property OnClickSignal: TNotifyEvent read FOnClickSignal write FOnClickSignal;
    property OnQtEvent: TQtEvent read FOnQtEvent write FOnQtEvent;
  end;
...
procedure TButtonEx2.ClickedSlot;
begin
  if Assigned(OnClickSignal) then
    OnClickSignal(Self)
end;

procedure TButtonEx2.CreateWidget;
begin
  //The inherited CreateWidget creates a pushbutton widget, and a
  //button hook object, so we do not need to do anything
  inherited;
  //If this were a component based on a new widget, we would:
  // 1) create the widget and assign its handle
  //    to the component's Handle property, then
  // 2) create a hook object and assign it to the Hooks property.

  //Hooks := QPushButton_hook_create(Handle);

  //As it is, we already get a suitable widget and hook object
  //by calling the inherited version of CreateWidget
end;

procedure TButtonEx2.DestroyWidget;
begin
  inherited;
  //Here we would destroy the widget and hook object,
  //if we were responsible for creating them
end;

procedure TButtonEx2.HookEvents;
var
  Method: TMethod;
begin
  inherited;
  QButton_clicked_Event(Method) := ClickedSlot;
  QButton_hook_hook_clicked(QButton_hookH(Hooks), Method);
end;

It is worth pointing out that you can install as many Object Pascal "slot" methods as you like for any given signal, and all of them will be called when the signal is emitted. Whilst you can prevent Qt events getting to the widget by returning True from the event filter, you cannot stop signals coming from the widget. When the widget detects that it has been clicked, it will cause all connected slots to execute, one after the other.

The TButton component already has a hook for the clicked signal, and calls its OnClick event in response. If you set up both an OnClick and an OnClickSignal event handler, both would execute when the button is clicked, emphasising the above statement.

Summary

In a 75 minute conference talk, we are unable to look thoroughly at each individual difference between the VCL and CLX libraries, but we focused on some of the key areas, where differences are important.

There follows a list of references to other articles that discuss some of the issues raised here in more depth.

Further Reading

  1. Cross-platform Controls: From Windows to Linux, and Back by Robert Kozak (Borland R&D), August 2000.
    This is a good introduction to CLX for component writers, explaining the differences in the class hierarchy and the key virtual methods and presenting the first cross-platform CLX component.
    Both this article and the following one require you to become a member of Delphizine before allowing you to read them.
  2. The Life and Death of TButton: An Under-the-Hood Comparison of the VCL and CLX by Robert Kozak (Borland R&D), January 2001.
    This article looks at the TButton class to see how the VCL and CLX implementations differ. This helps give a better understanding of how CLX components operate.
  3. How CLX Uses Qt by Brian Long, The Delphi Magazine, Issue 70, June 2001.
    This article looks under the hood of CLX and finds out how the Qt C++ class library is used from Object Pascal. It introduces all the key component areas where Qt entities are manipulated and explains the mechanics of how the CLXDisplay API (the Qt.pas unit) allows an Object Pascal program to talk to C++ classes.
  4. Programming Kylix with the CLXDisplay API by Bruno Sonnino, April 2001.
    This article shows how to talk to Qt from CLX applications using the CLXDisplay API (the Qt.pas unit).
  5. Internals of dbExpress by Ramesh Theivendran (Borland R&D), July 2000.
    This was the initial draft specification of the dbExpress cross-platform data access layer.

About Brian Long

Brian Long used to work at Borland UK, performing a number of duties including Technical Support on all the programming tools. Since leaving in 1995, Brian has spent the intervening years as a trainer, trouble-shooter and mentor focusing on the use of the C#, Delphi and C++ languages, and of the Win32 and .NET platforms. In his spare time Brian actively researches and employs strategies for the convenient identification, isolation and removal of malware. If you need training in these areas or need solutions to problems you have with them, please get in touch or visit Brian's Web site.

Brian authored a Borland Pascal problem-solving book in 1994 and occasionally acts as a Technical Editor for Wiley (previously Sybex); he was the Technical Editor for Mastering Delphi 7 and Mastering Delphi 2005 and also contributed a chapter to Delphi for .NET Developer Guide. Brian is a regular columnist in The Delphi Magazine and has had numerous articles published in Developer's Review, Computing, Delphi Developer's Journal and EXE Magazine. He was nominated for the Spirit of Delphi award in 2000.