Delphi 2 - Delphi 2.0's VCL Architecture

By: Xavier Pacheco

Abstract: This paper discusses Delphi 2.0's Visual Component Library (VCL)-the framework fromThis paper discusses Delphi 2.0's Visual Component Library (VCL)-the framework from which all Delphi applications are built.

The Visual Component Library
By Xavier Pacheco
Delphi Developer Support
Borland International

VCL hierarchy overview
The figure below illustrates a sub-set of Delphi 2.0's VCL hierarchy. The objects shown in the hierarchy the key classes from which components descend. Each object introduces a certain set of methods, events and properties and has a special purpose.

Object Hierarchy Diagram

Earlier in this paper we discussed four types of components: standard controls, custom controls, graphical controls and non-visual components. The following paragraphs discusses how these different types of components relate to the objects shown in the hierarchy.

Non-visual components are descendants of TComponent. Whereas TObject is the base class from which all classes descend, TComponent is the base class from which all components descend. This paper will discuss the TComponent class in more detail shortly.

The TGraphicControl class provides the capability to have controls which are not windowed controls (they have no window handle). Therefore graphical controls descend from TGraphicControl.

TWinControl is the base class from which all windowed controls descend. It is at the TWinControl level that the window handle is introduced. Both standard controls and custom controls which are windowed and are therefore descendants of TWinControl. Custom controls, however, are not likely to descend directly from TWinControl and will descend from TCustomControl.

TObject is the base class from which all other classes descend. Since all classes descend from TObject, every class inherits the methods which are defined by TObject. This gives all classes certain functionality. For example, every class can tell you its name, its type, and even it's ancestry.

TObject's definition comes from the SYSTEM unit and is defined as:

  TObject = class
    constructor Create;
    destructor Destroy; virtual;
    procedure Free;
    class function NewInstance: TObject; virtual;
    procedure FreeInstance; virtual;
    class procedure InitInstance(Instance: Pointer): TObject;
    function ClassType: TClass;
    class function ClassName: string;
    class function ClassParent: TClass;
    class function ClassInfo: Pointer;
    class function InstanceSize: Word;
    class function InheritsFrom(AClass: TClass): Boolean;
    procedure DefaultHandler(var Message); virtual;
    procedure Dispatch(var Message);
    class function MethodAddress(const Name: string): Pointer;
    class function MethodName(Address: Pointer): string;
    function FieldAddress(const Name: string): Pointer;

These methods are documented in Delphi 2.0's online help.

Note that some methods that are preceded by the keyword class. These methods can be called like a normal procedure or function from the class type. This means that you don't have to have an instance of this class in order to call such methods. This functionality was borrowed from C++'s static functions.

All components must descend from TComponent or from a TComponent descendant. TComponent, being a descendant of TObject, inherits TObjects methods, fields and properties.

Objects which descend from objects higher than TComponent in the VCL hierarchy are non-component classes. Some useful non-component classes are TStringList, TIniFile and TPrinter. You can look up these classes in the online help if you're unfamiliar with them.

TObject's Create constructor and Destroy destructor allocate and de-allocate memory for the object's instance respectively. The TObject.Create constructor returns a reference to the object being created.

Most of TObjects methods are used internally by the VCL. You will primarily call the Create and Free methods. One caution: try to use the Free method instead of calling the Destroy destructor directly when freeing an object instance. Free is a bit safer to use because it first checks to make sure the component instance is not Nil before destroying it.

The TPersistent class descends directly from TObject. The special characteristic of TPersistent is that it is an abstract class that defines the methods that allow it to be streamed. TPersistent defines no special properties or events, but does define certain methods of use to the Component Writer. The table below shows these methods.

AssignThis public method allows a component to assign the data associated with another component to itself.
AssignToThis protected method allows a component to override the implementation of the Assign method. TPersistent itself, raises an exception when this method is called. It is up to TPersistent descendant to override this method to define its implementation. The TClipboard class is an object that does this, for example.
DefinePropertiesThis protected method that allows component writers to define how the component stores extra or unpublished properties. By default, a component automatically stores published properties.

