六、控件手拉手――控件关联的实现
控件的关联在 Delphi 中也是很常见的,我们可以设定一个控件的某个属性指向另一个控件。比如我们在窗体上放上 Tedit,TpopupMenu 两个控件,然后设定 Tedit PopupMenu 属性为 TpopupMenu 控件,运行后在 Tedit 点击右键就会弹出刚才设定的那个 TpopupMenu 菜单,也就是说 Tedit,TpopupMenu 联手完成了任务。再比如 TDBEdit 控件的 DataSource 属性就可以指向一个 TdataSource 控件,这样就可以在 TDBEdit 控件中显示 TdataSource 输出的某个字段的值了。
  下面我们将写一个简单的实现控件关联的控件。这个控件派生于 Tedit, 它可以与一个 Tlabel 控件关联,在控件的编辑框中输入文字时,与它关联的 Tlabel 控件的文字将随着它而变化。代码如下:
unit MyEdit;
interface
uses
  SysUtils, Classes, Controls, StdCtrls;
type
  TMyEdit = class(TEdit)
  private
    FLinkLabel: TLabel;
    procedure FSetLinkLabel(AValue: TLabel);
  protected
    procedure Notification(AComponent: TComponent;Operation: TOperation);
          override;
    procedure Change;override;
  public
  published
    property LinkLabel: TLabel read FLinkLabel write FSetLinkLabel;
  end;
 <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

procedure Register;
 

implementation
procedure Register;
begin
  RegisterComponents('Linco', [TMyEdit]);
end;
procedure TMyEdit.Change;
begin
  inherited;
  if LinkLabel <> nil then
    LinkLabel.Caption := Text;
end;
 

procedure TMyEdit.FSetLinkLabel(AValue: TLabel);
begin
  FLinkLabel := AValue;
  if AValue <> nil then
    FLinkLabel.FreeNotification(self);
end;
 

procedure TMyEdit.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent = LinkLabel) then
    LinkLabel := nil;
end;
end.
代码解释:
1 )、我们只要将控件的任意一个属性的类型设定为另外一个控件的类名称,那么我们就可以在控件的 Object Inspector 中将这个属性指向那个控件(或那个控件的派生控件)的一个实例。比如本例中我们增加了 LinkLabel 属性,它的类型为 Tlabel ,所以我们就可以把 LinkLabel 属性指向一个标签控件。
2 )、请注意 FsetLinkLabel 中的这段代码:
 if AValue <> nil then
FLinkLabel.FreeNotification(self);
如果我们将控件关联属性指向了一个控件,可是后来又将被指向的控件删除了,那么我们的控件关联属性是不会自动删除的,这样就会造成控件关联属性指向的控件不存在的现象。我们必须自动感知被关联控件的删除并重新设定控件关联属性为不指向任何控件,这样就避免了错误的发生。
FLinkLabel.FreeNotification(self); 的作用就是这样的。它调用控件的 FreeNotification 方法(在 Tcomponent 中定义)向被指向的控件注册一个“消息”,当被指向控件被删除时,会向所有向他注册的控件发送一个它被删除的消息,此时向他注册的控件就会触发 Notification 方法,这样我们就可以自动感知被指向控件的状态了。这是设计模式中 Observer( 观察者 ) 模式的典型应用。
既然向他注册的控件就会触发 Notification 方法,我们就覆盖父类的 Notification 方法,写出如下的代码:
  if (Operation = opRemove) and (AComponent = LinkLabel) then
    LinkLabel := nil;
这句话的意思是:如果控件被删除并且被删除的控件(因为我们的控件可能向多个控件注册了消息)是 LinkLabel ,那么我们就设定 LinkLabel 属性不指向任何控件。
3 )覆盖父类的 Change 调度方法。在此方法里为连接的 LinkLabel Caption 赋值就达到我们的目的了。
思考题:
1 、做一个 Label 控件,给它增加一个 DataSource 属性,该属性可以指向一个 TdataSource 类型的控件,它有一个 GetRecordCount 方法。当调用此方法时,就在 Label 控件中显示这个 DataSource 对应的数据集中的记录的条数。