【问题标题】:Getting the Component Name in the Constructor?在构造函数中获取组件名称?
【发布时间】:2013-01-12 03:09:58
【问题描述】:

我正在创建一个从 TCustomControl 派生的自定义控件,例如:

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;

请注意,以上内容是不完整的,只是为了让示例简洁明了。

无论如何,在我的控制中,我有一个 Paint 事件,它使用 Canvas.TextOut 显示文本(来自 FText 字段)。

当我的组件被添加到 Delphi 表单设计器时(在用户可以对组件进行任何更改之前)我希望 TextOut 显示组件的名称 - TButton、TCheckBox、TPanel 等是带有标题的示例属性。

如果我尝试在构造函数中将我的组件的名称分配给 FText,它会返回空,例如 '';

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

如果我将FText := Name 更改为FText := 'Name';,它会将文本输出到我的组件,所以我知道这在实际代码中不是问题,但显然这会输出“名称”而不是像 MyCustomControl1 这样的实际组件名称, MyCustomControl2 等。

所以我的问题是,如何从其构造函数事件中获取组件的名称?

【问题讨论】:

    标签: delphi custom-component


    【解决方案1】:

    我认为处理此问题的正确方法是使用 TCustomControl 的继承的 TextCaption 属性,并确保设置了 csSetCaption ControlStyle

    【讨论】:

      【解决方案2】:

      构造函数运行时,Name 属性尚未分配。在设计时,在将组件拖放到设计器上、退出控件的构造函数之后,IDE 会为Name 属性分配一个值。在运行时,Name 属性由 DFM 流系统设置,它也在构造函数退出后调用。

      无论哪种方式,TControl.SetName() 属性设置器都会验证新值,然后将新值设置为控件的Text 属性以匹配当前Text 值是否与旧Name 值和控件的@ 匹配987654328@ 属性包括 csSetCaption 标志(默认情况下)。当Text 属性因任何原因发生更改时,控件会自动向自身发送CM_TEXTCHANGED 通知。您可以让您的控件捕获该消息并自行调用Invalidate() 以触发新的重绘。在您的Paint() 处理程序中,只需按原样绘制当前的Name,无论它是什么值。如果它是空白的,那就这样吧。不要强求Name,让VCL正常为你处理。

      【讨论】:

      • 我截获了 CM_TEXTCHANGED 并调用了 Invalidate,我将 TextOut 代码更改为输出 Name 而不是 FText,它现在确实正确输出了名称。唯一的问题是它没有像以前那样正确定位我的标题,但我可以看看我是否可以解决这个小问题。感谢您在回答中的解释,如果我现在考虑一下,如何从构造函数中为组件分配名称!
      • @Blobby,关于文本定位的题外话。确保您正在计算与渲染相同的文本的位置(如果您没有使用像 TextRect 这样的方法)。
      • @TLama 你说得对,我在发布后不久就注意到我仍在根据 FText 而不是名称计算 TextHeight。很高兴你发布了,以防我错过它;)
      【解决方案3】:

      要应用名称,您可以覆盖 TComponent.Loaded 方法。

      但我认为您不应该将名称复制到文本。这些是语义上独立的属性,向它们添加意想不到的绑定总有一天会伤害到你。

      WMPaint 方法应该检查 Text 是否为空,然后渲染 Name,但 Text 的属性不应该改变。

      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);
      

      如果有的话 - 您应该将属性分开,原因很简单,Name := 'AAA'; Name := 'BBB'; 不应使文本和名称不同步。使用您的方法,第一个语句将解决文本,第二个语句将在实际名称更改后仍显示旧名称。

      【讨论】:

      • TextName 应该不同步,而不是在将新实例放到表单上时。显然,OP 只是在搜索默认的 csSetCaption 行为。
      • @NGLN 他们应该不同步无论开发人员是否故意覆盖 Text 的值,但直到他们应该是同步的。就像 TLabel、TEdit、TPanel 等库存 VCL 组件一样。
      【解决方案4】:

      不简单的方法是覆盖方法 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;
      

      我需要自己的 Caption poreprty 变量,因为我想使用宽字符串而不是 unicode 并编写自定义属性编辑器。对不起,我写的是老话题,但我希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-02-23
        • 1970-01-01
        • 1970-01-01
        • 2023-03-17
        • 1970-01-01
        • 2021-07-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多