Jump to content
bravesofts

Getting an Object Instance Name when ancestor is of TComponent

Recommended Posts

i have this Abstract Base Unit :

unit UBase;

interface

uses
  System.Classes;

type
  TBaseAbstract = class(TComponent) 
    procedure Method(aObjectEvent: TNotifyEvent); virtual; abstract; 
  end;

  TConcrete = class(TBaseAbstract)
    procedure Method(aObjectEvent: TNotifyEvent); override; 
  strict private
    fReadOnlyProperty: string;
  public
    constructor New(aValue: string; aOwner: TComponent);
    destructor Destroy; override;
    property ReadOnlyProperty: string Read fReadOnlyProperty; // Read Only Property ..
  end;


implementation

{ TConcrete }

destructor TConcrete.Destroy;
begin

  inherited;
end;

procedure TConcrete.Method(aObjectEvent: TNotifyEvent);
begin
  aObjectEvent(Self)
end;

constructor TConcrete.New(aValue: string; aOwner: TComponent);
begin
  inherited Create(aOwner);

  fReadOnlyProperty := aValue + Self.ClassName +' ] '+ sLineBreak +
                      'Object Instance Name is: [' + Self.Name +' ]';
end;

end.
Quote

  in this Unit i'm using TCOMPONENT ANCESTOR for Abstracted Base Class in-order to get TComponentName for the Object that will Instantiate from TBaseAbstract Class

in my App Demo Main Form i have this:

var
  FrmMain: TFrmMain;

implementation

uses
  UBase;

var
  fConcrete: TConcrete;
  fBASE: TBaseAbstract; 

{$R *.dfm}

procedure TFrmMain.FormCreate(Sender: TObject);
begin
  fConcrete := TConcrete.New('i''m a Read Only PROPERTY of [ ', Self);
end;

procedure TFrmMain.FormDestroy(Sender: TObject);
begin
  fConcrete.Free;
end;

procedure TFrmMain.Btn_Normal_EventClick(Sender: TObject);
begin
  ShowMessage('i''m an Event of [ ' + Sender.ClassName +' ] '+ sLineBreak +
              ' And My Object Instance Name IS: [ ' + TComponent(Sender).Name +' ]');
end;

procedure TFrmMain.Btn_Call_MethodClick(Sender: TObject);
begin
  fBASE := TConcrete.Create(nil);
  try
    fBASE.Method(Btn_Normal_Event.OnClick);
  finally
    fBASE.Free;
  end;
end;

procedure TFrmMain.Btn_Call_Abstract_MethodClick(Sender: TObject);
begin
  fConcrete.Method(Btn_Normal_Event.OnClick);
end;

procedure TFrmMain.Btn_Get_Concret_PropertyClick(Sender: TObject);
begin
  ShowMessage(fConcrete.ReadOnlyProperty);
end;

end.

Btn_Normal_EventClick work correctlly (i got the name of this button when i click on it [Btn_Normal_Event])

but from fConcrete or fBase whether using Abstract Method or GetProperty the NAME IS EMPTY !!!

------------

do i missing something ?

Edited by bravesofts

Share this post


Link to post

I am unsure what you expect, but in this code

procedure TConcrete.Method(aObjectEvent: TNotifyEvent);
begin
  aObjectEvent(Self)
end;

the parameter Self represents the instance of TConcrete. As long as you don't give a name to that instance like in this code sequence

  fBASE := TConcrete.Create(nil);
  try
    fBASE.Name := 'My Name'; // give fBASE a name
    fBASE.Method(Btn_Normal_Event.OnClick);
  finally
    fBASE.Free;
  end;

the Name property remains empty.

 

But as I already mentioned in the beginning: It is  quite unclear what you expect.

  • Like 1

Share this post


Link to post
Guest
25 minutes ago, bravesofts said:

why ?

The answer is easy, and i assume you are low on caffeine ! 🙂

 

Anyway, try this change to get what you are missing

constructor TConcrete.New(aValue: string; aOwner: TComponent);
begin
  inherited Create(aOwner);

  Name := 'My_default_Name111111';

  fReadOnlyProperty := aValue + Self.ClassName + ' ] ' + sLineBreak +
    'Object Instance Name is: [' + Self.Name + ' ]';

  Name := 'My_default_Name222222';
end;

now you you can see the big picture, right ?

 

as for Btn_Call_MethodClick, it will not change its name and will stay empty coming from its ancestors.

 

Hope that helped.

Share this post


Link to post
Quote

fBASE.Name := 'My Name'; // give fBASE a name

is it fBase the name of that Object fBase ? and fConcrete the name of fConcrete Object

--------

for example the NORMAL Button when user click on it it will give him the TButton as ClassName and the 

Btn_Normal_Event

as Object Name where the Default was Button1 in Delphi

 

Edited by bravesofts

Share this post


Link to post

No, it is the name of the variable that contains the object. The name of a TComponent instance can be different from the names of the variables containing that instance.

  • Like 1

