constructor
TLinkedLabel.Create(AOwner: TComponent); begin inherited; FGap := 8;
end; 现在我们来看一下Adjust方法,它负责安排LinkedLabel或者关联控件的放置(取决于MoveLabel参数的取值)。正如你将在代码中看到的,LinkedLabel与相关控件的实际位置取决于Gap和OnTop属性(见图2)。虽然我们在OnTop中只提供了两种可能的选择,不过可以很容易的对其编程以提供更多的可能性。不过,把TlinkedLabel武装到牙齿(原文是“add
a lot of "bells and whistles"”,译者注)并不是本文的重点,这项任务就委托给读者们来完成吧。 procedure
TLinkedLabel.Adjust(MoveLabel: Boolean); var dx, dy: Integer;
begin if (Assigned(FAssociate)) then begin if (FOnTop)
then begin dx := 0; dy := Height + FGap;
end else begin dx := Width + FGap; dy := (Height -
FAssociate.Height) div 2; end; if (MoveLabel) then begin Left :=
FAssociate.Left - dx; Top := FAssociate.Top - dy;
end else begin FAssociate.Left := Left + dx; FAssociate.Top
:= Top + dy;
end; end; end; 现在,我们来完成Gap和OnTop属性的set方法(见图3),以便当Gap或者Onop属性被修改时我们可以改变LinkedLabel的位置。 procedure
TLinkedLabel.SetGap(Value: Integer); begin if (FGap $#@60;$#@62; Value)
then begin FGap := Value; Adjust(True);
end; end;
procedure TLinkedLabel.SetOnTop(Value: Boolean);
begin if (FOnTop $#@60;$#@62; Value) then begin FOnTop := Value;
Adjust(True); end; end;
现在是SetAssociate方法 procedure
TLinkedLabel.SetAssociate(Value: TControl); begin if (Value $#@60;$#@62;
FAssociate) then begin if (Assigned(FAssociate))
then FAssociate.WindowProc := FOldWinProc; FAssociate := Value; if
(Assigned(Value)) then begin Adjust(True); Enabled :=
FAssociate.Enabled; Visible := FAssociate.Visible; FOldWinProc :=
FAssociate.WindowProc; FAssociate.WindowProc := NewWinProc;
end; end; end;
为了便于理解,我们需要详细的讨论一下WindowProc属性。WindowProc被定义为TwndMethod类型。TwndMethod可以在Controls单元中找到,定义如下: TWndMethod
= procedure(var Message: TMessage) of
object; 注意,FoldWinProc同样被定义为TwndMethod,并且NewWinProc方法拥有与TwndMethod相同的参数结构。这就允许我们将FoldWinProc指向WindowProc的当前值,并把WindowProc重定向到NewWinProc方法。如果WindowProc只是另一个事件属性的话,我们为什么需要使用FoldWinProc呢?因为WindowProc与其它事件属性的不同之处在于WindowProc指向一个已经存在的事件处理器。如果我们只是简单的将WindowProc指向我们的方法,这个控件将不能再对任何Windows消息产生响应。为了解决这个问题,我们在把WindowProc指向NewWinProc之前把FoldWinProc设置为WindowProc的当前值。 在NewWinProc中,我们通过FoldWinProc调用原先的消息处理器(message
handler),并且处理特定的Windows消息。因为我们修改了关联控件的WindowProc值,因此要在把关联改变到一个新的控件之前恢复它从前的取值。 避免把关联控件的WindowProc属性指向一个不再存在的例程也同样重要。如同我们所见的,在析构器中调用SetAssociate(nil)将会把WindowProc恢复为初始值。 destructor
TLinkedLabel.Destroy;
begin SetAssociate(nil); inherited; end 另外,我们也不希望关联到一个不再存在控件。通过覆盖Notification方法,我们可以知道关联组件何时被销毁,从而重置关联的指针: procedure
TLinkedLabel.Notification(AComponent: TComponent; Operation: TOperation);
begin if ((Operation = opRemove) and (AComponent = FAssociate)) then
SetAssociate(nil); end; 现在我们来看NewProc方法。这里,我们只是寻找发送给关联控件的特定Windows消息。认识到这一点是很重要的:虽然方法通过关联控件调用,但它实际上是LinkedLabel的一部分,例如,Self=LinkedLabel,而不是关联控件。这对为一个按钮创建onclick事件处理器来说也是一样的,onclick事件处理器是作为按钮父窗体的一部分,而不是扩充Tbutton类的新方法。 procedure
TLinkedLabel.NewWinProc(var Message: TMessage); var Ch: Char;
begin if (Assigned(FAssociate) and (not FUpdating)) then
begin FUpdating := True; try case(Message.Msg) of WM_CHAR:
if (FCapsLock) then begin Ch := Char(TWMKey(Message).CharCode); if
(Ch $#@62;= ’a’) and (Ch $#@60;= ’z’) then TWMKey(Message).CharCode :=
ord(UpCase(Ch)); end; CM_ENABLEDCHANGED: Enabled :=
FAssociate.Enabled; CM_VISIBLECHANGED: Visible := FAssociate.Visible;
WM_SIZE, WM_MOVE, WM_WINDOWPOSCHANGED: Adjust(True);
end; finally FUpdating := False;
end; end; FOldWinProc(Message); end;
如果你检查一下这个例程,就会发现我们并没有花多少力气去处理Windows消息。我们只注意几个特定的消息,然后就让关联通过调用FOldWinProc正常的处理它们。在处理WM_CHAR消息的时候,我们对消息的一部分做了改变,让控件认为我们按下的是大写字母键。 最后,我们关心一下两个不同的消息,以确定关联控件是否被移动了。这样做的原因在于从TwinControl继承的控件会在它们被移动时接到WM_MOVE消息,而此时其它的可视控件(如一个标签)则会收到WM_WINDOWPOSCHANGED消息。程序也检查了WM_SIZE消息,原因是如果OnTop属性为False,则LinkedLabel的位置会随控件的高度而变化。 我们这个控件的最后一个方法是:当LinkedLabel被改变时,要在关联的什么地方作修改?当然我们不使用覆盖Tlabel的现存方法来实现它,而是要用修改关联行为的相同技术来做。注意我们不是重新定向WindowsProc属性,而是覆盖了WndProc方法。为什么把它们叫做相同的技术呢?如果你看一下TControl的构造器,你可以发现WindowProc会被初始化以指向WndProc方法。所以从本质上讲,我们覆盖的是同一种方法,不过做得更“干净”,也不用去保存WindowProc的初始值。 procedure
TLinkedLabel.WndProc(var Message: TMessage); begin if
(Assigned(FAssociate) and (not FUpdating)) then begin FUpdating := True;
try case(Message.Msg) of CM_ENABLEDCHANGED: FAssociate.Enabled :=
Enabled; CM_VISIBLECHANGED: FAssociate.Visible := Visible;
WM_WINDOWPOSCHANGED: Adjust(False); end; finally FUpdating :=
False;
end; end; inherited; end;