Kornephoros Kornephoros - 5 months ago 165
Java Question

Javafx TabPane with multiple rows of tabs

I'd like to use a (JavaFX) TabPane to display the content of 20 different tabs. This works fine with the standard TabPane, however, when the pane hits a certain amount of tabs, a button / ComboBox can be clicked to click on one of the tabs not seen.

I'm designing a feature that will be used on a touchscreen, so this is not ideal. I think it'd be more intuitive to have two separate rows of tabs.

How can I add two rows of tabs to a TabPane, or, what can be done to achieve a similar effect? Thanks in advanced.

Here's some sample code to reproduce what I mean:

public class TabTest extends Application {


public static void main(String args[]) {
Application.launch(args);
}

@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Tabs Test");
Group root = new Group();
Scene scene = new Scene(root, 450, 250, Color.WHITE);

TabPane tabPane = new TabPane();
BorderPane borderPane = new BorderPane();

for( int i = 0; i < 20; i++)
{
Tab tab = new Tab();
tab.setText("Tab " + i);
HBox hbox = new HBox();
hbox.getChildren().add(new Label("Tab " + i));
tab.setContent(hbox);
tabPane.getTabs().add(tab);
}

borderPane.prefHeightProperty().bind(scene.heightProperty());
borderPane.prefWidthProperty().bind(scene.widthProperty());

borderPane.setCenter(tabPane);
root.getChildren().add(borderPane);
primaryStage.setScene(scene);
primaryStage.show();
}


Link to tabbed view, since I can't post images yet

Answer

The tab header area for TabPane is basically a StackPane, therefore I think it is not so easy to create two rows of tabs instead of one.

My idea is to hide the original tabs of your TabPane and put a set of ToggleButton objects in a ToggleGroup then bind the selection of toggles with the selection of tabs.

This way you could add the "Tabs" into any container you want (flow, grid, etc).

Really minimal sample:

Main.java

public class Main extends Application {

    TabPane tabPane;
    private ToggleGroup toggleGroup;

    @Override
    public void start(Stage primaryStage) {

        Group root = new Group();
        Scene scene = new Scene(root, 700, 400, Color.WHITE);

        primaryStage.setTitle("Tabs Test");

        toggleGroup = new ToggleGroup();
        toggleGroup.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {

            @Override
            public void changed(ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) {
                if (newValue == null)
                    toggleGroup.selectToggle(oldValue);
                else
                    tabPane.getSelectionModel().select((Tab) newValue.getUserData());
            }
        });

        tabPane = new TabPane();
        tabPane.getStylesheets().add(getClass().getResource("application.css").toExternalForm());

        VBox vboxToggleOuterContainer = new VBox();
        HBox hboxToggleFirstRow = new HBox();
        HBox hboxToggleSecondRow = new HBox();

        vboxToggleOuterContainer.getChildren().addAll(hboxToggleFirstRow, hboxToggleSecondRow);

        for (int i = 0; i < 20; i++) {
            Tab tab = new Tab();
            tab.setText("Tab " + i);
            HBox hbox = new HBox();
            hbox.getChildren().add(new Label("Tab " + i));
            tab.setContent(hbox);
            tabPane.getTabs().add(tab);

            ToggleButton tb = new ToggleButton("Tab" + i);
            tb.setToggleGroup(toggleGroup);
            tb.setUserData(tab);

            if (i < 10)
                hboxToggleFirstRow.getChildren().add(tb);
            else
                hboxToggleSecondRow.getChildren().add(tb);
        }

        toggleGroup.selectToggle(toggleGroup.getToggles().get(0));

        VBox vbox = new VBox();
        vbox.getChildren().addAll(vboxToggleOuterContainer, tabPane);
        vbox.fillWidthProperty().set(true);
        root.getChildren().add(vbox);

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

application.css

.tab-pane {
    -fx-skin: "com.sun.javafx.scene.control.skin.TabPaneSkin";
    -fx-tab-min-height: 0;  
    -fx-tab-max-height: 0; 
}

.tab-pane .tab-header-area {
    -fx-padding: 0 0 0 0; 
}

.tab-pane .tab-header-area .headers-region .tab {

    -fx-padding: 0 0 1 0;
}

The example is really minimal, just shows the approach, you can improve the CSS to have the control much more fancy (or if you want I can also update it if I will have time).