Share this post


Link to post
Guest
5 minutes ago, bravesofts said:

as Object Name where the Default was Button1 in Delphi

That is not default name, that is a name added by the designer of your DFM at design time.

Share this post


Link to post

thank you All for All this Infos ...

-----

Really i'm not just low on caffeine :) i Missing one of the Pillar of delphi language hhh

Edited by bravesofts

Share this post


Link to post

i have another Kinda strange question: (Maybe a Stupid hhh :) ) 

why the components that we create at designtime can call them using their NAME's Rather Call them from their Variables Names that Contains them  ?

in runtime we can do this for ex:

procedure CreateButton(aParent: TWinControl; aBtnEvent: TNotifyEvent);
var
  vButton: TButton;
begin
  vButton := TButton.Create(Self);
  try
    vButton.Parent := aParent; 
    vButton.Name := 'Btn_Test';
    vButton.SetBounds(10, 10, 100, 25);
    vButton.Caption := 'Test';
    vButton.OnClick := aBtnEvent;
  finally
    vButton.Visible := True;
  end;
end;

whereas any Object created at designtime we call them using their Name !!!

--------

what is the secret thing or the missing part that should i know (seriously) to understand the difference between an Object Name at DesignTime & Obj Name RunTime ?

--------

i know Logically that we can't Call any Obj not yet created in designtime from his NAME because it is not reconized for our Project yet (Therefore, we used variables instead), whereas we can call Objects that was created at designTime just from their Names because our Project know them & Recognize them !!(so how did recognize them and use TComponentName which is a string by default as pointer or i don't know as whatever is !!!)

-------

what i mean exactlly:

what happen to any OBJ CREATED AT DESIGNTIME makes delphi & our project call them from thier Names instead of variables Names like the RunTime Scenario does ??

--------

i know that my question is trivial & stupid !!!

but (seriously) i'm lost here (i know that i missing An essential thing that childrens know ....) 

plz clarify to me the Secret behind delphi backstage designtime objects !!!

Notice: All Objects will at the end created Atruntime (so why designtime is a superbox than others !!! (is a designtime = a temporary or pre runtime section or what is the word that can describe this superbox better than designtime ???) )

--------

in another meaning:

why TComponentName of any OBJECT from ObjectInspector IS totally different from TComponentName of Runtime of any Object !!!

they are both a string (TComponentName = type string;) from System.Classes

--------

thanks in advance

 

Edited by bravesofts

Share this post


Link to post

When a TComponent is inserted into another TComponent (like a TButton into a TForm) it tries to connect that component to a published field matching the name of the component. The responsible code part is the SetReference call in TComponent.InsertComponent.

 

That is one reason why the Form Designer adds the component fields as published.

 

Side note: You can even take advantage of this mechanism for your own needs. It is not restricted to the streaming system.

Edited by Uwe Raabe
  • Like 1

Share this post


Link to post
Guest
28 minutes ago, bravesofts said:

why the components that we create at designtime can call them using their NAME's Rather Call them from their Variables Names that Contains them  ?

We are not calling them by name, we never do call any anything by its name, but the form(frame/module) designer is managing them, so when you drop a component on Form/Frame/Module the designer will automatically declare this component as a var in the TForm/TFrame/TModule, and will try to keep its TComponent.Name in sync with that var, this is happening for convenience also it is easier for the designer to manage the component by their names without confusing you (the developer) between these fields vars (declared in TForm) and their names in the designer (dropped component ) , imagine if you drop a button on a form and it called CloseButton in the designer while in the code it is "Button3", also the designer will store each component published property (changed from the default ) in the DFM file.

 

TComponent.Name is there for one thing only which to make a named component unique from its parent point of view, the VCL library designed this Name to make the visual designer more accurate, and to make it possible to for DFM to store properties properly.

 

 

Share this post


Link to post

wow it seems a complicated backstage that the developer like me didn't know or even think about it previously !!!

-------

i need to a respect time to understand exactly what happen inside that designtime backstage !!!

thank you All for this Very valuable information ...

Share this post


Link to post
Guest

Try this

type
  TForm9 = class(TForm)
...
  private   			// could be public or protected, it doesn't matter
    UnNamedBtn: TButton;
    procedure SomeNotifyClick(Sender: TObject);
  public
    { Public declarations }
  end; 
  
  ...
  
procedure TForm9.SomeNotifyClick(Sender: TObject);
begin
  if Sender is TButton then
  begin
    ShowMessage(TButton(Sender).Name + sLineBreak + UnNamedBtn.Name);
    UnNamedBtn.Name := 'Name' + IntToStr(GetTickCount);
  end;
end;

