user38725
user38725

Reputation: 903

JavaFX TabPane reordering bug, looking for a workaround

I found a little bug in JavaFX TabPane, and am looking for a workaround. I am running JavaFX 13.0.1.

How it happens:

The TabPane's DragPolicy must be set to TabPane.TabDragPolicy.REORDER.

You can navigate between tabs via keyboard shortcuts CTRL + TAB & CTRL + SHIFT + TAB.

However, if I drag, say, the last tab to the very left and release it back to the position it was in (so that nothing should change), these keyboard shortcuts get messed up - no longer pointing to proper next/previous tabs.

You should be able to reproduce it simply with the following code:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Test extends Application {

    @Override
    public void start(Stage primaryStage) {
        TabPane tabPane = new TabPane();
        tabPane.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);

        tabPane.getTabs().add(new Tab("First"));
        tabPane.getTabs().add(new Tab("Second"));
        tabPane.getTabs().add(new Tab("Third"));
        tabPane.getTabs().add(new Tab("Fourth"));
        tabPane.getTabs().add(new Tab("Fifth"));

        StackPane root = new StackPane();
        root.getChildren().add(tabPane);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("TabPane bug");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    public static void main(String[] args) {
        launch(args);
    }
}

Upvotes: 1

Views: 249

Answers (1)

VGR
VGR

Reputation: 44414

An interesting bug. It appears TabPane.getTabs() returns a List which may or may not reflect the visual ordering of the tabs. But the navigation keys always rely on the getTabs() order, not the visual order.

One workaround is to use a Label as a graphic for each Tab, while leaving the Tab’s text as null. You can then keep the Tabs sorted properly yourself, by checking the visual position of each such Label.

import java.util.List;
import java.util.ArrayList;

import javafx.beans.Observable;
import javafx.geometry.Bounds;
import javafx.geometry.NodeOrientation;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class TabDragTest2 extends Application {

    private static Tab createTab(String title) {
        Tab tab = new Tab();
        tab.setGraphic(new Label(title));
        return tab;
    }

    @Override
    public void start(Stage primaryStage) {
        TabPane tabPane = new TabPane();
        tabPane.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);

        tabPane.getTabs().add(createTab("First"));
        tabPane.getTabs().add(createTab("Second"));
        tabPane.getTabs().add(createTab("Third"));
        tabPane.getTabs().add(createTab("Fourth"));
        tabPane.getTabs().add(createTab("Fifth"));

        tabPane.getTabs().addListener((Observable o) -> {
            List<Tab> tabs = new ArrayList<>(tabPane.getTabs());

            NodeOrientation orientation = tabPane.getEffectiveNodeOrientation();
            boolean ltr = orientation == NodeOrientation.LEFT_TO_RIGHT;

            tabs.sort((t1, t2) -> {
                Node title1 = t1.getGraphic();
                Node title2 = t2.getGraphic();
                Bounds title1Bounds =
                    title1.localToScene(title1.getLayoutBounds());
                Bounds title2Bounds =
                    title2.localToScene(title2.getLayoutBounds());

                if (tabPane.getSide().isHorizontal()) {
                    if (ltr) {
                        return Double.compare(
                            title1Bounds.getMinX(), title2Bounds.getMinX());
                    } else {
                        return Double.compare(
                            title2Bounds.getMaxX(), title1Bounds.getMaxX());
                    }
                } else {
                    return Double.compare(
                        title1Bounds.getMinY(), title2Bounds.getMinY());
                }
            });

            if (!tabPane.getTabs().equals(tabs)) {
                Platform.runLater(() -> tabPane.getTabs().setAll(tabs));
            }
        });

        StackPane root = new StackPane();
        root.getChildren().add(tabPane);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("TabPane bug");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

Upvotes: 1

Related Questions