Search code examples
delphitabcontrolfiremonkey

Looking for a 3rd party tabcontrol in Delphi FireMonkey


I am looking for a alternative to Raize components' tab control.

I would like to have the ability to add close button at the top of each page and I want to use slanted tabs and colors on the tabs. Oh, and I'm using FireMonkey 2.

//I know that raize does not support firmonkey.


Solution

  • Firemonkey has it's own TTabControl in the Common Controls page (by default). You can style this using a TStylebook. For example, I'm quite confident it's possible to add a close button onto the tab itself.

    After all, FMX is a vector-based framework and so all visual elements must exist within the style. You'll probably want to load a style into a TStylebook for this, as I'm unable to find a way to load the default style into one. Navigate down to tabitemstyle, and from there you'll be able to tweak the visual appearance of it. Simply add a close button as you'd like (alignment, layout, etc).

    Back in your application code, you'll be looking to use the FindStyleResource routine in order to setup the code (XE2 uses FindBinding and as such you'd set the BindingName property instead). I'm going to assume your close button is called 'CloseButton' (without the quotes);

    var
      MyTab : TTabItem;
    begin
      MyTab := ((TabItem1 as TTabItem).FindStyleResource('CloseButton') as TButton).OnClick := TabClose;
    end;
    

    You'd want to add that code when you initially create the tab, or if creating all the tabs at design time, you'll want to run it at FormCreate. You're basically telling it that when CloseButton is clicked, that you want to call the notify event/procedure TabClose. This procedure is identical to a button click.

    You could go so far as changing the StyleName property of the tab to be CloseButton+Index_of_tab.

    Now, as for the code to close the tab itself, something like this untested example may work, though you'll want to iterate on it.

    procedure TForm1.TabClose(Sender: TObject);
    var
      _mytab : Integer;
      _activetab : Integer;
    begin
      _activetab := ((Sender as TTabItem).Parent as TTabControl).ActiveTab.Index;
      _mytab := ((Sender as TTabItem).Parent as TTabControl).ActiveTab.Index;
      ((Sender as TTabItem).Parent as TTabControl).Tabs[_MyTab].Free;
      ((Sender as TTabItem).Parent as TTabControl).TabIndex := _activetab;
    end;
    

    Now, this is the clever part, and exploits the design of the framework. When you click on a style element that's inside another element, by default, it'll select the parent element. In this example, it'll select the tab that contains the close button the user clicked on. From this, it'll then close that tab (technically, it'll free it, I've not dealt with tabs much in development so you'll want to look into the proper method of 'closing' them).

    There is one problem with this though; You'll probably want to find a better way of detecting the previously active tab if you wish to switch back to it. Right now, it'll simply open the tab that was after the tab you just closed (since the tabcount is now 1 less, the active tab index selects the next physical tab). You'll probably be able to do this by splitting the _activetab code off elsewhere.


    I've done similar things with some of my own programs, and this is how I usually create 'hybrid' components. You're essentially exploiting the modular design of the framework to make it do what you want it to do, without having to rely on third party components.