procedure TForm9.FormCreate(Sender: TObject);
begin
  // if you use nil instead of Self, here then we need to free that button or it will leak memory, 
  // by assinging the owenr, the owner will destroy it when it destroy itself, (it will be a child for Self which is this form)
  UnNamedBtn := TButton.Create(Self);   
  UnNamedBtn.Left := 150;
  UnNamedBtn.Top := 150;
  UnNamedBtn.Parent := Self;
  UnNamedBtn.Caption := 'UnNamed one';

  UnNamedBtn.OnClick := SomeNotifyClick;
end;

click that button any number of times and see the name is changing.

Share this post


Link to post
10 hours ago, bravesofts said:

wow it seems a complicated backstage that the developer like me didn't know or even think about it previously !!!

-------

i need to a respect time to understand exactly what happen inside that designtime backstage !!!

thank you All for this Very valuable information ...

When Delphi first came out, seeing how this worked was one of those O-M-G! moments! If you've never programmed the old Windows event loops using Microsoft's crappy old "object framework" based on C++ but not really using objects, it's really hard to appreciate what the inventors did that actually hid all of that garbage. I don't think any of us really learned any of that "complicated backstage" because ... honestly, who cares?  What it did was SIMPLIFY THE HELL out of creating forms in MS Windows apps. Not even VB was that elegant at first, because you had to write VB components in C++ due to the Windows part of that "complicated backstage". Delphi let you write components in ... Delphi! In large part because a TComponent had certain magical properties that let you drop it on a form and refer to everything in it as if it was part of the form. It was really ingenious, and so intuitive that nobody really gave it much thought.

So coming at it the way you are, from the "run-time first", it's not surprising that you're confused.

 

If you're going to do it that way, then study how to write components. That may confuse you even more at first, but at least it will get you going down the right road.

 

That said, your naiveté might lead you to figure out something that the rest of us are blind to because we've been working with this for so long.

 

The biggest annoyance I have is when I want to derive something from a common component, like a TLabel and make a TmyLabel with additional fields and methods. But you can't then inject it into the form easily because the IDE needs all components to be registered and installed, not just declared. I think I've seen some ways of doing it, but they've never stuck in my memory.

 

So while this mechanism was almost miraculous when it was first introduced, it does have its limitations.

  • Like 1

Share this post


Link to post
17 hours ago, Uwe Raabe said:

Side note: You can even take advantage of this mechanism for your own needs. It is not restricted to the streaming system

Out of curiosity (and lack of imagination this morning), what for might this mechanism be good for other than in the Designer ?

  • Like 1

Share this post


Link to post
1 hour ago, M.Joos said:

Out of curiosity (and lack of imagination this morning), what for might this mechanism be good for other than in the Designer ?

Well, I don't have a practical example at hand in the moment and some academic ones would be of no value. Nonetheless, I will try to think of some, but as always there will be other ways to achieve the same goal - whatever that will be.

  • Like 1

Share this post


Link to post

One thing about component names is that you f.x. cannot dynamically add multiple identical frames to a form at runtime unless you ensure a unique name by doing something like this:
 

type
  TOurFrameBase = class(TFrame)
     constructor Create(const AOwner: TWinControl); reintroduce;
  end;

constructor TOurFrameBase.Create(const AOwner: TWinControl);
begin
  inherited Create(TComponent(AOwner));
  FPanel := AOwner;
  Parent := AOwner;
  Align := alClient;
  Name := ClassName + '_' + Seed.Next.ToString; // Ensure that every frame created gets a unique name

I.e. we ensure a unique name for the frame instance. (Seed is a singleton for the class that delivers a unique (incrementing) cardinal (per app life time)).

If you don't do this - you will run into duplicate name errors.

 

  • Like 1

Share this post


Link to post

 

35 minutes ago, Lars Fosdal said:

cannot dynamically add multiple identical frames to a form at runtime unless you ensure a unique name

In case you don't need the name you can also assign it to an empty string. Unnamed components can coexist inside their owner.

  • Like 2

Share this post


Link to post
On 9/26/2021 at 12:29 AM, bravesofts said:

constructor TConcrete.New(aValue: string; aOwner: TComponent);
begin
  inherited Create(aOwner);

  fReadOnlyProperty := aValue + Self.ClassName +' ] '+ sLineBreak +
                      'Object Instance Name is: [' + Self.Name +' ]';
end;

Self.Name will never work in this situation, since it has not been assigned a value yet while the constructor is still running.  The Name property is assigned a value after the constructor exits, whether that be at design-time or run-time.  The only way around that is to pass in the desired value as a parameter to the constructor (but that will obviously not work for objects created at desing-time), eg:

constructor TConcrete.New(aValue: string; aName: string; aOwner: TComponent);
begin
  inherited Create(aOwner);
  Self.Name := aName; // <--

  fReadOnlyProperty := aValue + Self.ClassName +' ] '+ sLineBreak +
                      'Object Instance Name is: [' + aName +' ]';
end;

 

  • Like 1

Share this post


Link to post
16 hours ago, Uwe Raabe said:

In case you don't need the name you can also assign it to an empty string. Unnamed components can coexist inside their owner.

That I did not know. Thank you, Uwe!

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×