如何让Delphi复合控件在子控件获焦时绘制焦点标识边框?
实现Delphi复合控件在子控件获焦时显示系统风格焦点边框
核心思路
通过跟踪复合控件内部的焦点状态(自身或子控件获焦),利用Windows主题API绘制与TEdit一致的焦点边框,无需修改子控件的核心实现,工作量较小。
具体实现步骤
自定义复合控件基类
继承自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;初始化与事件绑定
在构造函数和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;跟踪焦点状态
通过子控件的焦点事件和自身的焦点消息,更新焦点状态并重绘: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;绘制焦点边框
利用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;使用说明
- 将现有复合控件改为继承自
TMyCompositeControl - 确保子控件的
BorderStyle设为bsNone,避免自身显示焦点边框 - 若复合控件有自定义背景绘制,需调整
WMPaint中的绘制顺序,避免边框被覆盖
- 将现有复合控件改为继承自
关键说明
- 利用主题API绘制的边框完全匹配系统TEdit的样式(Windows 10的蓝色细边框、Windows 11的圆角边框等)
- 无需修改子控件代码,仅需通过父控件统一处理焦点状态
- 兼容启用/禁用Runtime Themes的场景
内容的提问来源于stack exchange,提问作者William Tucker
相关产品推荐
相关产品推荐

