Getting the Component Name in the Constructor?
Asked Answered
L

4

6

I am creating a custom control derived from TCustomControl, for example:

type
  TMyCustomControl = class(TCustomControl)
private
  FText: string;
  procedure SetText(const Value: string);
protected
  procedure Paint; override;
public
  constructor Create(AOwner: TComponent); override;
  destructor Destroy; override;
published
  property Text: string read FText write SetText;
end;

Note, the above is incomplete for purpose of the example to keep it short and simple.

Anyway, in my control I have a Paint event which displays text (from FText field) using Canvas.TextOut.

When my component is added to the Delphi Form Designer (before any user changes can be made to the component) I want the TextOut to display the name of the Component - TButton, TCheckBox, TPanel etc are examples of this with their caption property.

If I try to assign the name of my Component to FText in the constructor it returns empty, eg '';

constructor TMyCustomControl.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FText  := Name; //< empty string
  ShowMessage(Name); //< empty message box too
end;

If I change FText := Name to FText := 'Name'; it does output the text to my Component so I do know it is not a problem within the actual code, but obviously this outputs 'Name' and not the actual Component name like MyCustomControl1, MyCustomControl2 etc.

So my question is, how can you get the name of your Component from its constructor event?

Lavonna answered 28/1, 2013 at 17:34 Comment(0)
A
6

The Name property has not been assigned yet when the constructor is running. At design-time, the IDE assigns a value to the Name property after the component has been dropped onto the Designer, after the control's constructor has exited. At runtime, the Name property is set by the DFM streaming system instead, which is also invoked after the constructor has exited.

Either way, the TControl.SetName() property setter validates the new value, and then sets the new value to the control's Text property to match if the current Text value matches the old Name value and the control's ControlStyle property includes the csSetCaption flag (which it does by default). When the Text property changes for any reason, the control automatically sends itself a CM_TEXTCHANGED notification. You can have your control catch that message and call Invalidate() on itself to trigger a new repaint. Inside of your Paint() handler, simply draw the current Name as-is, whatever value it happens to be. If it is blank, so be it. Don't try to force the Name, let the VCL handle it for you normally.

Antiphony answered 28/1, 2013 at 17:46 Comment(3)
I intercepted the CM_TEXTCHANGED and called Invalidate, I changed my TextOut code to output Name instead of FText and it does output the name correctly now. Only problem is it is not positioning my caption correctly like it did before but I can see if I can fix this little problem. Thanks for the explanation in your answer, if I think about it now how can a component be assigned a name from the constructor!Lavonna
@Blobby, off topic note about text positioning. Make sure you're calculating position for the same text as you're rendering (if you're not using method like TextRect).Mirabella
@Mirabella you were right I noticed soon after posting I was still calculating the TextHeight based off FText not the Name. Glad you posted anyway in case I missed it ;)Lavonna
B
5

I believe the proper way to handle this is to use the inherited Text or Caption property of TCustomControl, and to make sure that the csSetCaption ControlStyle is set.

Beezer answered 28/1, 2013 at 17:40 Comment(0)
A
0

To apply the name you may override TComponent.Loaded method.

But i don't think You should copy Name to Text. Those are semantically separate properties and adding unexpected binding to them would hurt you some day.

Rather WMPaint method should check if the Text is empty and then render Name then, but the very property of Text should not be changed.

procedure TMyComponent.WMPaint; message WM_Paint; var InternalCaption: string;
begin
....
   InternalCaption := Self.Text;
   If InternalCaption = '' then InternalCaption := Self.Name;
   If InternalCaption = '' then InternalCaption := Self.ClassName;
....
   Self.Canvas.OutText(InternalCaption);

If anything - you should keep properties separated just for the simple reason that Name := 'AAA'; Name := 'BBB'; should not make Text and name out of sync. And with your approach 1st statement would settle the Text and the second would make old Name still displayed after the actual name changed.

Aiken answered 29/1, 2013 at 8:15 Comment(2)
Text and Name ought to be out of sync, just not when dropping a new instance on a form. Obviously, OP is simply searching for the default csSetCaption behaviour which takes care.Shep
@Shep They ought to be out of sync whether developer intentionally overrode the value of Text but until that they should be in sync. Just as it goes with stock VCL components, like TLabel, TEdit, TPanel, etc.Tarpaulin
E
0

Un easy way is to override the method SetName:

TMyCaptionString = type of WideString;

TMyLabel = class(TCustomControl)
private
  FCaption: TMyCaptionString;
  FCaptionAsName: Boolean;
  procedure SetCaption(Value: TMyCaptionString);
protected
  procedure SetName(const NewName: TComponentName); override;
public
  constructor Create(AOwner: TComponent); override;
  property Caption: TMyCaptionString read FCaption write SetCaption;
end;

implementation

constructor TMyLabel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlStyle := ControlStyle + [csOpaque, csReplicatable,csSetCaption];
  FCaptionAsName := (csDesigning in ComponentState) and not (csReadingState in ControlState);
  ...
end;

procedure TMyLabel.SetName(const NewName: TComponentName);
begin
  if FCaptionAsName then
  begin
    FCaptionAsName := FCaption = Name;
    FCaption := NewName;
    invalidate;
  end;
  inherited SetName(NewName);
end;

procedure TMyLabel.SetCaption(Value: TMyCaptionString);
begin
  if FCaption <> Value then
  begin
    FCaption := Value;
    Invalidate;
    FCaptionAsName := False;
  end;
end;

I needed my own variable for the Caption poreprty, because I want to use widestring instead unicode and to write a custom Property editor. Sorry that i'm writing in old topic, but i hope this will helpfull.

Endlong answered 28/9, 2015 at 10:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.