views:

111

answers:

3

I have an TUpDown control whose Associate is set to an instance of a TEdit subclass. The edit class calls RecreateWnd in its overriden DoEnter method. Unfortunately this kills the buddy connection at the API level which leads to strange behavior e.g. when clicking on the updown arrows.

My problem is that the edit instance doesn't know that it is the buddy of some updown to which it should reconnect and the updown isn't notified of the loss of its buddy. Any ideas how I could reconnect the two?

A: 

Try storing the value of the Associate property in a local variable before you call RecreateWnd, then setting it back afterwards.

Mason Wheeler
The problem is that the updown knows its buddy but not vice versa and the RecreateWnd is issued by the buddy.
Ulrich Gerhardt
+2  A: 

I noticed how TCustomUpDown.SetAssociate checks that updown and buddy have the same parent and uses this to avoid duplicate associations. So I tried calling my own RecreateWnd method:

procedure TAlignedEdit.RecreateWnd;
var
  i: Integer;
  c: TControl;
  ud: TCustomUpDown;
begin
  ud := nil;
  for i := 0 to Pred(Parent.ControlCount) do
  begin
    c := Parent.Controls[i];
    if c is TCustomUpDown then
      if THACK_CustomUpDown(c).Associate = Self then
      begin
        ud := TCustomUpDown(c);
        Break;
      end;
  end;
  inherited RecreateWnd;
  if Assigned(ud) then
  begin
    THACK_CustomUpDown(ud).Associate := nil;
    THACK_CustomUpDown(ud).Associate := Self;
  end;
end;

et voila - it works!

Ulrich Gerhardt
+1  A: 

You've discovered something rather unfortunate. You set up an association between two controls at the application level, so you should be able to continue to manage that association in application-level code, but the VCL doesn't provide the framework necessary for maintaining that. Ideally, there would be a generic association framework, so associated controls could notify each other that they should update themselves.

The VCL has the beginnings of that, with the Notification method, but that only notifies of components being destroyed.

I think your proposed solution is a little too specific to the task. An edit control shouldn't necessarily know that it's attached to an up-down control, and even if it does, they shouldn't be required to share a parent. On the other hand, writing an entire generic observer framework for this problem would be overkill. I propose a compromise.

Start with a new event property on the edit control:

property OnRecreateWnd: TNotifyEvent read FOnRecreateWnd write FOnRecreateWnd;

Then override RecreateWnd as you did above, but instead of all the up-down-control-specific code, simply trigger the event:

procedure TAlignedEdit.RecreateWnd;
begin
  inherited;
  if Assigned(OnRecreateWnd) then
    OnRecreateWnd(Self);
end;

Now, handle that event in your application code, where you know exactly which controls are associated with each other, so you don't have to search for anything, and you don't need to require any parent-child relationships:

procedure TUlrichForm.AlignedEdit1RecreateWnd(Sender: TObject);
begin
  Assert(Sender = AlignedEdit1);
  UpDown1.Associate := nil;
  UpDown1.Associate := AlignedEdit1;
end;
Rob Kennedy
The fact that they have to share the parent is already imposed by the VCL, so this shouldn't be a problem. But your solution just feels cleaner, so'll try it tomorrow.
Ulrich Gerhardt