Re: Getting component's name from notification parameter?

From: Maarten Wiltink (maarten_at_kittensandcats.net)
Date: 01/31/05


Date: Mon, 31 Jan 2005 12:35:28 +0100


"Raptor" <bogus@none.com> wrote in message
news:ctjp3j013h7@news2.newsguy.com...
> Maarten Wiltink <maarten@kittensandcats.net> wrote in message
> news:41fd3812$0$28977$e4fe514c@news.xs4all.nl...
[...]
>>> if assigned(aWCMessage) then aWCMessage.Free;
>>
>> Does Freeing it also clear the reference? Watch your step.
>
> Not sure what you're suggesting-- free and nil?

You're using the fact that aWCMessage is not nil to deduce that it
contains a reference to a valid object. Then you free the object,
invalidating it and the reference, but you do not clear the reference.
You can only do that once.

If you're inferring things from pointers, you should keep them up
to date. Unless you can guarantee that you'll never use that pointer
again, you can't free the object and leave the pointer standing.

[...]
> I made [TWCMessage] a freestanding type, but have wondered if there
> was any advantage to incorporating it as a field. See next comment.

You had to make it a freestanding type because Delphi doesn't allow
inner classes. But IMO you didn't need the reference to the temporary
object, and in fact the way you use it is dangerous.

>> You can make the windowed control refer to the translation component
>> instead of the other way; that would minimise the impact of the
>> whole scheme on the permanent part. By placing the reference in the
>> inner component, it automatically goes away once the component has
>> served its purpose.
>
> The translate component will persist, as languages can be changed by
> user while the app runs. This windowed thing, on the other hand, is
> only a gimmick to send a message, and can be discarded when the message
> is received. I can't see any need to let the window hang around for the
> duration. At least I think that's what you're getting at.

No. I may have worded it a little murkily last time, but the gist of it
is that the outer object has no need to know about the inner object.

But it mixed with the fact that the knowledge that you programmed into
the outer object is handled in a dangerous way. The inner object has no
problems with invalid references because it self-destructs; anyway its
pointers don't go stale. But the pointer to the inner object does, and
it should be cleared when it does. You have the good luck (as usual it's
not luck but design) that you know _when_ the pointer goes stale and can
throw it away right then.

[...]
> But I'm still not getting how to generically locate the parent it
> demands at startup. This compiled and looked syntactically promising:
>
> constructor TWCMessage.Create(aOwner: TComponent);
> begin
> inherited Create(aOwner);
> Self.Parent := Application.MainForm;
>
> but still demands a parent window on startup. What's with that, I
> wonder. Maybe the MainForm is assigned later in the startup process.

MainForm, it turns out, is assigned _after_ the form is fully loaded.
So that code only works when your TTranslator is on a secondary form,
and then it will use a different form for its Parent. It doesn't seem
very elegant.

