Delphi 2 - Delphi 2.0's VCL Architecture

By: Xavier Pacheco

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

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

Component Types, structure, and VCL hierarchy

This paper discusses Delphi 2.0's Visual Component Library (VCL)-the framework from which all Delphi applications are built. It discusses the VCL hierarchy and explains the purpose of the key levels within the hierarchy. It also discusses the purposes of the common properties, methods and events that appear at the different component levels in the VCL hierarchy.

This paper does not discuss individual components and classes that make up the VCL. Its goal is to further the reader's understanding of how various components and classes are built.

This paper assumes that you have a general working knowledge of Delphi 2.0's component usage.

The Visual Component Library, introduced in Delphi 1.0, is a framework consisting of classes and components that you use to create Delphi applications. This VCL is designed so that you can manipulate these classes within Delphi's visual environment, at design-time, while you create your application. This differs from many other development environments, where the behavioral and visual characteristics of your applications are handled at run-time. In Delphi, you modify the behavioral and visual characteristics of your components as you develop your application visually-although you can modify component behavior and appearance at run-time as well.

You should have a good working knowledge of the VCL. The depth of knowledge that you require depends on how you intend on using the VCL. Therefore, you should realize that there are two types of Delphi developers: Applications Developers and Component Writers.

The VCL to Applications Developers
Applications Developers create complete applications by interacting with the Delphi visual environment (as mentioned earlier, this is a concept nonexistent in many other frameworks). These people use the VCL to create their user-interface and the other elements of their application: database connectivity, data validation, business rules, etc..

Applications Developers should know which properties, events, and methods each component makes available. Additionally, by understanding the VCL architecture, Applications Developers will be able to easily identify where they can improve their applications by extending components or creating new ones. Then they can maximize the capabilities of these components, and create better applications.

The VCL to Component Writers
Component Writers expand on the existing VCL, either by developing new components, or by increasing the functionality of existing ones. Many component writers make their components available for Applications Developers to use.

A Component Writer must take their knowledge of the VCL a step further than that of the Application Developer. For example, they must know whether to write a new component or to extend an existing one when the need for a certain characteristic arises. This requires a greater knowledge of the VCL's inner workings.

The VCL is made up of components
Components are the building blocks that developers use to design the user-interface and to provide some non-visual capabilities to their applications. To an Application Developer, a component is an object most commonly dragged from the Component palette and placed onto a form. Once on the form, one can manipulate the component's properties and add code to the component's various events to give the component a specific behavior. To a Component Writer, components are objects in Object Pascal code. Some components encapsulate the behavior of elements provided by the system, such as the standard Windows 95 controls. Other objects introduce entirely new visual or non-visual elements, in which case the component's code makes up the entire behavior of the component.

The complexity of different components varies widely. Some might be simple while others might encapsulate a elaborate task. There is no limit to what a component can do or be made up of. You can have a very simple component like a TLabel, or a much more complex component which encapsulates the complete functionality of a spreadsheet.

Component Types, structure, and VCL hierarchy
Components are really just special types of objects. In fact, a component's structure is based on the rules that apply to Object Pascal. There are three fundamental keys to understanding the VCL.

  • First, you should know the special characteristics of the four basic component types: standard controls, custom controls, graphical controls and non-visual components.
  • Second, you must understand the VCL structure with which components are built. This really ties into your understanding of Object Pascal's implementation.
  • Third, you should be familiar with the VCL hierarchy and you should also know where the four component types previously mentioned fit into the VCL hierarchy. The following paragraphs will discuss each of these keys to understanding the VCL.

Component Types
As a component writer, there four primary types of components that you will work with in Delphi: standard controls, custom controls, graphical controls, and non-visual components. Although these component types are primarily of interest to component writers, it's not a bad idea for applications developers to be familiar with them. They are the foundations on which applications are built.

Standard Components
Some of the components provided by Delphi 2.0 encapsulate the behavior of the standard Windows controls: TButton, TListbox and Tedit, for example. You will find these components on the Standard page of the Component Palette. These components are Windows' common controls with Object Pascal wrappers around them.