The TComponent class is a direct descendant of TPersistent. As we said earlier, all components are TComponent descendants. TComponent's special characteristics are that its properties are streamable and can be manipulated at design-time through the Object Inspector. TComponent can also own other components.

Certain non-visual components that descend from TComponent are also capable of being manipulated at design time. One such is the TTimer component. TTimers are not visual controls but still are available on the Component palette.

TComponent defines several properties and methods that give it its special functionality. It's properties are defined in the table below.

Component NamePurpose
OwnerRefers to the component's owner.
ComponentCountNumber of components owned.
ComponentIndexThe position of this component in its owners list of components. The first component in this list has the value of zero.
ComponentsA property array containing a list of components that are owned by this component.
ComponentStateThe current state of a component. Look up TComponentState in Delphi 2.0's online help for additional information on this property.
ComponentStyleA style that dictates the behavior of a component. Look up TComponentStyle in Delphi 2.0's online help for additional information on this property.
NameThe component's name.
TagAn integer property which has no defined meaning and can therefore be used at the developer's discretion to hold any user defined data. Since this value is an integer type, pointers to data structures, or even object instances may be referred to by this property.
DesignInfoInternally used by the Form's Designer. Do not access this property.

The methods defined by TComponent have to do with TComponent's capability to own other components and its accessibility in the Object Inspector. TComponent's method definitions are shown below:

  TComponent = class(TPersistent)
  constructor Create(AOwner: TComponent); virtual;
  destructor Destroy; override;
  procedure DestroyComponents;
  procedure Destroying;
  function FindComponent(const AName: string): TComponent;
  function HasParent: Boolean; dynamic;
  procedure InsertComponent(AComponent: TComponent);
  procedure RemoveComponent(AComponent: TComponent);

The methods Destorying and DestroyComponents sets the component and its owned components to a state indicating that they are being destroyed You'll probably never have to deal with these methods directly.

The FindComponent method is handy when you want to refer to a component of which you only know the name but to which you don't have a reference. For example, suppose you know that the main form has a TEdit component named "Edit1". To get a pointer to Edit1's instance use the following code.

procedure TMainForm.Button1Click(Sender: TObject);
  EditInstance: TEdit;
  EditInstance := FindComonent.('Edit1');

Here, we are issuing the FindComponent of the main form; therefore this code will work as long as it resides in a method of the main form. EditInstance must be a TEdit type. You can access properties, and methods of the returned instance of FindComponent as shown below as well.

TEdit(FindComponent('Edit1')).Text := 'Hello';

It is necessary to typecast FindComponent's return reference because the return reference is of the type TComponent. By typecasting it to the type of the component to which it refers, you can access the special properties and methods of that component type.

A component's HasParent method returns a boolean value indicating whether or not the component has been assigned a parent. You would use this method before referring to a component's parent. Note, that this method does not indicate whether or not a component has an owner.

The InsertComponent method adds the component passed as a parameter as an owned component and RemoveComponent removes a component from the list of owned components.

TControls defines properties, methods, and events common to visual components. TControl for example has the capability to display itself. Therefore, some of its properties have to do with its size and position: Top, Left, Width and Height. Other properties are ClientRect, ClientWidth ClientHeight.

TControl also introduces various properties having to do with its appearance and accessibility, such as: Visible, Enabled and Color. The Font property lets you specify a particular font for the control. Also you can set the text for the control through TControl's Text and Caption properties.

TControl contains several mouse and drag-drop events required of visual controls. Such events are: OnClick, OnDblClick, OnMouseDown, OnMouseMove, OnMouseUp, OnDragOver, OnDragDrop, and OnEndDrag. One interesting note about these events is that they are declared in the protected section of TControl. This is because TControl is most likely to be descended from rather than being used directly. Declaring TControl's properties and events in the protected section allows writers of descendant components to determine which properties and/or events to make public or published.

