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

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内部控件焦点与窗口重创建的逻辑差异导致的:

  1. 修改TEdit的PasswordChar时,VCL会根据控件父容器的特性,决定是否重新创建内部的编辑窗口(HWND)。
  2. TPanel是具备焦点接收能力的容器(默认设置TabOrder后拥有WS_TABSTOP属性),修改其内部TEdit的PasswordChar时,VCL会先让TEdit失去焦点(触发OnExit),销毁原编辑窗口并重新创建后,再将焦点返还给TEdit(触发OnEnter)。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 23:57:37