Delphi PasswordChar 触发 OnExit / OnEnter,但仅在面板上

问题描述 投票:0回答:1

我有一个带有密码编辑字段的 Delphi 12.1 VCL 应用程序(TEdit,PasswordChar 设置为 *)

我添加了一个“显示密码”TSpeedButton,但是当密码输入具有焦点并且我使用该按钮切换PasswordChar时,OnEnter和OnExit事件会触发,但仅当TEdit位于面板上时才会触发。如果 TEdit 位于表单上,则事件不会触发。

如果编辑是在面板上而不是在表单上,为什么会触发事件?我希望无论父母如何,他们都会表现得一样?

示例程序

测试表格.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.

相同输出

关闭面板:TEdit

  • OffPanel.OnEnter
  • [此处按下按钮]
  • 在 OffPanel 上交换密码字符

面板上:TEdit

  • OnPanel.OnEnter
  • [此处按下按钮]
  • 在 OnPanel 上交换密码字符
  • OnPanel.OnExit
  • OnPanel.OnEnter

编辑:我已经在安装在另一台 PC 上的 Delphi 11 CE 上尝试过此操作,没有扩展/插件/第三方控件等,结果相同。

delphi vcl delphi-12-athens
1个回答
0
投票

感谢@remy-lebeau 为我指明了正确的方向,我才能够弄清楚发生了什么。我会将我的发现放在下面,供其他遇到相同问题的人使用。

这只影响 Delphi 11/12,但不影响 Delphi 2007,我不确定中间的版本。

PasswordChar
更改时,Delphi 会调用
RecreateWnd
,还有其他属性在更改时也会调用 RecreateWnd。

RecreateWnd
CMRecreateWnd
发送消息,这实际上会销毁并重新创建控件,首先检查它是否已聚焦,以便在重新创建后可以恢复焦点。

当控件被销毁时,Delphi 会尝试使用

function TCustomForm.SetFocusedControl(Control: TWinControl): Boolean;
函数为父控件提供焦点。

如果

SetFocusedControl
找到一个控件来为其提供焦点,则会触发已销毁控件的 OnExit 事件,并将 Screen.ActiveControl 设置为父控件。

重新创建控件后,焦点将返回到该控件并触发 OnEnter。

因此,在我的例子中,TEdit 框被破坏,焦点暂时交给父 TPanel。

那么为什么只有当控件位于容器上而不是直接放置在 TForm 上时才会发生这种情况?

SetFocusedControl
中的第一行代码看起来像这样

if Control <> Self then
      FActiveControl := Control else
      FActiveControl := nil;

我不是 100% 确定为什么,但是如果控件被给予焦点是一个表单,这将阻止事件被触发。

Remy 使用 Windows 消息解决此问题的解决方案效果很好,另一个可能有效的解决方案是暂时取消事件的挂钩,更改属性,然后重新附加事件。

© www.soinside.com 2019 - 2024. All rights reserved.