Another important characteristic of TControl is that it may have a parent. This parent must be a descendant of TWinControl since parent controls must be windowed controls. Since TControl introduces the concept of having a parent it introduces the Parent property which refers to its parent.

Most of the Delphi 2.0 controls are descendants of either TWinControl or TGraphicControl both of which are discussed next.

The TWinControl class encapsulates a window-controls with a window handle. Certain descendants of TWinControl such as TEdit, TListBox and TComboBox encapsulate the standard Windows controls such as edit controls, list boxes, combo boxes respectively. Since these descendant components encapsulate the functionality of standard controls, you don't have to manipulate them through Win32 API functions. Instead you manipulate them through properties and methods provided by the various control components.

TWinControls have three basic characteristics: they have a window handle, they receive input focus, and they can be parents to other controls. Therefore, many of TWinControl's properties, methods and events have something to do with focus changing, keyboard events and the displaying of child controls.

TWinControl's various properties having to do with focus changing and control appearance are shown in the table below.

Used for drawing patterns and shapes of the control.
ControlsMaintains a list of controls to which the said TWinControl is parent.
ControlCountThe number of children controls.
Ctl3dSpecifies whether or not to draw the control using a three-dimensional appearance.
HandleRefers to the handle of the Win32 object which the said TWinControl encapsulates. You would pass this property to Win32 API functions where a windows handle is required as a parameter.
HelpContextA help context number corresponding to a help screen in a help file. This property makes it possible to provide context-sensitive help for individual controls.
ShowingSpecifies if the control is visible or not.
TabStopSpecifies if the user can tab to the control.
TabOrderSpecifies where in the parents list of tabbed controls the said control is positioned.

TWinControl's methods mainly have to do with window creation, focus control, message dispatching, and positioning.

TWinControl's methods are of interest to Component Writers; their definitions are shown below:

TWinControl = class(TControl)
  procedure Broadcast(var Message);
  function CanFocus: Boolean;
  function ContainsControl(Control: TControl): Boolean;
  function ControlAtPos(const Pos: 
      TPoint; AllowDisabled: Boolean): TControl;
  procedure DisableAlign;
  procedure EnableAlign;
  function Focused: Boolean;
  procedure GetTabOrderList(List: TList); dynamic;
  function HandleAllocated: Boolean;
  procedure HandleNeeded;
  procedure InsertControl(AControl: TControl);
  procedure Invalidate; override;
  procedure PaintTo(DC: HDC; X, Y: Integer);
  procedure RemoveControl(AControl: TControl);
  procedure Realign;
  procedure Repaint; override;
  procedure ScaleBy(M, D: Integer);
  procedure ScrollBy(DeltaX, DeltaY: Integer);
  procedure SetBounds(ALeft, ATop, 
      AWidth, AHeight: Integer); override;
  procedure SetFocus; virtual;
  procedure Update; override;

Broadcast is used to send a message to all child windows of the TWinControl.

CanFocus returns a boolean value that determines whether or not the TWinControl can get focus. A control cannot get focus is when it's Visible property is set to false, for example.

ContainsControl determines whether or not a given control is contained within the TWinControl. This is not the same as determining whether or not the control is a child to the TWinControl but rather is contained by the TWinControl. For example, a TWinControl may be a parent to another TWinControl. This child control may itself be a parent to other controls. These other controls are still contained by the outer TWinControl.

It is possible to search for a child control at a given position relative to the parent control by using the ControlAtPos function. This function takes the client coordinates of the parent control as a parameter. If a child control appears at these coordinates a reference to that control is returned, otherwise, nil is returned. The AllowDisabled parameter determines whether or not disabled controls can be returned as well.

The DisableAlign and EnableAlign methods are used to temporarily disable and enable the alignment of controls within a TWinControl. Usually, this happens when performing scaling operations or reading a form file.

The Focused function returns true if the TWinControl as focus and is therefore the ActiveControl of the form on which it sits.

