原则
首先,控件的大多数视觉属性不要求控件具有有效的窗口句柄才能进行设置。这是一个错误的假设。
一旦创建了构成控件的对象,即执行了构造函数,通常可以设置所有(视觉)属性,如大小、位置、字体、颜色、对齐方式等。或者他们应该能够,最好是。对于子控件,理想情况下,Parent 也必须在构造函数运行后立即设置。对于组件本身,该构造函数将是其自己的构造函数期间继承的构造函数。
这样做的原因是所有这些类型的属性都存储在 Delphi 对象本身的字段中:它们不会立即传递给 Windows API。这发生在CreateWnd,但不会早于所有必要的父窗口句柄都被解析和分配。
所以简短的回答是:自定义组件的初始设置是在其构造函数中完成的,因为它是唯一运行一次的例程。
但这个问题(无意中)涉及组件构建的广泛主题,因为控件初始设置的复杂性完全取决于控件的类型和要设置的属性。
示例
考虑编写这个(无用但说明性的)组件,它由一个面板和一个在其顶部对齐的组合框组成。面板最初应该有:没有标题、自定义高度和银色背景。组合框应具有:自定义字体大小和“选择列表”样式。
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;
框架一致性
组件编写者现在可以认为它已完成,但事实并非如此。他/她有责任按照综合 Delphi Component Writer's Guide 的描述正确编写组件。
请注意,由于设计时组件定义不正确,至少有四个属性(在对象检查器中以粗体表示)不必要地存储在 DFM 中。虽然不可见,但标题属性仍为 MyPanel1,这违反了要求。这可以通过删除适用的control style 来解决。 ShowCaption、Color 和 ParentBackground 属性缺少正确的 default property value。
还要注意TPanel 的所有默认属性都存在,但您可能不希望出现一些默认属性,尤其是ShowCaption 属性。这可以通过从正确的类类型下降来防止。 Delphi 框架中的标准控件大多提供自定义变体,例如TCustomEdit 而不是 TEdit 正是出于这个原因。
摆脱这些问题的示例复合控件如下所示:
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;
当然,由于设置组件而产生的其他影响也是可能的。
例外情况
不幸的是,有些属性需要控件的有效窗口句柄,因为控件将其值存储在 Windows 的本机控件中。以上面组合框的Items 属性为例。考虑用一些预定义的文本项填充它的设计时间要求。然后您应该需要覆盖CreateWnd并在第一次调用它时添加文本项。
有时控件的初始设置取决于其他控件。在设计时,您不(想要)控制读取所有控件的顺序。在这种情况下,您需要覆盖Loaded。考虑将所有菜单项从PopupMenu 属性(如果有)添加到组合框的Items 属性的设计时间要求。
上面的例子,加上这些新特性,最终得到:
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;
组件也有可能以某种方式依赖于其父级。然后覆盖SetParent,但还要记住,对其父级(的属性)的任何依赖都可能表明可能需要重新评估的设计问题。
当然还有其他可以想象的依赖关系。然后他们需要在组件代码的其他地方进行特殊处理。或者关于SO的另一个问题。 ?