Creating components[]
Introduction[]
Creating components in Delphi is a fairly simple task as long as you understand the workings of Object Oriented Programming. Since components are classes, most of the tips and techniques apply to both worlds, but there are a few gotcha's for components, since they descend from a particular type of class, TComponent. Components divide into three main huge categories: non-visual, visual and half-way. Non-visual components usually descend directly from TComponent or some other class that will ultimately descend from it, don't have a paint mechanism of any kind and are identifiable only through their icon. Visual components, instead, will mostly ultimately descend from one of these:
- TGraphicControl - TWinControl - TCustomControl
TGraphicControl[]
This is a base class for controls which doesn't need keyboard input or the ability to be containers for other components. It will indeed be used for all lightweight components. An example component is TSpeedButton. This is actually a not so common behaviour, i.e. you will usually want to give keyboard awareness to your components. It does have a painting mechanism.
TWinControl[]
This is the base component wrapping up Windows-provided components, such as TButton or TForm(well, this latter case is a bit more involved, but it ultimately inherits from TWinControl). TWinControl doesn't have a paint mechanism of its own, it rather relies on the built-in component facilities for this, but it has keyboard awareness.
TCustomControl[]
This is the wealthiest and heaviest base class of all: it features all of the other base classes plus much more, custom events and is the most flexible of all. You'll most probably be using this as base class for most of your components, because it unleashes the full horse-power of component development.
Inheriting from existing components[]
Clearly, although those three above are the base classes all visual components will come from, you can always inherit an already existing component if that suits you. You might, for example, want to add a print method to the TMemo component or make TImageList usage easier. Let's make a simple example using TMemo, saying you want to add a print method to it. As with most components, you have two choices: inherit from TCustomMemo or from TMemo. The difference is that in TCustomMemo you have all the properties, events and methods already defined but not published. TMemo, instead, publishes all relevant properties and events. This is true for most of the components in VCL, so keep it in mind.
Properties and Events[]
While you can surely read many articles about defining properties and events in Delphi classes, it's maybe worth to recap it shortly so that everyone is at the same level at least. Properties can be defined using getters and setters. These can be either fields or methods or a mixed approach (for instance, a getter field and a setter method, which actually is the default in Delphi). The syntax for property's definition is as follows:
property IntProp: Integer read FIntProp write FIntProp; property IntProp: Integer read GetIntProp write SetIntProp; property IntProp: Integer read FIntProp write SetIntProp; property IntProp: Integer read GetIntProp write FIntProp;
These above are the four possible combinations, although properties can also be read only or write only(although the latter not very common ones).
Events are object method's pointers in this form:
type TMyEvent = {procedure|function}[(Arguments)] [: ReturnType] of object;
Clearly, method procedures won't have a return type while functions will, although the most common event type is just that, a method procedure. The reason for this is that the code calling an event must work whether that event is assigned or not. A function returns a value so the calling code must assign a default for this value anyway. A procedure type of event can define that value as a var parameter and the code that calls it can set that variable to the default, so the event code doesn't really need to care about that value but just leave it to its default.
The most important event type in Delphi component development is TNotifyEvent, which has a notification purpose and many events use it such as:
- OnClick - OnDblClick - OnCreate - OnDestroy
and many others.
When you double click in the events tab of the object inspector to hook to an event you're creating an event handler. But the power of Delphi is that you can hook to events even at runtime, so you might have - for example - a class defining an event handler for another class and hook to it, so that you have inter-objects communication. We'll see this in an example very soon.
Components at design time[]
This is all cool and stuff, but how does the IDE remember that I placed a TMemo component in certain co-ordinates and load it back once it's saved? This is done thanks to the streaming system which allows you to save TPersistent descendants (and TComponent inherits from that) in a Delphi Form (= dfm). DFM files are basically resources of the application and it works more or less like the Visual C++ resource editor, except that Delphi takes all that to RAD without needing to mess with handles and stuff like that, which is all managed automatically.
A simple component[]
This unit represents a simple component:
unit UselessComponent; interface uses Classes; Type TUselessComponent = class( TComponent ) //contoh private FIntProp : Integer; published property IntProp : Integer read FIntProp write FIntProp; end; procedure Register; implementation procedure Register; begin RegisterComponents('Useless', [TUselessComponent]); end; end.
Please note that for compatibility reasons with Borland C++ Builder, the register procedure must be written all lowercase except the first letter in upper case.
Components deployment[]
Now that we have our TUselessComponent, we shall install it in the IDE. To do this we need one or two packages, according to what we want to do. The first step, in any case, is to create a package. Once we have it, the easiest way is to verify the options and set "Designtime and runtime" radio button, add a description, click ok and then adding the unit in it. The last step is pressing "install" and let the IDE compile and install it. This is the easy way. Then there's a more correct way, consisting of two packages, the first run time only and the second design time only. The second one will have a unit called "UselessComponentReg" which will contain the Register procedure, which will then be refactored out of the component source. Then we add the runtime package reference in the Requires section of the designtime one, and then install the latter.
The difference in the two approaches is that, in the first one, everything is mixed together, while in the second design and run time stuff are separated. See Creating Packages for a more in depth explanation of runtime and designtime packages and why you should split them.