Each standard component looks and works like the Windows' common control which it encapsulates. The VCL wrapper's simply makes the control available to you in the form of a Delphi component-it doesn't define the common control's appearance or functionality, but rather, surfaces the ability to modify a control's appearance/functionality in the form of methods and properties. If you have the VCL source code, you can examine how the VCL wraps these controls in the file STDCTRLS.PAS.

If you want to use these standard components unchanged, there is no need to understand how the VCL wraps them. If, however, you want to extend or change one of these components, then you must understand how the Window's common control is wrapped by the VCL into a Delphi component.

For example, the Windows class LISTBOX can display the list box items in multiple columns. This capability, however, isn't surfaced by Delphi's TListBox component (which encapsulates the Windows LISTBOX class). (TListBox only displays items in a single column.) Surfacing this capability requires that you override the default creation of the TListBox component.

This example also serves to illustrate why it is important for Applications Developers to understand the VCL. Just knowing this tidbit of information helps you to identify where enhancements to the existing library of components can help make your life easier and more productive.

Custom components
Unlike standard components, custom components are controls that don't already have a method for displaying themselves, nor do they have a defined behavior. The Component Writer must provide to code that tells the component how to draw itself and determines how the component behaves when the user interacts with it. Examples of existing custom components are the TPanel and TStringGrid components.

It should be mentioned here that both standard and custom components are windowed controls. A "windowed control" has a window associated with it and, therefore, has a window handle. Windowed controls have three characteristics: they can receive the input focus, they use system resources, and they can be parents to other controls. (Parents is related to containership, discussed later in this paper.) An example of a component which can be a container is the TPanel component.

Graphical components
Graphical components are visual controls which cannot receive the input focus from the user. They are non-windowed controls. Graphical components allow you to display something to the user without using up any system resources; they have less "overhead" than standard or custom components. Graphical components don't require a window handle-thus, they cannot can't get focus. Some examples of graphical components are the TLabel and TShape components.

Graphical components cannot be containers of other components. This means that they cannot own other components which are placed on top of them.

Non-visual components
Non-visual components are components that do not appear on the form as controls at run-time. These components allow you to encapsulate some functionality of an entity within an object. You can manipulate how the component will behave, at design-time, through the Object Inspector. Using the Object Inspector, you can modify a non-visual component's properties and provide event handlers for its events. Examples of such components are the TOpenDialog, TTable, and TTimer components.

Structure of a component
All components share a similar structure. Each component consists of common elements that allow developers to manipulate its appearance and function via properties, methods and events. The following sections in this paper will discuss these common elements as well as talk about a few other characteristics of components which don't apply to all components.

Component properties
Properties provide an extension of an object's fields. Unlike fields, properties do not store data: they provide other capabilities. For example, properties may use methods to read or write data to an object field to which the user has no access. This adds a certain level of protection as to how a given field is assigned data. Properties also cause "side effects" to occur when the user makes a particular assignment to the property. Thus what appears as a simple field assignment to the component user could trigger a complex operation to occur behind the scenes.

Properties provide access to internal storage fields
There are two ways that properties provide access to internal storage fields of components: directly or through access methods. Examine the code below which illustrates this process.

TCustomEdit = class(TWinControl)
  FMaxLength: Integer;
  procedure SetMaxLength(Value: Integer);
  property MaxLength: Integer read 
      FMaxLength write SetMaxLength default 0;

The code above is snippet of the TCustomEdit component class. TCustomEdit is the base class for edit boxes and memo components such as TEdit, and TMemo.

TCustomEdit has an internal field FMaxLength of type Integer which specifies the maximum length of characters which the user can enter into the control. The user doesn't directly access the FMaxLength field to specify this value. Instead, a value is added to this field by making an assignment to the MaxLength property.

The property MaxLength provides the access to the storage field FMaxLength. The property definition is comprised of the property name, the property type, a read declaration, a write declaration and optional default value.

The read declaration specifies how the property is used to read the value of an internal storage field. For instance, the MaxLength property has direct read access to FMaxLength. The write declaration for MaxLength shows that assignments made to the MaxLength property result in a call to an access method which is responsible for assigning a value to the FMaxLength storage field. This access method is SetMaxLength.

