您无法将 VCL-Control 行为与 FMX-Control 行为进行比较,因为有时它们的行为不同 - 它们不应该,但它们确实如此。
在 VCL 中,您有一个 OnExit 事件,它在焦点离开控件后立即发生。所以这是一个OnAfterExit 事件。
在 FMX 中,OnExit 事件在焦点消失之前触发。所以这是OnBeforeExit。
procedure TControl.DoExit;
begin
if FIsFocused then
begin
try
if CanFocus and Assigned(FOnExit) then
FOnExit(Self);
FIsFocused := False;
现在,这与您当前的问题有什么关系?
如果您将焦点设置到OnExit 事件中的另一个控件,则会调用当前活动控件DoExit 方法,该方法调用OnExit 事件,并且您有一个完美的圆圈。
所以你有几个选项来解决这个问题
错误报告
最佳解决方案是创建一个错误报告并让 emba 修复此问题。
已经有一个错误报告117752 具有相同的原因。所以我将解决方案作为评论发布。
补丁FMX.Controls.pas
将FMX.Controls复制到您的项目源目录并修补错误代码(仅一行)
procedure TControl.DoExit;
begin
if FIsFocused then
begin
try
FIsFocused := False; // thats the place to be, before firering OnExit event
if CanFocus and Assigned(FOnExit) then
FOnExit(Self);
//FIsFocused := False; <-- buggy here
SetFocus控制
要在OnExit 中设置焦点,您必须做更多的工作,因为将焦点更改为下一个控件的消息已经排队。您必须确保对所需控件的焦点更改发生在已经排队的焦点更改消息之后。最简单的方法是使用计时器。
这是一个带有 3 个编辑控件的示例 FMX 表单,每个控件都有一个 OnExit 事件
unit MainForm;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
FMX.Edit;
type
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
EnsureActiveControl_Timer: TTimer;
procedure EnsureActiveControl_TimerTimer(Sender: TObject);
procedure Edit1Exit(Sender: TObject);
procedure Edit2Exit(Sender: TObject);
procedure Edit3Exit(Sender: TObject);
private
// locks the NextActiveControl property to prevent changes while performing the timer event
FTimerSwitchInProgress: Boolean;
FNextActiveControl: TControl;
procedure SetNextActiveControl(const Value: TControl);
protected
property NextActiveControl: TControl read FNextActiveControl write SetNextActiveControl;
public
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.Edit1Exit(Sender: TObject);
begin
NextActiveControl := Edit3;
end;
procedure TForm1.Edit2Exit(Sender: TObject);
begin
NextActiveControl := Edit1;
end;
procedure TForm1.Edit3Exit(Sender: TObject);
begin
NextActiveControl := Edit2;
end;
procedure TForm1.EnsureActiveControl_TimerTimer(Sender: TObject);
begin
EnsureActiveControl_Timer.Enabled := False;
FTimerSwitchInProgress := True;
try
if (Self.ActiveControl <> NextActiveControl) and NextActiveControl.CanFocus then
NextActiveControl.SetFocus;
finally
FTimerSwitchInProgress := False;
end;
end;
procedure TForm1.SetNextActiveControl(const Value: TControl);
begin
if FTimerSwitchInProgress
or (FNextActiveControl = Value)
or (Assigned(Value) and not Value.CanFocus)
or (Self.ActiveControl = Value)
then
Exit;
FNextActiveControl := Value;
EnsureActiveControl_Timer.Enabled := Assigned(FNextActiveControl);
end;
end.