[...]
> I'll look at that "give the outer component a Parent property" notion.
> But that I assume that my TTranslate would become the parent for my
> TWCMessage (which I've renamed to TAppLoadedMsg). That sounds vaguely
> promising.

Not to me. TTranslate is a TComponent, not a TWinControl. So it can't
be a Parent. That was the whole problem.

For the property, I'd start with the Owner because it that is a
TWinControl you're out of the woods. If the component user wants to use
a different parent you can let him. That means you should delay creation
of the inner component until the outer component is fully Loaded, or
the Parent property will not have been set yet.

There is a slight problem with creating a TTranslate at run-time (with
a nil Owner) and setting its Parent in your own code. You could require
people to call TTranslate.Loaded after that but it seems friendlier to
propagate the Parent to the TWCMessage when set on the TTranslate. This
does require keeping a reference in the outer object.

That makes the code (untested):

type
  TCustomTranslate = class(TComponent)
  private
    FDelayLine: TWCMessage;
    FParent: TWinControl;

    procedure SetParent(const Value: TWinControl);
  protected
    property DelayLine: TWCMessage read FDelayLine write FDelayLine;
    property Parent: TWinControl read FParent write SetParent;

    procedure DoParentChanged; virtual;
    procedure DoDelayExpired; virtual;

    procedure Loaded; override;
  public
    constructor Create(Owner: TComponent); override;
    destructor Destroy; override;
  end;

  TTranslate = class(TCustomTranslate)
    property Parent;
  end;

  TWCMessage = class(TWinControl)
  private
    FTarget: TCustomTranslate;
  protected
    property Target: TCustomTranslate read FTarget write FTarget;

    procedure SetParent(Value: TWinControl); override;

    procedure DoParentChanged; virtual;

    procedure UMDelayExpired(var Msg: TMessage); message UM_DELAYEXPIRED;
  public
    constructor Create(Owner: TComponent); override;

    procedure LightFuse;
  end;

constructor TCustomTranslate.Create(Owner: TComponent);
begin
  FreeAndNil(FDelayLine);
  FParent:=nil;

  inherited Create(Owner);

  if (Owner is TWinControl)
  then Parent:=Owner as TWinControl;
end;

destructor TCustomTranslate.Destroy;
begin
  FreeAndNil(FDelayLine);

  inherited Destroy;
end;

procedure TCustomTranslate.SetParent(const Value: TWinControl);
begin
  if not (Parent=Value)
  then begin
    FParent:=Value;
    DoParentChanged;
  end;
end;

procedure TCustomTranslate.DoParentChanged;
begin
  if (Assigned(DelayLine))
  then DelayLine.Parent:=Parent;
end;

procedure TCustomTranslate.Loaded;
begin
  if not (Assigned(DelayLine))
  then begin
    DelayLine:=TWCMessage.Create(nil);
    DelayLine.Parent:=Self.Parent;
  end;
end;

constructor TWCMessage.Create(Owner: TComponent);
begin
  FTarget:=nil;

  inherited Create(Owner);

  if (Owner is TCustomTranslate)
  then Target:=Owner as TCustomTranslate;
end;

procedure TWCMessage.SetParent(Value: TWinControl);
begin
  if not (Parent=Value)
  then begin
    inherited Parent:=Value;
    DoParentChanged;
  end;
end;

procedure TWCMessage.DoParentChanged;
begin
  LightFuse;
end;

procedure TWCMessage.UMDelayExpired(var Msg: TMessage);
begin
  if (Assigned(Target))
  then begin
    Target.DelayLine:=nil;
    Target.DoDelayExpired;
  end;

  Self.Free;
end;

procedure TWCMessage.LightFuse;
begin
  PostMessage(Self.Handle, UM_DELAYEXPIRED);
end;

This is only one implementation of the exact relationship between inner
and outer object. Here, the inner object de-registers itself with the
outer one and frees itself. Both functions might be placed in either
object.

There are _many_ significant but tiny details in this code. Whether a
reference is FreeAndNil'ed or merely cleared, and whether a property or
its backing field is used are the ones most so.

Groetjes,
Maarten Wiltink



Relevant Pages

  • Re: owner of class
    ... >> terminate a given object but because I've created circular reference, ... > Public Sub AttachParent ... > Then each class you need to know it's parent object can simply implement ... > Private Sub IParentTrack_AttachParent ...
    (microsoft.public.vb.general.discussion)
  • Re: Can objects be passed around as in Java?
    ... > Each oMACdefn contains oFUNCTdefn objects of class clsFUNCTdefn5A. ... > main line doesn't seem to pass me a good wsWorksheet, ... Public) object variable to retain the reference. ... Public Property Get Parent() As clsMACdefn5A ...
    (microsoft.public.excel.programming)
  • Re: Re: BUG in: Driver core: convert block from raw kobjects to core devices (fwd)
    ... "drop reference to parent kobject at remove time instead of release ... with how it deals with the driver model, but I need to take the time to ... but as other block device drivers create the ... parent until it is released. ...
    (Linux-Kernel)
  • Re: closing DB connections
    ... subject to the circular reference problem, since it IS based on reference ... where the parent has a child collection, and the children have a parent ... You're right that many people don't think of cleaning up strings, ... Dim rs1 As Object ...
    (microsoft.public.access.adp.sqlserver)
  • Re: Inheritance Collection Classes
    ... parent class other than passing it to the child. ... > have a reference to the Customer class; ... create your class Emails with a constructor which take the ... >> of collection classes each passing a reference to the parent object down ...
    (microsoft.public.dotnet.languages.vb)