Delphi VCL:Panel上TEdit切换PasswordChar触发OnEnter/OnExit异常
问题分析:TPanel上的TEdit切换PasswordChar触发OnEnter/OnExit,Form上的不触发
问题场景
开发Delphi 11/12 VCL应用时,遇到以下差异行为:
- 当
TEdit设置PasswordChar='*'且父容器为TPanel时,点击“显示密码”按钮切换PasswordChar,会触发该TEdit的OnEnter和OnExit事件。 - 当
TEdit直接放在Form上时,同样操作不会触发这两个焦点事件。
测试代码
DFM代码
object Form2: TForm2 Left = 0 Top = 0 Caption = 'Form2' ClientHeight = 279 ClientWidth = 469 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -12 Font.Name = 'Segoe UI' Font.Style = [] TextHeight = 15 object SpeedButton1: TSpeedButton Left = 142 Top = 20 Width = 23 Height = 22 OnClick = SpeedButtonClick end object Memo1: TMemo Left = 195 Top = 71 Width = 266 Height = 200 ScrollBars = ssBoth TabOrder = 0 end object Panel1: TPanel Left = 4 Top = 71 Width = 185 Height = 66 TabOrder = 1 object OnPanelButton: TSpeedButton Left = 138 Top = 17 Width = 23 Height = 22 OnClick = SpeedButtonClick end object OnPanel: TEdit Left = 11 Top = 13 Width = 121 Height = 23 PasswordChar = '*' TabOrder = 0 Text = 'OnPanel' OnEnter = InputOnEnter OnExit = InputOnExit end end object OffPanel: TEdit Left = 15 Top = 24 Width = 121 Height = 23 PasswordChar = '*' TabOrder = 2 Text = 'OffPanel' OnEnter = InputOnEnter OnExit = InputOnExit end end
PAS代码
unit TestForm; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Buttons, Vcl.ExtCtrls; type TForm2 = class(TForm) Memo1: TMemo; Panel1: TPanel; OnPanel: TEdit; OffPanel: TEdit; SpeedButton1: TSpeedButton; OnPanelButton: TSpeedButton; procedure SpeedButtonClick(Sender: TObject); procedure InputOnEnter(Sender: TObject); procedure InputOnExit(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form2: TForm2; implementation {$R *.dfm} procedure TForm2.InputOnEnter(Sender: TObject); begin Memo1.Lines.Add((Sender as TEdit).Name + '.OnEnter'); end; procedure TForm2.InputOnExit(Sender: TObject); begin Memo1.Lines.Add((Sender as TEdit).Name + '.OnExit'); end; procedure TForm2.SpeedButtonClick(Sender: TObject); var edt: TEdit; begin if Sender = OnPanelButton then edt := OnPanel else edt := OffPanel; Memo1.Lines.Add('Swapping PasswordChar on ' + edt.Name); if edt.PasswordChar = '*' then edt.PasswordChar := #0 else edt.PasswordChar := '*' end; end.
测试输出
OffPanel(直接在Form上):
- OffPanel.OnEnter
- [点击按钮]
- Swapping PasswordChar on OffPanel
OnPanel(在TPanel上):
- OnPanel.OnEnter
- [点击按钮]
- Swapping PasswordChar on OnPanel
- OnPanel.OnExit
- OnPanel.OnEnter
原因解析
这是VCL内部控件焦点与窗口重创建的逻辑差异导致的:
- 修改
TEdit的PasswordChar时,VCL会根据控件父容器的特性,决定是否重新创建内部的编辑窗口(HWND)。 TPanel是具备焦点接收能力的容器(默认设置TabOrder后拥有WS_TABSTOP属性),修改其内部TEdit的PasswordChar时,VCL会先让TEdit失去焦点(触发OnExit),销毁原编辑窗口并重新创建后,再将焦点返还给TEdit(触发OnEnter)。- Form作为顶级窗口,其内部控件的窗口重创建逻辑更简化,修改
PasswordChar时无需销毁原窗口,因此不会触发焦点变更事件。
解决方案
要消除这种行为差异,避免不必要的OnEnter/OnExit触发,可以临时禁用这两个事件,修改PasswordChar后再恢复:
procedure TForm2.SpeedButtonClick(Sender: TObject); var edt: TEdit; OldEnterEvent: TNotifyEvent; OldExitEvent: TNotifyEvent; begin if Sender = OnPanelButton then edt := OnPanel else edt := OffPanel; Memo1.Lines.Add('Swapping PasswordChar on ' + edt.Name); // 保存原有事件并临时禁用 OldEnterEvent := edt.OnEnter; OldExitEvent := edt.OnExit; edt.OnEnter := nil; edt.OnExit := nil; // 切换密码显示状态 if edt.PasswordChar = '*' then edt.PasswordChar := #0 else edt.PasswordChar := '*'; // 恢复原有事件 edt.OnEnter := OldEnterEvent; edt.OnExit := OldExitEvent; end;
此方法能让两种父容器下的TEdit行为保持一致,不会触发额外的焦点事件。
内容的提问来源于stack exchange,提问作者Re0sless
相关产品推荐
相关产品推荐