HandleAllocated returns true if the control has a valid handle. Whenever you access a TWinControl's Handle property directly, a new handle is created automatically if one was not yet created. Therefore, you would use HandleAllocated to query if a control has a handle without causing one to be created. HandleNeeded is the procedure that creates a windows handle for the TWinControl if it is needed.

The InsertControl procedure adds the control passed as a parameter to the TWinControl's Controls array. This makes the added control a child to the TWinControl.

RemoveControl removes the control from the TWinControl's Controls array. Normally, if you are adding a control as a child to another control at run-time, you would just make the assignment of the controls Parent property to specify its parent rather than calling InsertControl.

The Invalidate and Repaint procedures cause the control to repaint itself. Repaint causes Windows to process any paint messages as well by calling the Update method which in turn calls the Win32 API function, UpdateWindow. PaintTo can be used to paint the contents of a TWinControl to the device context of another control. The ReAlign method forces the TWinControl to realign the controls within it. ScaleBy is used to scale the TWinControl to a percentage of its original size. The ScrollBy method may be used if you do not want to use the default scrolling logic of a TWinControl. Normally, you'll have no need to use this method.

The SetBounds method sets the Left, Top, Width, and Height properties of the TWinControl. Using SetBounds is more efficient then setting these properties individually since setting them individually causes the control to repaint itself for each property assignment.

SetFocus makes the TWinControl the ActiveControl.

Some protected methods of interest to component designers are: CreateParams, CreateWnd, CreateWindowHandle, DestroyWnd, DestroyWindowHandle and RecreateWnd. These methods have to do with the creation and destruction of the window handle and window which the TWinControl encapsulates.

The CreateWnd procedure creates the window control encapsulated by the TWinControl by first calling CreateParams and the CreateWindowHandle.

CreateParams initializes any windows creation parameters. CreateParams may be overriden to change any of the default window creation parameters.

CreateWindowHandle creates the window handle by calling the Win32 API function CreateWindowEx. CreateWindowHandle is passed the window creation parameters set up in CreateParams.

DestroyWnd destroys the encapsulated windows control by calling DestroyWindowHandle, which in turn calls the Win32 API function DestroyWindow. DestroyWnd performs some additional logic to save the windows text and to free any device contexts associated with the windowed control.

TWinControl has events for keyboard interaction and focus change. These events are: OnKeyDown, OnKeyPress, OnKeyUp, OnEnter and OnExit. All of these events are documented in Delphi's online help.

TGraphicControls differ from TWinControls in that they do not have a window handle and cannot receive input focus. Also, they cannot be parents to other controls

TGraphicControls are used when you want to display something on the form, without the functionality(ies) of a regular windowed controls. Two key advantages to this strategy are:

  • TGraphicControls don't use up system resources since they don't require a window handle.
  • They are a bit faster at drawing then their TWinControl counterparts since their painting is not subject to the Windows message dispatching system. Instead, they piggyback on their parent's paint process.

TGraphicControls can respond to mouse events and therefore have mouse event handlers.

Since TGraphicControl allows you to paint the itself, it contains a TCanvas property, Canvas, and a Paint method which TGraphicControl descendants must override.

The standard controls which descend from TWinControl like TEdit and TListbox already have default drawing capabilities, provided by the encapsulated Windows control. What if you want to create a windowed component that provides its own custom painting? This is the purpose of TCustomControl.

TCustomControl is a descendant of TWinControl and is therefore a windowed control. This means that TCustomControl can also get input focus. Component writers create components that can descend from TCustomControl. Like the TGraphicControl, TCustomControl surfaces its Canvas property, which allows for custom drawing to its canvas. In fact, TCustomControl's provides a virtual Paint method which you override to perform your custom drawing.

Whether you plan use the Visual Component Library as an Applications Developer, or expand the existing library as a Component Writer, your understanding of the VCL will only make it easier for you to successfully accomplish either task. The VCL may seem very complex at first. However, by having a basic understanding of the its hierarchy and by knowing the role played by key objects within the VCL, you will be able to effectively maximize your use of the VCL to create better applications and more powerful components.

Server Response from: ETNASC03