Search code examples
javajavafxjavafx-8

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?

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();
}

Image of tabbed view


Solution

  • 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).