1
votes

I've searched around and the general answer seems to place

SomeEdit2.setFocus;

in SomeEdit1.OnExit event. I have tried this (Using Delphi Xe5, developing for iOS) and it causes the application to crash. The app does not throw an error, it just blanks out and crashes. I've tried placing the same code in other events but it does not work as expected. For example, when placed in SomeEdit1.OnChange event, when a user hits 'done' on the virtual keyboard - Focus is switched to the desired control, but the keyboard does not show and stops working properly.

What is the proper way to change focus inbetween controls when a user hits the 'done' button provided on the virtual keyboard?

1

1 Answers

4
votes

You can not compare VCL-Control behaviour with FMX-Control behaviour, because sometimes they behave different - they should not, but they do.

In VCL you have an OnExit event and it occurs right after the focus has left the control. So this is an OnAfterExit event.

In FMX the OnExit event is fired before the focus gets away. So this is an OnBeforeExit.

procedure TControl.DoExit;
begin
  if FIsFocused then
  begin
    try
      if CanFocus and Assigned(FOnExit) then
        FOnExit(Self);
      FIsFocused := False;

Now, what has this to do with your current problem?

If you set the focus to another control inside the OnExit event, the current active control DoExit method gets called, which calls the OnExit event, and you have a perfect circle.

So you have several options to fix this

Bug Report

The best solution is to create a bug report and let emba fix this.

There is already a bug report 117752 with the same reason. So I posted the solution as a comment.

Patch FMX.Controls.pas

Copy FMX.Controls into your project source directory and patch the buggy code (just one line)

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 to control

To set the focus in the OnExit you have to do some more work, because the message to change the focus to the next control is already queued. You must ensure that the focus change to the desired control take place after that already queued focus change message. The simplest approach is using a timer.

Here is an example FMX form with 3 edit controls and each of them has an OnExit event

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.