6
votes

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?

4

4 Answers

6
votes

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.

5
votes

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.

0
votes

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.

0
votes

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.