This chapter provides an overview of component design and the process of writing components for Delphi applications. The material here assumes that you are familiar with Delphi and its standard components.
For information on installing new components, see "Installing component packages".
Delphi's components are all part of a class hierarchy called the Visual Component Library (VCL). Figure 31.1 shows the relationship of selected classes that make up the VCL. For a more detailed discussion of class hierarchies and the inheritance relationships among classes, see "Object-oriented programming for component writers."
The TComponent class is the shared ancestor of every component in the VCL. TComponent provides the minimal properties and events necessary for a component to work in Delphi. The various branches of the library provide other, more specialized capabilities.
When you create a component, you add to the VCL by deriving a new class from one of the existing class types in the hierarchy.
Because components are classes, component writers work with objects at a different level from application developers. Creating new components requires that you derive new classes.
Briefly, there are two main differences between creating components and using them in applications. When creating components,
Because of these differences, you need to be aware of more conventions and think about how application developers will use the components you write.
A component can be almost any program element that you want to manipulate at design time. Creating a component means deriving a new class from an existing one. You can derive a new component from any existing component, but the following are the most common ways to create components:
Table 31.1 summarizes the different kinds of components and the classes you use as starting points for each.
Any existing component, such as TButton or TListBox, or an abstract component type, such as TCustomListBox | |
You can also derive classes that are not components and cannot be manipulated on a form. Delphi includes many such classes, like TRegIniFile and TFont.
The simplest way to create a component is to customize an existing one. You can derive a new component from any of the components provided with Delphi.
Some controls, such as list boxes and grids, come in several variations on a basic theme. In these cases, the VCL includes an abstract class (with the word "custom" in its name, such as TCustomGrid) from which to derive customized versions.
For example, you might want to create a special list box that does not have some of the properties of the standard TListBox class. You cannot remove (hide) a property inherited from an ancestor class, so you need to derive your component from something above TListBox in the hierarchy. Rather than force you to start from the abstract TWinControl class and reinvent all the list box functions, the VCL provides TCustomListBox, which implements the properties of a list box but does not publish all of them. When you derive a component from an abstract class like TCustomListBox, you publish only the properties you want to make available in your component and leave the rest protected.
"Creating properties," explains publishing inherited properties. "Modifying an existing component," and "Customizing a grid," show examples of modifying existing controls.
Windowed controls are objects that appear at runtime and that the user can interact with. Each windowed control has a window handle, accessed through its Handle property, that lets Windows identify and operate on the control. The handle allows the control to receive input focus and can be passed to Windows API functions.
All windowed controls descend from the TWinControl class. These include most standard Windows controls, such as pushbuttons, list boxes, and edit boxes. While you could derive an original control (one that's not related to any existing control) directly from TWinControl, Delphi provides the TCustomControl component for this purpose. TCustomControl is a specialized windowed control that makes it easier to draw complex visual images.
"Customizing a grid," presents an example of creating a windowed control.
If your control does not need to receive input focus, you can make it a graphic control. Graphic controls are similar to windowed controls, but have no window handles, and therefore consume fewer system resources. Components like TLabel, which never receive input focus, are graphic controls. Although these controls cannot receive focus, you can design them to react to mouse messages.
Delphi supports the creation of custom controls through the TGraphicControl component. TGraphicControl is an abstract class derived from TControl. Although you can derive controls directly from TControl, it is better to start from TGraphicControl, which provides a canvas to paint on and handles WM_PAINT messages; all you need to do is override the Paint method.
"Creating a graphic component," presents an example of creating a graphic control.
In traditional Windows programming, you create custom controls by defining a new window class and registering it with Windows. The window class (which is similar to the objects or classes in object-oriented programming) contains information shared among instances of the same sort of control; you can base a new window class on an existing class, which is called subclassing. You then put your control in a dynamic-link library (DLL), much like the standard Windows controls, and provide an interface to it.
Using Delphi, you can create a component "wrapper" around any existing window class. So if you already have a library of custom controls that you want to use in Delphi applications, you can create Delphi components that behave like your controls, and derive new controls from them just as you would with any other component.
For examples of the techniques used in subclassing Windows controls, see the components in the STDCTLS unit that represent standard Windows controls, such as TEdit.
Nonvisual components are used as interfaces for elements like databases (TDataSet) and system clocks (TTimer), and as placeholders for dialog boxes (TCommonDialog and its descendants). Most of the components you write are likely to be visual controls. Nonvisual components can be derived directly from TComponent, the abstract base class for all components.
To make your components reliable parts of the Delphi environment, you need to follow certain conventions in their design. This section discusses the following topics:
One quality that makes components usable is the absence of restrictions on what they can do at any point in their code. By their nature, components are incorporated into applications in varying combinations, orders, and contexts. You should design components that function in any situation, without preconditions.
An excellent example of removing dependencies is the Handle property of TWinControl. If you have written Windows applications before, you know that one of the most difficult and error-prone aspects of getting a program running is making sure that you do not try to access a window or control until you have created it by calling the CreateWindow API function. Delphi windowed controls relieve users from this concern by ensuring that a valid window handle is always available when needed. By using a property to represent the window handle, the control can check whether the window has been created; if the handle is not valid, the control creates a window and returns the handle. Thus, whenever an application's code accesses the Handle property, it is assured of getting a valid handle.
By removing background tasks like creating the window, Delphi components allow developers to focus on what they really want to do. Before passing a window handle to an API function, there is no need to verify that the handle exists or to create the window. The application developer can assume that things will work, instead of constantly checking for things that might go wrong.
Although it can take time to create components that are free of dependencies, it is generally time well spent. It not only spares application developers from repetition and drudgery, but it reduces your documentation and support burdens.
Aside from the visible image manipulated in the Form designer, the most obvious attributes of a component are its properties, events, and methods. Each of these has a chapter devoted to it in this book, but the discussion that follows explains some of the motivation for their use.
Properties give the application developer the illusion of setting or reading the value of a variable, while allowing the component writer to hide the underlying data structure or to implement special processing when the value is accessed.
There are several advantages to using properties:
"Creating properties," explains how to add properties to your components.
An event is a special property that invokes code in response to input or other activity at runtime. Events give the application developer a way to attach specific blocks of code to specific runtime occurrences, such as mouse actions and keystrokes. The code that executes when an event occurs is called an event handler.
Events allow application developers to specify responses to different kinds of input without defining new components.
"Creating events," explains how to implement standard events and how to define new ones.
Class methods are procedures and functions that operate on a class rather than on specific instances of the class. For example, every component's constructor method (Create) is a class method. Component methods are procedures and functions that operate on the component instances themselves. Application developers use methods to direct a component to perform a specific action or return a value not contained by any property.
Because they require execution of code, methods can be called only at runtime. Methods are useful for several reasons:
"Creating methods," explains how to add methods to your components.
Delphi simplifies Windows graphics by encapsulating various graphic tools into a canvas. The canvas represents the drawing surface of a window or control and contains other classes, such as a pen, a brush, and a font. A canvas is much like a Windows device context, but it takes care of all the bookkeeping for you.
If you have written a graphical Windows application, you are familiar with the requirements imposed by Windows' graphics device interface (GDI). For example, GDI limits the number of device contexts available and requires that you restore graphic objects to their initial state before destroying them.
With Delphi, you do not have to worry about these things. To draw on a form or other component, you access the component's Canvas property. If you want to customize a pen or brush, you set its color or style. When you finish, Delphi disposes of the resources. Delphi also caches resources to avoid recreating them if your application frequently uses the same kinds of resource.
You still have full access to the Windows GDI, but you will often find that your code is simpler and runs faster if you use the canvas built into Delphi components. Graphics features are detailed in "Using graphics in components."
Before you can install your components in the Delphi IDE, you have to register them. Registration tells Delphi where you want your component to appear on the Component palette. You can also customize the way Delphi stores your components in the form file. For information on registering a component, see "Making components available at design time."
You can create a new component two ways:
You can use either of these methods to create a minimally functional component ready to install on the Component palette. After installing, you can add your new component to a form and test it at both design time and runtime. You can then add more features to the component, update the Component palette, and continue testing.
There are several basic steps that you perform whenever you create a new component. These steps are described below; other examples in this document assume that you know how to perform them.
When you finish, the complete component includes these files:
Creating a help file to instruct component users on how to use the component is optional. Including a help file is mandatory only if one is created.
The chapters in the rest of Part IV explain all the aspects of building components and provide several complete examples of writing different kinds of components.
The Component wizard simplifies the initial stages of creating a component. When you use the Component wizard, you need to specify only these things:
The Component wizard performs the same tasks you would when creating a component manually:
The Component wizard cannot add components to an existing unit. You must add components to existing units manually.
To start the Component wizard, choose one of these two methods:
Fill in the fields in the Component wizard:
To place the component in a new or existing package, click Component|Install and use the dialog box that appears to specify a package.
Warning: If you derive a component from a VCL class whose name begins with "custom" (such as TCustomControl), do not try to place the new component on a form until you have overridden any abstract methods in the original component. Delphi cannot create instance objects of a class that has abstract properties or methods.
To see the source code for your unit, click View Unit. (If the Component wizard is already closed, open the unit file in the Code editor by selecting File|Open.) Delphi creates a new unit containing the class declaration and the Register procedure, and adds a uses clause that includes all the standard Delphi units. The unit looks like this:
unit MyControl;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
TMyControl = class(TCustomControl)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TMyControl]);
end;
end.
The easiest way to create a new component is to use the Component wizard. You can, however, perform the same steps manually.
To create a component manually, follow these steps:
A unit is a separately compiled module of Object Pascal code. Delphi uses units for several purposes. Every form has its own unit, and most components (or groups of related components) have their own units as well
When you create a component, you either create a new unit for the component or add the new component to an existing unit.
To create a unit, choose File|New to and double-click on Unit. Delphi creates a new unit file and opens it in the Code editor.
To open an existing unit, choose File|Open and select the source code unit that you want to add your component to.
Note: When adding a component to an existing unit, make sure that the unit contains only component code. For example, adding component code to a unit that contains a form causes errors in the Component palette.
Once you have either a new or existing unit for your component, you can derive the component class.
Every component is a class derived from TComponent, from one of its more specialized descendants (such as TControl or TGraphicControl), or from an existing component class. "How do you create components?" describes which class to derive different kinds of components from.
Deriving classes is explained in more detail in the section "Defining new classes".
To derive a component, add an object type declaration to the interface part of the unit that will contain the component.
A simple component class is a nonvisual component descended directly from TComponent.
To create a simple component class, add the following class declaration to the interface part of your component unit:
type TNewComponent = class(TComponent) end;
So far the new component does nothing different from TComponent. You have created a framework on which to build your new component.
Registration is a simple process that tells Delphi which components to add to its component library, and on which pages of the Component palette they should appear. For a more detailed discussion of the registration process, see "Making components available at design time."
procedure Register;
If you are adding a component to a unit that already contains components, it should already have a Register procedure declared, so you do not need to change the declaration.
To register a component named TMyControl and place it on the Samples page of the palette, you would add the following Register procedure to the unit that contains TMyControl's declaration:
procedure Register;
begin
RegisterComponents('Samples', [TNewControl]);
end;
This Register procedure places TMyControl on the Samples page of the Component palette.
Once you register a component, you can compile it into a package (see Chapter 20) and install it on the Component palette.
You can test the runtime behavior of a component before you install it on the Component palette. This is particularly useful for debugging newly created components, but the same technique works with any component, whether or not it is on the Component palette.
You test an uninstalled component by emulating the actions performed by Delphi when the component is selected from the palette and placed on a form.
To test an uninstalled component,
This is one of the main differences between the way you add components and the way Delphi does it. You add the object field to the public part at the bottom of the form's type declaration. Delphi would add it above, in the part of the type declaration that it manages.
Never add fields to the Delphi-managed part of the form's type declaration. The items in that part of the type declaration correspond to the items stored in the form file. Adding the names of components that do not exist on the form can render your form file invalid.
When you call the component's constructor, you must pass a parameter specifying the owner of the component (the component responsible for destroying the component when the time comes). You will nearly always pass Self as the owner. In a method, Self is a reference to the object that contains the method. In this case, in the form's OnCreate handler, Self refers to the form.
Setting the Parent property is always the first thing to do after constructing a control. The parent is the component that contains the control visually; usually it is the form on which the control appears, but it might be a group box or panel. Normally, you'll set Parent to Self, that is, the form. Always set Parent before setting other properties of the control.
Warning: If your component is not a control (that is, if TControl is not one of its ancestors), skip this step. If you accidentally set the form's Parent property (instead of the component's) to Self, you can cause an operating-system problem.
Suppose you want to test a new component of type TMyControl in a unit named MyControl. Create a new project, then follow the steps to end up with a form unit that looks like this:
unit Unit1;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, MyControl; { 1. Add NewTest to uses clause }
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject); { 3. Attach a handler to OnCreate }
private
{ Private declarations }
public
{ Public Declarations }
MyControl1: TMyControl1; { 2. Add an object field }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
MyControl1 := TMyControl.Create(Self); { 4. Construct the component }
MyControl1.Parent := Self; { 5. Set Parent property if component is a control }
MyControl1.Left := 12; { 6. Set other properties... )
... ...continue as needed }
end;
end.
pubsweb@inprise.com
Copyright © 1999, Inprise Corporation. All rights reserved.