JavaFX TabPane 排序选项卡造成严重破坏

JavaFX TabPane sorting tabs creates havoc

如果您在 space 不足以显示所有选项卡时尝试对选项卡进行排序,JavaFX TabPane 会出现非常奇怪的行为。

更准确地说,选项卡选择按钮(选项卡窗格右侧带有向下箭头的圆形按钮 header)应显示包含所有选项卡的 drop-down 列表,没有显示任何东西

我创建了一个小测试来重现该问题。只需在 "Add new tab & sort" 上单击几次(直到没有足够的 space 用于所有选项卡)(或在 "Add new tab" 上单击几次,然后单击 "Sort tabs"),然后单击右上角的选项卡选择按钮...只是看到它根本没有显示!

请注意,调整 window 的大小以使所有选项卡适合,然后重新调整大小使选项卡选择按钮再次出现,问题就解决了。

这是要重现的代码。我正在使用 jdk1.8.0_92。看起来像 JDK 错误?

public class TabPaneTest extends Application {

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

int i = 1;

@Override
public void start(Stage primaryStage) throws Exception {
    TabPane tabPane = new TabPane();

    tabPane.getTabs().add(new Tab("My beautiful tab " + i, new TextArea("pane " + (i++))));

    Button add = new Button("Add new tab");
    add.setOnAction(event -> {
        tabPane.getTabs().add(new Tab("My beautiful tab " + i, new TextArea("pane " + (i++))));
    });

    Button addSort = new Button("Add new tab & sort");
    addSort.setOnAction(event -> {
        tabPane.getTabs().add(new Tab("My beautiful tab " + i, new TextArea("pane " + (i++))));
        tabPane.getTabs().sort((o1, o2) -> o2.getText().compareTo(o1.getText()));
    });
    Button sort = new Button("Sort tabs");
    sort.setOnAction(event -> {
        tabPane.getTabs().sort((o1, o2) -> o2.getText().compareTo(o1.getText()));
    });


    VBox vbox = new VBox(tabPane, new HBox(add, addSort, sort));

    primaryStage.setScene(new Scene(vbox));
    primaryStage.setWidth(400);
    primaryStage.setHeight(300);
    primaryStage.show();
}
}

Tab 列表的 TabPaneSkin 中有一个 ListChangeListener,但正如您已经提到的那样,它无法对列表进行排序。

作为解决方法,您可以将选项卡放在新列表中并在排序后将其应用于 TabPane

 List<Tab> tabs = new ArrayList(tabPane.getTabs());
 tabs.sort((o1, o2) -> o2.getText().compareTo(o1.getText()));
 tabPane.getTabs().clear();
 tabPane.getTabs().setAll(tabs);

我想我在 TabPaneSkin class' 方法 removeTabs 中发现了问题:它从 tabHeaderArea.controlButtons.popup:

中删除了条目
                // remove the menu item from the popup menu
            ContextMenu popupMenu = tabHeaderArea.controlButtons.popup;
            TabMenuItem tabItem = null;
            if (popupMenu != null) {
                for (MenuItem item : popupMenu.getItems()) {
                    tabItem = (TabMenuItem) item;
                    if (tab == tabItem.getTab()) {
                        break;
                    }
                    tabItem = null;
                }
            }
            if (tabItem != null) {
                tabItem.dispose();
                popupMenu.getItems().remove(tabItem);
            }
            // end of removing menu item

这是一个问题,因为:

  1. 相反的方法addTabs不做相反的事情(即不将项目添加到弹出菜单中,并且
  2. tabHeaderArea.controlButtons.popup 通过订阅 tabPane.getTabs():

    中的更改自行管理其条目
        tabPane.getTabs().addListener((ListChangeListener<Tab>) c -> setupPopupMenu());
    

所以他们都从弹出菜单中删除了项目,但是因为 setupPopupMenuremoveTabs 之前被调用,当使用 [=18 重新添加选项卡时,条目不会重新添加回来=].

我从 removeTabs 方法中删除了以上几行,它工作得很好。

将向 JDK 提交错误...

更新:

http://Bugs.java.com 提交了错误报告(评论 ID JI-9038050),但修复它的希望很小(我最后一次提交错误报告是在 2015 年 9 月,仍然是 "pending")。

与此同时,丑陋的解决方法(感谢@jns)是删除所有选项卡,对它们进行排序,然后再添加回去:

        List<Tab> tabs = Lists.newArrayList(tabPane.getTabs());
        tabs.sort((o1, o2) -> o2.getText().compareTo(o1.getText()));
        tabPane.getTabs().clear();
        tabPane.getTabs().setAll(tabs);

它很难看,因为您实际上可以看到标签消失然后又回来。

更新 2:

更好的解决方法(再次感谢@jns)是在插入之前确定新选项卡的正确位置:

    Comparator<Tab> comparator = (o1, o2) -> o2.getText().compareTo(o1.getText());
    Button addSort = new Button("Add new tab, sorted");
    addSort.setOnAction(event -> {
        Tab newTab = new Tab("My beautiful tab " + i, new TextArea("pane " + (i++)));

        // THIS IS WRONG! See UPDATE 3 below:
        // int pos = Math.max(0, Collections.binarySearch(tabPane.getTabs(), newTab, comparator));
        tabPane.getTabs().add(pos, newTab);
    });

只有在每次插入新选项卡时排序顺序都没有改变的情况下,这才明显有效。如果您需要使用其他排序顺序对现有选项卡进行排序,您仍然需要删除所有选项卡,排序,然后重新添加(请参阅解决方法 1)。

更新 3:

事实证明 Java 的 binarySearch 仅搜索 精确 匹配,而不像我一样返回下限(经验丰富的 C++ 开发人员;) 期望......所以你需要实施以下暴行才能找到插入点:

    <...>
    int pos = 0;
    while(pos < tabPane.getTabs().size() && tabPane.getTabs().get(pos).getText().compareTo(newTab.getText()) < 0) {
        pos++;
    }
    tabPane.getTabs().add(pos, newTab);
    <...>