You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Delphi复合控件在子控件获焦时绘制焦点标识边框?

实现Delphi复合控件在子控件获焦时显示系统风格焦点边框

核心思路

通过跟踪复合控件内部的焦点状态(自身或子控件获焦),利用Windows主题API绘制与TEdit一致的焦点边框,无需修改子控件的核心实现,工作量较小。

具体实现步骤

  1. 自定义复合控件基类
    继承自TCustomControl(支持自行绘制的控件基类),添加焦点状态跟踪和主题句柄字段:

    type
      TMyCompositeControl = class(TCustomControl)
      private
        FHasFocus: Boolean;
        FTheme: HTHEME;
        procedure ChildEnter(Sender: TObject);
        procedure ChildExit(Sender: TObject);
        procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
        procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
        procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
        procedure WMThemeChanged(var Message: TMessage); message WM_THEMECHANGED;
      protected
        procedure CreateParams(var Params: TCreateParams); override;
        procedure Loaded; override;
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
      end;
    
  2. 初始化与事件绑定
    在构造函数和Loaded方法中,给所有子控件绑定焦点变更事件,同时初始化主题相关字段:

    constructor TMyCompositeControl.Create(AOwner: TComponent);
    begin
      inherited;
      FHasFocus := False;
      FTheme := 0;
      BorderStyle := bsNone;
      Padding.SetBounds(1, 1, 1, 1); // 预留边框空间,避免子控件被遮挡
    end;
    
    procedure TMyCompositeControl.Loaded;
    var
      I: Integer;
    begin
      inherited;
      // 给所有子控件绑定Enter/Exit事件
      for I := 0 to ControlCount - 1 do
      begin
        Controls[I].OnEnter := ChildEnter;
        Controls[I].OnExit := ChildExit;
      end;
    end;
    
    destructor TMyCompositeControl.Destroy;
    begin
      if FTheme <> 0 then
        CloseThemeData(FTheme);
      inherited;
    end;
    
  3. 跟踪焦点状态
    通过子控件的焦点事件和自身的焦点消息,更新焦点状态并重绘:

    procedure TMyCompositeControl.ChildEnter(Sender: TObject);
    begin
      FHasFocus := True;
      Invalidate;
    end;
    
    procedure TMyCompositeControl.ChildExit(Sender: TObject);
    var
      I: Integer;
    begin
      FHasFocus := False;
      // 检查是否还有其他子控件保持焦点
      for I := 0 to ControlCount - 1 do
        if Controls[I].Focused then
        begin
          FHasFocus := True;
          Break;
        end;
      Invalidate;
    end;
    
    procedure TMyCompositeControl.WMSetFocus(var Message: TWMSetFocus);
    begin
      inherited;
      FHasFocus := True;
      Invalidate;
    end;
    
    procedure TMyCompositeControl.WMKillFocus(var Message: TWMKillFocus);
    begin
      inherited;
      // 失焦时检查子控件是否仍有焦点
      FHasFocus := False;
      for var I := 0 to ControlCount - 1 do
        if Controls[I].Focused then
        begin
          FHasFocus := True;
          Break;
        end;
      Invalidate;
    end;
    
  4. 绘制焦点边框
    利用Windows主题API绘制与系统TEdit一致的焦点边框,兼容无主题环境:

    procedure TMyCompositeControl.WMPaint(var Message: TWMPaint);
    var
      PaintDC: HDC;
      PS: TPaintStruct;
      ClientR: TRect;
    begin
      PaintDC := BeginPaint(Handle, PS);
      try
        // 先绘制控件原有内容(子控件会自行绘制)
        inherited;
    
        // 有焦点时绘制边框
        if FHasFocus then
        begin
          ClientR := ClientRect;
          if ThemeServices.ThemesEnabled then
          begin
            if FTheme = 0 then
              FTheme := OpenThemeData(Handle, 'EDIT');
            if FTheme <> 0 then
            begin
              // 绘制EDIT控件的焦点状态边框
              DrawThemeBackground(FTheme, PaintDC, EDITS_FOCUSED, 0, ClientR, nil);
            end;
          end
          else
          begin
            // 无主题时绘制标准焦点矩形
            InflateRect(ClientR, -1, -1);
            DrawFocusRect(PaintDC, ClientR);
          end;
        end;
      finally
        EndPaint(Handle, PS);
      end;
    end;
    
    procedure TMyCompositeControl.WMThemeChanged(var Message: TMessage);
    begin
      inherited;
      // 主题变更时重新加载主题句柄
      if FTheme <> 0 then
      begin
        CloseThemeData(FTheme);
        FTheme := 0;
      end;
      Invalidate;
    end;
    
    procedure TMyCompositeControl.CreateParams(var Params: TCreateParams);
    begin
      inherited;
      // 设置控件样式,确保能正确接收焦点和绘制
      Params.Style := Params.Style or WS_TABSTOP;
    end;
    
  5. 使用说明

    • 将现有复合控件改为继承自TMyCompositeControl
    • 确保子控件的BorderStyle设为bsNone,避免自身显示焦点边框
    • 若复合控件有自定义背景绘制,需调整WMPaint中的绘制顺序,避免边框被覆盖

关键说明

  • 利用主题API绘制的边框完全匹配系统TEdit的样式(Windows 10的蓝色细边框、Windows 11的圆角边框等)
  • 无需修改子控件代码,仅需通过父控件统一处理焦点状态
  • 兼容启用/禁用Runtime Themes的场景

内容的提问来源于stack exchange,提问作者William Tucker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 04:56:04