五、复合控件
复合控件是 Delphi 控件中非常重要的一种控件,复合控件就是将两个或两个以上的控件重新组合成一个新的控件。例如 TspinEdit TlabeledEdit TDBNavigator 等就是复合控件, TDBNavigator 其实就是在一个 Panel 放上若干个 Button 而已。制作一个复合控件时,我们一般从 TwinControl 派生控件。
我们这次做的控件是拥有一个 Edit 编辑框和一个 Button 按钮的复合控件,在用户在编辑框中输入文字的过程中, Button 将随时显示编辑框中文字的长度。我们把控件的源码先展示给大家。
unit EditButton;
interface
uses
  SysUtils, Classes, Controls, StdCtrls, Messages;
type
  TEditButton = class(TWinControl)
  private
    FEdit: TEdit;
    FButton: TButton;
    FText: string;
    procedure FSetText(AValue: string);
    procedure OnEditChange(Sender: TObject);
  protected
    procedure WMSize(var Msg: TMessage);message WM_SIZE;
  public
    constructor Create(AOwner: TComponent);override;
    destructor Destroy;override;
  published
    property Text: string read FText write FSetText;
  end;
 <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

procedure Register;
implementation
 

procedure Register;
begin
  RegisterComponents('Linco', [TEditButton]);
end;
 

constructor TEditButton.Create(AOwner: TComponent);
begin
  inherited;
  FEdit := TEdit.Create(nil);
  FEdit.Parent := self;
  FEdit.Top := 0;
  FEdit.Left := 0;
  FEdit.Height := Height;
  FEdit.Width := Width div 2;
  FEdit.OnChange := OnEditChange;
  FButton := TButton.Create(nil);
  FButton.Parent := self;
  FButton.Top := 0;
  FButton.Left := Width div 2;
  FButton.Height := Height;
  FButton.Width := Width div 2;
end;
 

destructor TEditButton.Destroy;
begin
  FEdit.Free;
  FButton.Free;
  inherited;
end;
 

procedure TEditButton.FSetText(AValue: string);
begin
  FEdit.Text := AValue;
end;
 

procedure TEditButton.OnEditChange(Sender: TObject);
begin
  FButton.Caption := IntToStr(Length(FEdit.Text));
end;
 

procedure TEditButton.WMSize(var Msg: TMessage);
begin
  FEdit.Height := Height;
  FEdit.Width := Width div 2;
  FButton.Left := Width div 2;
  FButton.Height := Height;
  FButton.Width := Width div 2;
end;
end.
代码解释:
1 )、我们首先定义了两个变量   
    FEdit: TEdit;
    FButton: TButton;
  分别代表复合控件中的文字编辑框和按钮。
2 )所谓复合控件说简单一点就是在一个共同的基板上将组成复合控件的各个控件(可以叫做子控件)画出来。所以我们在构造函数中建立各个子控件,然后分别设定它们的位置等属性。
以文字编辑框为例:
FEdit := TEdit.Create(nil);
的作用是建立编辑框控件。如果 Create 的参数指定为 nil, 则子控件在设计状态是可以响应用户的操作的;而如果设定为 self( 即设定子控件的父控件为基板 ) ,则子控件在设计时时不可响应用户操作的,如果设定为 self 则析构函数中就不用 Fedit.Free 来销毁对象了,对象会自动销毁。
  FEdit.Parent := self; 的作用是设定子控件的父控件,如果没有这一句则控件是无法显示的。
  FEdit.Top := 0;
  FEdit.Left := 0;
  FEdit.Height := Height;
  FEdit.Width := Width div 2;
这四句是设定控件在基板上的相对位置的,这里的 Top,Left 不是相对于窗体的,而是相对于基板的。
  FEdit.OnChange := OnEditChange;
则是设定编辑框控件的 OnChange( 文字改变事件 ) 的处理句柄为 OnEditChange
(1)    用户有可能在设计时或运行时通过代码改变控件的大小,这时控件中子控件的顺序就会变得乱七八糟,所以需要相应控件的 WM_SIZE 事件(控件大小发生变化的事件)重新设定子控件的位置,大小等。函数 WMSize 的作用就是这样的。
安装控件后发现控件已经可以正确运行了,但是还有一个问题,就是这个控件没有了 Onclick,Onchange 等必须的属性。我们只要为控件增加事件处理句柄属性,然后把事件处理句柄属性的读写方法都指向子控件的事件处理句柄属性即可。例如我们为控件增加 OnClick 事件,这个事件发生在用户单击按钮时,我么只要在 Pulished 部分增加如下代码:
property  OnClick:  TnotifyEvent read GetOnClick write SetOnClick
Private 中增加如下方法声明:
function GetOnclick: TnotifyEvent;
procedure SetOnclick(AValue: TnotifyEvent);
这两个方法的实现分别为:
function  TeditButton. GetOnclick: TnotifyEvent;
begin
  result := Fbutton.Onclick;
end;
procedure TeditButton. SetOnclick(AValue: TnotifyEvent);
begin
  Fbutton.OnClick := Avalue;
end;
思考题:
1 、做一个模仿播放器中的操作按钮的复合控件,控件由三个按钮组成,分别是“播放”、“暂停”、“停止”,请按照正常的逻辑关系,处理这三个按钮的可用 / 不可用关系。(提示:可以参考 TDBNavigator 的源代码)