The C++Builder Visual Component Library
Written by Xavier Pacheco. Revised by Xavier Pacheco and John Huang.
Introduction
This paper discusses C++Builder's Visual Component Library (VCL)-the framework from which all C++Builder applications are built. It discusses the VCL hierarchy and explains the purpose of the key levels within the hierarchy. It 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 the C++ language and C++Builder's component usage.
The History of VCL
The Visual Component Library, introduced in Delphi 1.0, is a framework consisting of classes and components that you use to create Delphi applications. VCL was updated to 32 bit with Delphi 2. Delphi has been very successful in the RAD market, however, many users have expressed the interest in a C++ RAD tool, which eventually was born as C++Builder. C++Builder uses an unmodified version of Delphi 2 Visual Component Library. This paper concentrates on the usage of VCL in C++Builder, although the concepts also apply to Delphi users.
This VCL is designed so that you can manipulate these classes within C++Builder'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 application are handled at run-time. In C++Builder, 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 24424f57y there are two types of C++Builder developers: Applications Developers and Component Writers.
The VCL to Applications Developers
Applications developers create complete applications by interacting with the C++Builder 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 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.
Because C++Builder and Delphi 2.0 are built upon the same Borland Visual Component Library, third party components written in Delphi will work in C++Builder as well. Application developers should be aware of the mass third party components market and look for proper third party tools when new functionality is needed.
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.
A component writer can choose the tool he prefers, C++Builder or Delphi, to create new components. He also needs to ensure that the components works in both environments.
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 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 behavior. To a Component Writer, components are objects in C++ or Object Pascal code. Some components encapsulate the behavior of elements provided by the system, as the standard Windows95 controls. Other objects introduce entirely new visual 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 an 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 on the rules that apply to C++ with few exceptions. 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 C++'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 are four primary types of components that you will work with in C++Builder: 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 C++Builder encapsulate the behavior of the standard Windows controls: TButton, TListbox and TEdit, for example. You will find these components on the Standard page and Win95 page of the Component Palette. These components are Windows' common controls with VCL wrappers around them.
Each standard component looks and works like the Windows' common control which it encapsulates. The VCL wrappers simply make the control available to you in the form of a C++Builder 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 component.
For example, the Windows class LISTBOX can display the list box items in multiple columns. This capability, however, isn't surfaced by C++Builder'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. This will be discussed in detail later in the paper in TWinControl section. 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
Traditionally, a class definition of a C++ class contains data members which holds data of interest to the object, and methods which define the behavior of the object. This concept applies to other Object-oriented languages too, including Smalltalk and Java. Subclassing is used to add new behaviors.
In VCL, the same concept applies. Each object contains data members and methods, and the programmer can still use methods to manipulate the object. However, VCL takes this one step further - it uses the concept of component, properties, and events. Each component consists of common elements that allow developers to manipulate its appearance and function via properties and events, in addition to methods. 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 data members. Unlike data members, properties do not store data: they provide other capabilities. For example, properties may use methods to read or write data to an object data member to which the user has no access. This adds a certain level of protection as to how a given data member 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 data member assignment to the component user could trigger a complex operation to occur behind the scenes.
C++Builder keyword: __property
C++Builder uses the keyword "__property" to identify properties. The syntax is as follows:
__property type_identifier identifier = property_specifiers;
property_specifiers contains read specifier, write specifier, stored specifier, and default specifier. The next section of this paper illustrates the use of the __property keyword.
Properties provide access to data members
There are two ways that properties provide access to internal data members of components: directly or through getter/setter functions. Examine the code below which illustrates this process.
class __declspec(delphiclass) TCustomEdit;
class __declspec(pascalimplementation) TCustomEdit : public Controls::TWinControl
;
.
};
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 data member 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 data member to specify this value. Instead, a value is added to this data member by making an assignment to the MaxLength property.
The property MaxLength provides the access to the data member 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 a data member. For instance, the MaxLength property has direct read access to FMaxLength. The write declaration for MaxLength shows that assignments made to the MaxLength property results in a call to a setter function which is responsible for assigning a value to the FMaxLength data member. This setter function is SetMaxLength.
Getter and setter functions
Setter functions, take a single parameter of the same type as the property. One of the primary reasons for setter functions is to cause some side-effect to occur as a result of an assignment to a property. Setter functions also provide a function layer over assignments made to a component's data members. Instead of the component user making the assignment to the data member directly, the property's setter function will assign the value to the data member if the property refers to a particular data member.
For example, examine the implementation of the SetMaxLength function below. The original source code is written in Delphi, it has been translated into C++ for this sample.
void TCustomEdit::SetMaxLength(int Value)
}
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 function then assigns the new value to the data member, 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.
It is also possible to have getter functions for the read access of a property. The getter function might return a type which is different from that of a property's data member. For instance, it could return the string representation of a data member. For example, TField component has AsString, AsFloat, AsInteger properties, all of them are pointing to the same internal storage. Providing access to data members through setter/getter functions offers the advantage that the Component Writer can modify the implementation of a class without modifying the interface.
Another fundamental reason for properties is that properties are accessible for modification at run-time through C++Builder's Object Inspector. This occurs whenever the declaration of the property appears in the published section of a component's declaration.
C++Builder keyword: __published
The properties comply with the same visibility rules as normal data member and methods. The rules for private, protected, and public keywords still apply. C++Builder's keyword: __published supports visibility rules that are identical to those of public members. The only difference is that Delphi-style run-time type information(RTTI) is generated for published data members and properties, and C++Builder Object Inspector uses RTTI to display them.
In the example above, the MaxLength property is defined in the protected section and thus can only be accessed by TCustomEdit's subclasses. TEdit re-declare MaxLength in __published to enable access from outside of the classes, and enable the property to be modified in the Object Inspector window.
Types of properties
Properties can be of the standard data types defined by the C++ rules. Property types also determine how they are edited in C++Builder's Object Inspector. The table below shows the different property types as they are defined in C++Builder's online help.
Property type |
Object Inspector treatment |
Simple |
Numeric, 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. |
Enumerated |
Properties of enumerated types (including bool) 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. |
Set |
Properties 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. |
Object |
Properties 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. |
Array |
Array 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 C++Builder.
Set
There are some data types which are built-in in Delphi that are not available in C++. C++Builder declared several template classes to handle these situations. One data type extensively used by Delphi is Set. Examples are the declaration of TFontStyle and TFontStyles below.
enum TFontStyle ;
typedef Set<TFontStyle, fsBold, fsStrikeOut> TFontStyles;
TFontStyle is an enumerated type. TFontStyles is defined as a set, unordered collection of TFontStyle. In the Object Inspector, It is show as an array of booleans which you can turn on or off.
Member functions
Since components are really just objects, they can have methods. We will discuss some of the more commonly used functions later in this paper when we discuss the different levels of the VCL hierarchy.
Events
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. They can be accessed from the event page of the Object Inspector window.
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.
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 C++Builder 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.
Unit1.hpp:
class TForm1 : public TForm
;
Unit1.cpp
void __fastcall TForm1::Button1Click(TObject *Sender)
It becomes clearer that events are function pointers when you assign an event handler to an event programmatically. The above example lists C++Builder generated code. To link your own 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 C++Builder creates belong to the form on which the component resides. The code below illustrates how you would create an event handler function.
// Unit1.hpp:
class TForm1 : public TForm
// Unit1.cpp
void __fastcall TForm1::Button1Click(TObject *Sender)
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 C++Builder's Object Inspector except that C++Builder generates the function declaration, and saves the relationship between the event and event handler into the .dfm file. The .dfm file is compiled into the target application's resource. When an application built with C++Builder starts up, VCL reads the form from the resource and assign properties and events of the components on the form.
When you define methods for event handlers, these methods must be defined as the same type as the event property and the data member to which the event property refers. For instance, the OnClick event refers to an internal data member, FOnClick. Both the property OnClick, and data member FOnClick are of the type TNotifyEvent. TNotifyEvent is a function type as shown below:
typedef void __fastcall (__closure *TNotifyEvent)(System::TObject *Sender);
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.
class TForm1 : public TForm
Events and Windows messages
For experienced Windows C programmers, C++Builder events are similar to the window messages. The following is a brief list of events for TForm and the Windows messages you would use in a regular C program using Windows API.
Windows messages |
VCL event |
WM_CREATE |
OnCreate |
WM_DESTROY |
OnClose |
WM_SIZE |
OnReSize |
WM_ACTIVATE |
OnActivate, OnDeactivate |
WM_SHOWWINDOW |
OnShow, OnHide |
WM_KEYDOWN |
OnKeyDown |
WM_KEYUP |
OnKeyUp |
WM_KEYDOWN |
OnKeyDown |
WM_LBUTTONDOWN, WM_RBUTTONDOWN |
OnMouseDown |
WM_LBUTTONUP, WM_RBUTTONUP |
OnMouseUp |
WM_MOUSEMOVE |
OnMouseMove |
WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK |
OnDblClick |
WM_PAINT |
OnPaint |
Not all Windows messages have a corresponding VCL event. For example, in a Windows API C program, WM_COMMAND is used to handle button clicks and menu selections. In C++Builder, this is handled in TButton::OnClick and TMenuItem::OnClick events. Another example is the TTime component, whose OnTimer event replaces the functionality of WM_TIMER Windows message.
In addition. There are VCL events which extend the built-in functionality of Windows. In the Object Inspector, you can see TForm has OnDragOver and OnDragDrop events. They make adding drag-and-drop abilities to your VCL programs simple and direct. Most of the components on "Standard" and "Win95" are VCL wrappers around windows controls. The components on other pages are new components for different functional areas and you will find completely new events for them.
Extend VCL with Windows messages
The VCL components on the Standard page and the Win95 page encapsulate Windows controls. The interaction between the user and the computer, which used to be handled by responding to Windows messages, now reside in event handlers for VCL components. However, there are cases one needs to trap Windows messages, because VCL is not handling it, or the handling is not satisfactory. In this case, VCL provides a syntax similar to OWL or MFC to enable responding to Windows messages directly.
First, a method which takes a pointer to a TMessage as the parameter has to be defined. This is the function being called when the visual component receives the Windows message. TMessage is declared in vcl\messages.hpp. There are other structures which slice TMessage different ways to give the fields different meanings.
Second, in the class declaration of the component which responds to the message, macros BEGIN_MESSAGE_MAP, MESSAGE_HANDLER, and END_MESSAGE_MAP are used. The following is an example of the usage. It triggers a function when the form receives WM_USER + 2000.
// in the .hpp file
class TForm1 : public TForm
// in the .cpp file
MESSAGE void __fastcall TForm1::HandleWMUser(TMessage *Message)
Rarely does one need to use this technique. Many developers coming from traditional C/C++ background will tend to overuse this method because this is close to how OWL and MFC handle Windows messages. However, check the component to see if there is an existing event you can use instead first.
Containership
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.
Ownership
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. The ownership hierarchy for a C++Builder application looks something like this:
Notice that ownership applies not only to visual components, like forms and panels, but also non-visual components, like TTimer and TDataSource.
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 "this" variable to the TButton's constructor. TButton will then assign whatever is passed to it, in this case "this" or rather the form, and assign it to the button's Owner property.
MyButton = new TButton(this);
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 0 to the component's 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.
TTable * MyTable = new TTable (0)
/* Do stuff with MyTable */
delete MyTable;
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.
void __fastcall TForm1::Button1Click(TObject *Sender)
}
Parenthood
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 functions 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. The visual hierarchy of a form may look like the following chart. Notice that only visual components are contained in the hierarchy.
Many properties of visual components, such as Left, Width, Top, Height are relative to the parent control. Other properties, such as ParentColor and ParentFont, enable child controls to use properties of parents.
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 C++Builder'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:
void __fastcall TForm1::FormCreate(TObject *Sender)
Streamability
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 C++Builder, 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.
VCL class hierarchy overview
The figure below illustrates a sub-set of C++Builder 2.0's VCL class 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.
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
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.HPP and is defined as:
class __declspec(delphiclass) TObject
__fastcall Free();
TClass __fastcall ClassType();
void __fastcall CleanupInstance();
void * __fastcall FieldAddress(const ShortString &Name);
static TObject * __fastcall InitInstance(TClass cls, void *instance);
static ShortString __fastcall ClassName(TClass cls);
static bool __fastcall ClassNameIs(TClass cls, const AnsiString string);
static TClass __fastcall ClassParent(TClass cls);
static void * __fastcall ClassInfo(TClass cls);
static long __fastcall InstanceSize(TClass cls);
static bool __fastcall InheritsFrom(TClass cls, TClass aClass);
static void * __fastcall MethodAddress(TClass cls, const ShortString &Name);
static ShortString __fastcall MethodName(TClass cls, void *Address);
ShortString __fastcall ClassName()
bool __fastcall ClassNameIs(const AnsiString string)
TClass __fastcall ClassParent()
void * __fastcall ClassInfo()
long __fastcall InstanceSize()
bool __fastcall InheritsFrom(TClass aClass)
void * __fastcall MethodAddress(const ShortString &Name)
ShortString __fastcall MethodName(void *Address)
virtual void __fastcall Dispatch(void *Message);
virtual void __fastcall DefaultHandler(void* Message);
private:
virtual TObject* __fastcall NewInstance(TClass cls);
public:
virtual void __fastcall FreeInstance();
virtual __fastcall ~TObject()
};
These methods are documented in C++Builder's online help.
Note that some methods that are preceded by the keyword static. These functions 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 functions.
All components must descend from TComponent or from a TComponent descendant. TComponent, being a descendant of TObject, inherits TObjects data members, methods 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 constructor and destructor allocate and de-allocate memory for the object's instance respectively. The TObject constructor returns a pointer to the object being created.
TPersistent
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 functions.
Member function |
Purpose |
Assign |
This public method allows a component to assign the data associated with another component to itself. |
AssignTo |
This protected method allows a component to override the implementation of the Assign method. TPersistent itself, raises an exception when this function is called. It is up to TPersistent descendant to override this function to define its implementation. The TClipboard class is an object that does this, for example. |
DefineProperties |
This protected method that allows component writers to define how the component stores extra or unpublished properties. By default, a component automatically stores published properties. |
TComponent
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 Name |
Purpose |
Owner |
Refers to the component's owner. |
ComponentCount |
Number of components owned. |
ComponentIndex |
The position of this component in its owners list of components. The first component in this list has the value of zero. |
Components |
A property array containing a list of components that are owned by this component. |
ComponentState |
The current state of a component. Look up TComponentState in C++Builder's online help for additional information on this property. |
ComponentStyle |
A style that dictates the behavior of a component. Look up TComponentStyle in C++Builder's online help for additional information on this property. |
Name |
The component's name. |
Tag |
An 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. |
DesignInfo |
Internally 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:
class __declspec(pascalimplementation) TComponent : public TPersistent
;
The methods Destroying 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 functions directly.
The FindComponent function 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.
void __fastcall TForm1::Button1Click(TObject *Sender)
Here, we are issuing the FindComponent of the main form; therefore this code will work as long as it resides in a function 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 *)(FindComonent("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 function returns a boolean value indicating whether or not the component has been assigned a parent. You would use this function before referring to a component's parent. Note, that this function does not indicate whether or not a component has an owner.
The InsertComponent function adds the component passed as a parameter as an owned component and RemoveComponent removes a component from the list of owned components.
TControl
TControls defines data members, 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 C++Builder controls are descendants of either TWinControl or TGraphicControl both of which are discussed next.
TWinControl
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, functions 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.
Property |
Purpose |
Brush |
Color and pattern the canvas uses for filling graphical shapes and backgrounds. |
Controls |
Maintains a list of controls to which the said TWinControl is parent. |
ControlCount |
The number of children controls. |
Ctl3d |
Specifies whether or not to draw the control using a three-dimensional appearance. |
Handle |
Refers 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. |
HelpContext |
A 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. |
Showing |
Specifies if the control is visible or not. |
TabStop |
Specifies if the user can tab to the control. |
TabOrder |
Specifies 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:
class __declspec(pascalimplementation) TWinControl : public TControl
;
Broadcast is used to send a message to all child windows of the TWinControl.
CanFocus returns a bool 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, 0 is returned. The AllowDisabled parameter determines whether or not disabled controls can be returned as well.
The DisableAlign and EnableAlign functions 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 functions 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 functions 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 function.
The SetBounds function sets the Left, Top, Width, and Height properties of the TWinControl. Using SetBounds is more efficient than 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 functions of interest to component designers are: CreateParams, CreateWnd, CreateWindowHandle, DestroyWnd, DestroyWindowHandle and RecreateWnd. These member functios have to do with the creation and destruction of the window handle and window which the TWinControl encapsulates.
The CreateWnd functions 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 C++Builder's online help.
TGraphicControl
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.
TCustomControl
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 functions which you override to perform your custom drawing.
Conclusion
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.
Copyright © 1996. Borland International, Inc.
|