我有一个带有密码编辑字段的 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
面板上:TEdit
编辑:我已经在安装在另一台 PC 上的 Delphi 11 CE 上尝试过此操作,没有扩展/插件/第三方控件等,结果相同。
感谢@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 消息解决此问题的解决方案效果很好,另一个可能有效的解决方案是暂时取消事件的挂钩,更改属性,然后重新附加事件。