Article submitted by: Peter Morris
"This article originally appeared in Delphi Developer. Copyright Pinnacle Publishing, Inc. All rights reserved."
This article is part two of a three part article on components. Part one covered the basic creating of components, part two will cover how to write advanced properties, how to write custom streaming for those properties, and sub-properties. The final part will cover property / component editors, how to write dedicated editors for your component / property, and how to write "hidden" components.
Quite often it is necessary to write components that perform more advanced functions. These components often need to either reference other components, have custom property data formats, or have a property that owns a list of values rather than a single value. In this part, we will explore various examples covering these very subjects, starting with the most simple.
Component references
Some components need to reference other components. TLabel for instance has a "FocusControl" property. When you include an ampersand in the "Caption" property it underlines the next letter (&Hello becomes Hello), pressing the shortcut key ALT-H on your keyboard will trigger an event in your label. If the "FocusControl" property has been set focus will be passed to the control specified.
To have such a property in your own component is quite simple. All you do is declare a new property, and set the property type to the lowest base class that it may accept (TWinControl will allow any descendent of TWinControl to be used), but, there are implications.
type
TSimpleExample = class(TComponent)
private
FFocusControl : TWinControl;
protected
procedure SetFocusControl(const value : TWinControl);
virtual;
public
protected
property FocusControl : TWinControl
read FFocusControl
write SetFocusControl;
end;
procedure TSimpleExample.SetFocusControl(const Value : TWinControl);
begin
FFocusControl := Value;
end;
|
Take the above example. This is quite a simple example (hence the component name) of how to write a component that references another component. If you have such a property in your component the Object Inspector will show a combobox with a list of components that match the criteria (all components descended from TWinControl).
Our component may do something like
procedure TSimpleExample.DoSomething;
begin
if (Assigned(FocusControl)) and
(FocusControl.Enabled) then
FocusControl.Setfocus;
end;
|
First we check if the property has been assigned, if so we set focus to it, but there are situations when the property is not Nil yet the component it points to is no longer valid. This often happens when a property references a component that has been destroyed.
Luckily Delphi provides us with a solution. Whenever a component is destroyed it notifies its owner (our form) that it is being destroyed. At this point every component owned by the same form is notified of this event too. To trap this event we must override a standard method of TComponent called "Notification".
type
TSimpleExample = class(TComponent)
private
FFocusControl : TWinControl;
protected
procedure SetFocusControl(const value : TWinControl);
virtual;
public
protected
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
property FocusControl : TWinControl
read FFocusControl
write SetFocusControl;
end;
procedure TSimpleExample.SetFocusControl(const Value : TWinControl);
begin
FFocusControl := Value;
end;
procedure TSimpleExample.Notification(AComponent : TComponent;
Operation : TOperation);
begin
If (Operation = opRemove) and
(AComponent = FocusControl) then
FFocusControl := Nil;
end;
|
Now when our referenced component is destroyed we are notified, at which point we can set our reference to Nil. Note, however, that I said
"every component owned by the same form is notified of this event too"
This introduces us with another problem. We are only notified that the component is being destroyed if it is owned by the same form. It is possible to have our property point to components on other forms (or even without an owner at all), and when these components are destroyed we are not notified. Yet again there is a solution.
TComponent introduces a method called "FreeNotification". The purpose of FreeNotification is to tell the component (FocusControl) to keep us in mind when it is destroyed.
An implementation would look like this
type
TSimpleExample = class(TComponent)
private
FFocusControl : TWinControl;
protected
procedure SetFocusControl(const value : TWinControl);
virtual;
public
protected
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
property FocusControl : TWinControl
read FFocusControl
write SetFocusControl;
end;
procedure TSimpleExample.SetFocusControl(const Value : TWinControl);
begin
if Assigned(FFocusControl) then
FFocusControl.RemoveFreeNotification(Self);
FFocusControl := Value;
if Assigned(FFocusControl) then
FFocusControl.FreeNotification(Self);
end;
procedure TSimpleExample.Notification(AComponent : TComponent;
Operation : TOperation);
begin
if (Operation = opRemove) and
(AComponent = FocusControl) then
FFocusControl := Nil;
end;
|
When setting our FocusControl property we first check if it is already set to a component. If it is already set we need to tell the original component that we no longer need to know when it is destroyed. Once our property has been set to the new value we inform the new component that we require a notification when it is freed. The rest of our code remains the same as the referenced component still calls our Notification method.
Next page > Sets > Page 1, 2, 3, 4, 5
Creating Custom Delphi Components >>
>>
Part III.