Firemonkey, the framework for cross plattform development, still seems to have some problems and bugs. 🙁 One of the problems I came across was that the TabOrder property was not working:
The problem starts with the inability to set the TabOrder correctly in the designer. They are reset all the time to their defaults. When the application is running, the TabOrder works for some basic dialogs. In others dialogs nothing happens, or the tab key focuses the wrong control.
Looking up the bug database from Embarcadero, this is a known problem / bug. Some people in the internet suggest to set the TabOrder property programmatically during runtime, but that also did not work for me.
After a lot of trying, I found a workaround, that at least works for me. Since I could not set the TabOrder property, I used the HelpContext property (which I dont use in my application) to define the order of the controls:
Note: You can also use the Tag value, but I am using that one for my translation logic.
Next step was to create a class helper for TForm:
unit FormHelper;
interface
type
TFormHelper = class helper for TForm
procedure DoTabHandlingXE(comp : TForm; tabOrder : Integer);
end;
implementation
//Workaround for Tab-Bug in Firemonkey
procedure TFormHelper.DoTabHandlingXE(comp: TForm; tabOrder : Integer);
var
i, c :integer;
current : TComponent;
currentNext : integer;
focus : TStyledControl;
begin
c := Self.ComponentCount - 1;
currentNext := 9999;
focus := nil;
for i := 0 to c do begin
current := Self.Components[i];
if (current is TStyledControl) then begin
if ((current as TStyledControl).HelpContext < currentNext) and
((current as TStyledControl).HelpContext > tabOrder) then begin
currentNext := (current as TStyledControl).HelpContext;
end;
end;
end;
for i := 0 to c do begin
current := Self.Components[i];
if (current is TStyledControl) then begin
if (currentNext = (current as TStyledControl).HelpContext) then begin
focus := (current as TStyledControl);
end;
end;
end;
if focus <> nil then begin
focus.SetFocus;
end;
end;
end.
I am sure there could be some optimizations done with that code, but it works. What we are doing here is to iterate over all controls of a given form and find the next higher value of the HelpContext. Thats the control we want to set focus, when the user hits the tab key.
Of course the code does nothing to far, since the method is not called yet. So the next step is to implement the KeyDown event in the form:
procedure TfrmEinzField.KeyDown(var Key: Word; var KeyChar: Char;
Shift: TShiftState);
var
control : TStyledControl;
begin
if Key = vkTab then
begin
//custom handling
if (Self.GetFocused is TStyledControl) then begin
control := (Self.GetFocused as TStyledControl);
DoTabHandlingXE(Self, control.HelpContext);
end;
end else
inherited; //do default handling
end;
In the code, we get the currently focused control. Then we call the method we wrote before with the current Form variable and the controls HelpContext value.
With that workaround the tab key is now working as expected, jumping to the next control.
With c++ builder I’haven’t found a way to translate this (may be because of another bug?): if (Self.GetFocused is TStyledControl) then begin
BTW, this is the “nested” workaround :
void DoPriorTabHandlingXE(TForm1 *pForm, int tabOrder, WORD &Key);
void DoPriorTabHandlingXE(TForm1 *pForm, int tabOrder, WORD &Key)
{
int i, c;
TStyledControl *current ;
int currentPrior;
TStyledControl *focus;
c = pForm->ComponentCount;
int currentPriorVal = 0;
focus = NULL;
cout <<"tab order: " << tabOrder <= 0; –i)
{
current = dynamic_cast (pForm->Components[i]);
if (current != NULL)
{
if (current->CanFocus == true)
{
if (current->HelpContext == 0) {
continue;
}
cout <<"HelpContext: " <HelpContext <HelpContext > currentPriorVal) && (current->HelpContext HelpContext;
}
}
}
}
for (i = 0; i<c; ++i)
{
current = dynamic_cast (pForm->Components[i]);
if (current != NULL)
{
if (currentPriorVal == current->HelpContext)
{
focus = current;
}
}
}
if (focus != NULL )
{
focus->SetFocus();
}
}
…and this is the form key down click event handling:
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, System::WideChar &KeyChar, TShiftState Shift)
{
cout <<"KEY DOWN!!!" << Key <ComponentCount;
for (i = 0; i< c; ++i)
{
current = dynamic_cast (this->Components[i]);
if (current == NULL) {
continue;
}
if (current->IsFocused == true)
{
switch (Key)
{
case FN_RIGHT:
DoNextTabHandlingXE(this, current->HelpContext, Key);
break;
case FN_LEFT:
DoPriorTabHandlingXE(this, current->HelpContext, Key);
break;
case FN_UP: // not handled yet
case FN_DOWN: // not handled yet
break;
default:
return;
}
break; // exiting for loop
}
}
}
Hmm, sorry I dont have sufficient C++ knowledge to provide detailed help on that. 🙁