Principles
First al all, most of the visual properties of a control do not require the control to have a valid window handle in order to be set. It is a false assumption that they do.
Once the object that constitutes a control is created, i.e. the constructor has been executed, normally all (visual) properties like size, position, font, color, alignment, etc. can be set. Or they should be able to, preferably. For sub controls, also the Parent
ideally must be set as soon as the constructor has run. For the component itself, that constructor would be the inherited constructor during its own constructor.
The reason this works is that all these kind of properties are stored within the fields of the Delphi object itself: they are not immediately passed to the Windows API. That happens in CreateWnd
but no sooner than when all necessary parent window handles are resolved and assigned.
So the short answer is: the initial setup of a custom component is done in its constructor, because it is the only routine that runs once.
But the question (unintentionally) touches a wide range of topics on component building, because the complexity of an initial setup of a control depends entirely on the type of control and the properties that are to be set.
Example
Consider writing this (useless yet illustrative) component that consists of a panel with a combo box aligned on top of it. The panel should initially have: no caption, a custom height and a silver background. The combo box should have: a custom font size and a 'picklist' style.
type
TMyPanel = class(TPanel)
private
FComboBox: TComboBox;
public
constructor Create(AOwner: TComponent); override;
end;
constructor TMyPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Color := clSilver;
ShowCaption := False;
Height := 100;
FComboBox := TComboBox.Create(Self);
FComboBox.Parent := Self;
FComboBox.Align := alTop;
FComboBox.Style := csDropDownList;
FComboBox.Font.Size := 12;
end;
Framework conformity
A component writer could now consider it done, but it is not. He/she has the responsibility to write components properly as described by the comprehensive Delphi Component Writer's Guide.
Note that no less then four properties (indicated bold in the object inspector) are needlessly stored in the DFM because of an incorrect designtime component definition. Although invisible, the caption property still reads MyPanel1, which is against te requirements. This can be solved by removing the applicable control style. The ShowCaption
, Color
and ParentBackground
properties lack a proper default property value.
Note too that all default properties of TPanel
are present, but you might want some not te be, especially the ShowCaption
property. This can be prevented by descending from the right class type. The standard controls in the Delphi framework mostly offer a custom variant, e.g. TCustomEdit
instead of TEdit
that are there for exactly this reason.
Our example compound control that is rid of these issues looks as follows:
type
TMyPanel = class(TCustomPanel)
private
FComboBox: TComboBox;
public
constructor Create(AOwner: TComponent); override;
published
property Color default clSilver;
property ParentBackground default False;
end;
constructor TMyPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Color := clSilver;
ControlStyle := ControlStyle - [csSetCaption];
Height := 100;
FComboBox := TComboBox.Create(Self);
FComboBox.Parent := Self;
FComboBox.Align := alTop;
FComboBox.Style := csDropDownList;
FComboBox.Font.Size := 12;
end;
Of course, other implications due to setting up a component are possible.
Exceptions
Unfortunately there áre properties that require a control's valid window handle, because the control stores its value in Windows' native control. Take the Items
property of the combo box above for example. Consider a deisgn time requirement of it been filled with some predefined text items. You then should need to override CreateWnd
and add the text items the first time that it is called.
Sometimes the initial setup of a control depends on other controls. At design time you don't (want to) have control over the order in which all controls are read. In such case, you need to override Loaded
. Consider a design time requirement of adding all menu-items from the PopupMenu
property, if any, to the Items
property of the combo box.
The example above, extended with these new features, results finally in:
type
TMyPanel = class(TCustomPanel)
private
FInitialized: Boolean;
FComboBox: TComboBox;
procedure Initialize;
protected
procedure CreateWnd; override;
procedure Loaded; override;
public
constructor Create(AOwner: TComponent); override;
published
property Color default clSilver;
property ParentBackground default False;
property PopupMenu;
end;
constructor TMyPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Color := clSilver;
ControlStyle := ControlStyle - [csSetCaption];
Height := 100;
FComboBox := TComboBox.Create(Self);
FComboBox.Parent := Self;
FComboBox.Align := alTop;
FComboBox.Style := csDropDownList;
FComboBox.Font.Size := 12;
end;
procedure TMyPanel.CreateWnd;
begin
inherited CreateWnd;
if not FInitialized then
Initialize;
end;
procedure TMyPanel.Initialize;
var
I: Integer;
begin
if HandleAllocated then
begin
if Assigned(PopupMenu) then
for I := 0 to PopupMenu.Items.Count - 1 do
FComboBox.Items.Add(PopupMenu.Items[I].Caption)
else
FComboBox.Items.Add('Test');
FInitialized := True;
end;
end;
procedure TMyPanel.Loaded;
begin
inherited Loaded;
Initialize;
end;
It is also possible that the component depends in some way on its parent. Then override SetParent
, but also remember that any dependency on (properties of) its parent likely indicates a design issue which might require re-evaluation.
And surely there are other kind of dependencies imaginable. They then would require special handling somewhere else in the component code. Or another question here on SO. ??