jpfollenius
jpfollenius

Reputation: 16612

Custom component and tab order

I have a custom component (inheriting from TCustomPanel) that consists of two other components (let's say two edits). How do I get the tab order right when using the component?

In the tab order designer I can only access the component itself which cannot have focus because it is a panel. What happens at runtime is that I can access the edits using the tab key, but only after the two buttons below the component got focused.

How can I change the tab order in this situation?

Upvotes: 8

Views: 5262

Answers (1)

NGLN
NGLN

Reputation: 43659

The tab order of those nested controls within your component is distinct from the tab order of the form on which your component resides. The tab order of the component in the tab order list of the form desides when you tab to the nested controls. Once all tab order lists combined, they result in the final cycle:

  • A control on the form (TabOrder=0)
  • Another control on form (TabOrder=1)
  • Your panel component (TabOrder=2)
    • Edit 1 (TabOrder=0)
    • Edit 2 (TabOrder=1)
  • Another control on form (TabOrder=3)

To be able to set the tab order of the panel component design time:

  • Use the tab order editor in the designer (right click on the parent of the component) and change the tab order with the arrows in the editor, or
  • Publish the TabOrder property for your component and set it in the object inspector:

    type 
      TMyPanel = class(TCustomPanel) 
      published 
        property TabOrder; 
      end; 
    

At runtime it is always possible to set the tab order of the component since the TabOrder property is declared public in TWinControl.

... which cannot have focus because it is a panel.

No, a panel can aqcuire focus just fine, but will not by default. This is handled with the TabStop property, which is False by default. You don't want TabStop to set True for your component since (1) a panel has no indicator it has focus and (2) it is not desired (I imagine).


Changing the tab order of the nested controls is preferably done in the constructor of your component, or at runtime.

To be able to set the tab order of the nested controls within you component at design time requires some more work. I do not think you want that, but since my previous answer (deleted) was rubbish (and voted on, strangly) I have worked out an example as compensation.

First, notice that setting the tab order of those edits with the tab order editor in the designer (right click on the panel component) will change the tab order, but it will not last. That is because those changes are not streamed to the DFM.

To be able to stream/save the design time changes of the controls, you need to publish them:

type
  TMyPanel = class(TCustomPanel)
  private
    FEdit1: TEdit;
    FEdit2: TEdit;
  public
    constructor Create(AOwner: TComponent); override;
  published 
    property Edit1: TEdit read FEdit1;
    property Edit2: TEdit read FEdit2;
  end;

constructor TMyPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FEdit1 := TEdit.Create(Self);
  FEdit1.SetBounds(10, 10, 100, 21);
  FEdit1.Name := 'Edit1';
  FEdit1.Parent := Self;
  FEdit1.SetSubComponent(True);
  FEdit2 := TEdit.Create(Self);
  FEdit2.SetBounds(10, 41, 100, 21);
  FEdit2.Name := 'Edit2';
  FEdit2.Parent := Self;
  FEdit2.SetSubComponent(True);
end;

Of course this publishes áll properties of those controls and now users can change whatever they want. To prevent this, consider limiting the published properties of the TEdit controls:

unit MyPanelEdit;

interface

uses
  DesignEditors, Unit2, DesignIntf, SysUtils, Classes, TypInfo, StdCtrls;

type
  TEditProperty = class(TComponentProperty)
  private
    function FilterFunc(const ATestEditor: IProperty): Boolean;
  public
    function GetAttributes: TPropertyAttributes; override;
    procedure GetProperties(Proc: TGetPropProc); override;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterPropertyEditor(TypeInfo(TEdit), TMyPanel, '', TEditProperty);
end;

{ TEditProperty }

function TEditProperty.FilterFunc(const ATestEditor: IProperty): Boolean;
begin
  Result := ATestEditor.GetName = 'TabOrder';
end;

function TEditProperty.GetAttributes: TPropertyAttributes;
begin
  Result := [paSubProperties];
end;

procedure TEditProperty.GetProperties(Proc: TGetPropProc);
var
  LComponents: IDesignerSelections;
  LDesigner: IDesigner;
begin
  LComponents := GetSelections;
  if LComponents <> nil then
  begin
    if not Supports(
        FindRootDesigner(LComponents[0]), IDesigner, LDesigner) then
      LDesigner := Designer;
    GetComponentProperties(LComponents, [tkInteger], LDesigner, Proc,
      FilterFunc);
  end;
end;

end.

This limits the properties of the published TEdit properties to show only TabOrder.

Upvotes: 14

Related Questions