Property-access methods
Access methods take a single parameter of the same type as the property. One of the primary reasons for write access methods is to cause some side-effect to occur as a result of an assignment to a property. Write access methods also provide a method layer over assignments made to a component's fields. Instead of the component user making the assignment to the field directly, the property's write access method will assign the value to the storage field if the property refers to a particular storage field. For example, examine the implementation of the SetMaxLength method below.

procedure TCustomEdit.SetMaxLength(Value: Integer);
  if FMaxLength <> Value then
    FMaxLength := Value;
    if HandleAllocated then 
         SendMessage(Handle, EM_LIMITTEXT, Value, 0);

The code in the SetMaxLength method checks if the user is assigning the same value as that which the property already holds. This is done as a simple optimization. The method then assigns the new value to the internal storage field, FMaxLength. Additionally, the method then sends an EM_LIMITTEXT Windows message to the window which the TCustomEdit encapsulates. The EM_LIMITTEXT message places a limit on the amount of text that a user can enter into an edit control. This last step is what is referred to as a side-effect when assigning property values. Side effects are any additional actions that occur when assigning a value to a property and can be quite sophisticated.

Providing access to internal storage fields through property access methods offers the advantage that the Component Writer can modify the implementation of a class without modifying the interface. It is also possible to have access methods for the read access of a property. The read access method might, for example, return a type which is different that that of a properties storage field. For instance, it could return the string representation of an integer storage field.

Another fundamental reason for properties is that properties are accessible for modification at run-time through Delphi's Object Inspector. This occurs whenever the declaration of the property appears in the published section of a component's declaration.

Types of properties
Properties can be of the standard data types defined by the Object Pascal rules. Property types also determine how they are edited in Delphi's Object Inspector. The table below shows the different property types as they are defined in Delphi's online help.

Property typeObject Inspector treatment
SimpleNumeric, character, and string properties appear in the Object Inspector as numbers, characters, and strings, respectively. The user can type and edit the value of the property directly.
EnumeratedProperties of enumerated types (including Boolean) display the value as defined in the source code. The user can cycle through the possible values by double-clicking the value column. There is also a drop-down list that shows all possible values of the enumerated type.
SetProperties of set types appear in the Object Inspector looking like a set. By expanding the set, the user can treat each element of the set as a Boolean value: True if the element is included in the set or False if it's not included.
ObjectProperties that are themselves objects often have their own property editors. However, if the object that is a property also has published properties, the Object Inspector allows the user to expand the list of object properties and edit them individually. Object properties must descend from TPersistent.
ArrayArray properties must have their own property editors. The Object Inspector has no built-in support for editing array properties.

For more information on properties, refer to the "Component Writers Guide" which ships with Delphi.

Since components are really just objects, they can have methods. We will discuss some of the more commonly used methods later in this paper when we discuss the different levels of the VCL hierarchy.

Events provide a means for a component to notify the user of some pre-defined occurrence within the component. Such an occurrence might be a button click or the pressing of a key on a keyboard.

Components contain special properties called events to which the component user assigns code. This code will be executed whenever a certain event occurs. For instance, if you look at the events page of a TEdit component, you'll see such events as OnChange, OnClick and OnDblClick. These events are nothing more than pointers to methods.

When the user of a component assigns code to one of those events, the user's code is referred to as an event handler. For example, by double clicking on the events page for a particular event causes Delphi to generate a method and places you in the Code Editor where you can add your code for that method. An example of this is shown in the code below, which is an OnClick event for a TButton component.

TButton component.
TForm1 = class(TForm)
  Button1: Tbutton;
  procedure Button1Click(Sender: TObject);
procedure TForm1.Button1Click(Sender: TObject);
  { Event code goes here }

It becomes clearer that events are method pointers when you assign an event handler to an event programmatically. The above example was Delphi generated code. To link your own an event handler to a TButton's OnClick event at run time you must first create a method that you will assign to this event. Since this is a method, it must belong to an existing object. This object can be the form which owns the TButton component although it doesn't have to be. In fact, the event handlers which Delphi creates belong to the form on which the component resides. The code below illustrates how you would create an event handler method.

