Hola de nuevo:
Después de darles muchas vueltas finalmente he conseguido resolver el problema. Voy a intentar explicar la solución lo mejor que pueda, porque la verdad es que hay que saber primero como se comporta el mensaje WM_MOVE para comprender la solución; finalmente he decidido capturar este mensaje y no WM_WINDOWPOSCHANGING propuesto por Lepe, ya que pensaba (sin haberlo probado a fondo) que a efectos prácticos era lo mismo uno que otro.
El problema estaba en que cada componente (TComponenteA y TComponenteB) respondían a su correspondiente mensaje WM_MOVE sin tener en cuenta la respuesta del otro. De ese modo cuando, en tiempo de diseño, seleccionaba y arrastraba los dos componente a la vez se liaban los cálculos.
Para comprobar el orden de llamada incluí varios ShowMessage en el código. Así pude comprobar, para mi sorpresa, que el modo en que se sucedían los mensajes WM_MOVE no era el que yo esperaba. Este es el orden (recuerden siempre que estamos hablando de tiempo de diseño y que estamos arrastrando los dos componentes a la vez):
1.- Se ejecuta la respuesta al mensaje WM_MOVE en TComponenteA. Aquí se modifican las propiedades Left y Top de TComponenteB.
2.- Primera sorpresa: se ejecuta dos veces la respuesta al mensaje WM_MOVE en TComponenteB: uno para Left (desplazamiento horizontal) y otro para Top (desplazamiento vertical).
3.- Segunda sorpresa: Se ejecuta una tercera respuesta al mensaje WM_MOVE en TComponenteB, que correspondería al momento en que se suelta el componente en el form. Para empeorar las cosas, en este momento el propio Delphi suma (o resta) las propiedades Top y Left del componente según el desplazamiento.
Vamos, un lio.
El principal problema era saber si el evento correspondía al punto 2 o al 3, sin necesidad de que los componentes se comunicaran a través de eventos o propiedades. Al final utilicé la función GetTickCount, del API, para medir el tiempo en milésimas de segundo entre una llamada y otra. ¡Ojo, hay que calcular un tiempo para Left y otro para Top, porque no siempre el desplazamiento es en diagonal! Tras hacer pruebas, vi que la diferencia de tiempo entre el punto 2 y 3 era 0.
Y este es el resultado...
Código Delphi
[-]
TComponenteA = class(TPadre)
private
FTop: Integer;
FLeft: Integer;
FTickCountTop: Cardinal;
FTickCountLeft: Cardinal;
procedure WMMove(var Msg: TMessage); message WM_MOVE;
function GetComponenteB: TComponent;
protected
public
Constructor Create(AOwner: TComponent); Override;
published
end;
Constructor TComponenteA.Create(AOwner: TComponent);
Begin
Inherited Create(AOwner);
FTop:=Top;
FLeft:=Left;
FTickCountTop:=GetTickCount;
FTickCountLeft:=FTickCountTop;
End;
procedure TComponenteA.WMMove(var Msg: TMessage);
var
MoveTickCountTop: Cardinal;
MoveTickCountLeft: Cardinal;
DiferenciaTickCount: Cardinal;
begin
inherited;
If GetComponenteB=Nil Then
begin
FTop:=Top;
FLeft:=Left;
exit;
end;
If (FTop<>Top) Then
begin
MoveTickCountTop:=GetTickCount;
DiferenciaTickCount:=MoveTickCountTop-FTickCountTop;
FTickCountTop:=MoveTickCountTop;
If DiferenciaTickCount=0 Then Top:=FTop;
(GetComponenteB As TComponenteB).Top:=Top;
end;
If (FLeft<>Left) Then
begin
MoveTickCountLeft:=GetTickCount;
DiferenciaTickCount:=MoveTickCountLeft-FTickCountLeft;
FTickCountLeft:=MoveTickCountLeft;
If DiferenciaTickCount=0 Then Left:=FLeft;
(GetComponenteB As TComponenteB).Left:=Left+((GetComponenteB As TComponenteB).Width-1);
end;
FTop:=Top;
FLeft:=Left;
end;
Function TComponenteA.GetComponenteB: TComponent;
var
Padre: TWinControl;
begin
Padre:=Parent;
While Not (Padre is TForm) Do
Padre:=Padre.Parent;
If Padre.FindComponent(FNombreComponenteB)<>Nil Then
Result:=Padre.FindComponent(FNombreComponenteB)
else
Result:=Nil;
end;