TForm1 = class(TForm)
  Button1: TButton;
  MyOnClickEvent(Sender: TObject); // 
  Your method declaration
{ Your method definition below }
procedure TForm1.MyOnClickEvent(Sender: TObject);
  { Your code goes here }

The MyOnClickEvent method becomes the event handler for Button1.OnClick when it is assigned to Button1.OnClick in code as shown below.

Button1.OnClick := MyOnClickEvent

This assignment can be made anytime at runtime, such as in the form's OnCreate event handler. This is essentially the same thing that happens when you create an event handler through Delphi's Object Inspector except that Delphi generates the method declaration.

When you define methods for event handlers, these methods must be defined as the same type as the event property and the field to which the event property refers. For instance, the OnClick event refers to an internal data field, FOnClick. Both the property OnClick, and field FOnClick are of the type TNotifyEvent. TNotifyEvent is a procedural type as shown below:

TNotifyEvent = procedure (Sender: TObject) of object; 

Therefore, if you are creating a method for an OnClick event, it must be defined with the same type and number of parameters as shown below.

TForm1 = class(TForm)
  procedure (Sender: TObject);

Note the use of the of object specification. This tells the compiler that the procedure definition is actually a method and performs some additional logic like ensuring that an implicit Self parameter is also passed to this method when called. Self is just a pointer reference to the class to which a method belongs.

Some components in the VCL can own other components as well as be parents to other components. These two concepts have a different meaning as will be discussed in the section to follow.

All components may be owned by other components but not all components can own other components. A component's Owner property contains a reference to the component which owns it.

The basic responsibility of the owner is one of resource management. The owner is responsible for freeing those components which it owns whenever it is destroyed. Typically, the form owns all components which appear on it, even if those components are placed on another component such as a TPanel. At design-time, the form automatically becomes the owner for components which you place on it. At run-time, when you create a component, you pass the owner as a parameter to the component's constructor. For instance, the code below shows how to create a TButton component at run-time and passes the form's implicit Self variable to the TButton's Create constructor. TButton.Create will then assign whatever is passed to it, in this case Self or rather the form, and assign it to the button's Owner property.

MyButton := TButton.Create(self);

When the form that now owns this TButton component gets freed, MyButton will also be freed.

You can create a component without an owner by passing nil to the component's Create constructor, however, you must ensure that the component is freed when it is no longer needed. The code below shows you how to do this for a TTable component.

MyTable := TTable.Create(nil)
  { Do stuff with MyTable }

As shown in the code above, it is best to use a try..finally block to ensure that the component gets freed even if an exception were to be raised.

The Components property of a component is an array property which contains a list of the components which it owns. For instance, the code below shows how to loop through a form's components and then shows their class name.

  I: integer;
  for I := 0 to ComponentCount - 1 do 

Parenthood is a much different concept from ownership. It applies only to windowed components, which can be parents to other components. Later, when we discuss the VCL hierarchy, you will see the level in the hierarchy which introduces windowed controls.

Parent components are responsible for the display of other components. They call the appropriate methods internally that cause the children components to draw themselves. The Parent property of a component refers to the component which is its parent. Also, a component's parent does not have to be it's owner. Although the parent component is mainly responsible for the display of components, it also frees children components when it is destroyed.

Windowed components are controls which are visible user interface elements such as edit controls, list boxes and memo controls. In order for a windowed component to be displayed, it must be assigned a parent on which to display itself. This task is done automatically by Delphi's design-time environment when you drop a component from the Component Palette onto your form. When creating a component at run-time, however, you must explicitly make this assignment, otherwise the component will not be displayed. An example of creating a TEdit component at run-time is shown below:

constructor TForm.CreateForm(Sender: TObject);
  MyEdit: TEdit;
  MyEdit := TEdit.Create(self); // Pass Self as the owner
  MyEdit.Parent := Self;        // Pass self as the parent

Another characteristic of a component is that it can be streamed. Streaming is a way to store a component and information regarding its properties' values to a file or to an area in memory. For example, the .DFM file created by Delphi, is a resource file with information about a form and the components residing on the form. This information was streamed to that resource file.

Component Writers must understand the streaming mechanism of the VCL if they intend for their components to stream special data, which is not done automatically by the VCL.

Server Response from: ETNASC04