JavaFX TabPane has a very strange behavior if you try to sort the tabs when there is not enough space to show all the tabs.
More precisely, the tab selection button (round button with a down facing arrow on the right of the tab pane header) that should show a drop-down list with all the tabs, doesn't show anything .
I've created a small test to reproduce the problem. Just click several times (till there is not enough space for all the tabs) on "Add new tab & sort" (or several times on "Add new tab" and then "Sort tabs"), and then click on the tab selection button in the top right corner... Just to see that it shows nothing at all!
Note that resizing the window to make all tabs fit, and then resizing it back so the tab selection button appears again, solves the issue.
Here is the code to reproduce. I'm using jdk1.8.0_92 . Looks like a JDK bug?
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();
}
}
There is a ListChangeListener
in the TabPaneSkin
for the Tab
list, but somehow it's not working on sorting the list, as you already mentioned.
As a workaround you can put the tabs in a new list and apply it to the TabPane
after it is sorted:
List<Tab> tabs = new ArrayList(tabPane.getTabs());
tabs.sort((o1, o2) -> o2.getText().compareTo(o1.getText()));
tabPane.getTabs().clear();
tabPane.getTabs().setAll(tabs);
I think I found the problem in the TabPaneSkin
class' method removeTabs
: it removes the entries from the 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
This is a problem because:
addTabs
does not do the opposite (ie, does not add the items into the popup menu, and tabHeaderArea.controlButtons.popup
manages its entries by itself, by subscribing to changes in the tabPane.getTabs()
:
tabPane.getTabs().addListener((ListChangeListener<Tab>) c -> setupPopupMenu());
So they both remove the items from the popup menu, but because setupPopupMenu
is called before removeTabs
, the entries are not re-added back when the tabs re-added with addTabs
.
I removed the above lines from removeTabs
method, and it works just fine.
Will submit a bug to JDK...
UPDATE:
Submitted a bug report at http://Bugs.java.com (review ID JI-9038050), but have little hope that it will be fixed (my last submitted bug report in September 2015 is still "pending").
In the meantime the ugly workaround (thanks to @jns) is to remove all the tabs, sort them, and then add back:
List<Tab> tabs = Lists.newArrayList(tabPane.getTabs());
tabs.sort((o1, o2) -> o2.getText().compareTo(o1.getText()));
tabPane.getTabs().clear();
tabPane.getTabs().setAll(tabs);
It is ugly, because you can actually see the tabs going away and then coming back.
UPDATE 2:
A nicer workaround (thanks again @jns) is to determine the correct position of the new tab before the insertion:
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);
});
This only works obviously if the sorting order does not change every time a new tab is inserted. If you need to sort existing tabs with another sorting order, you will still need to remove all tabs, sort, then re-add (see workaround 1).
UPDATE 3:
It turns out that Java's binarySearch
is only searching for exact match, and not returning the lower bound as I (seasoned C++ developer ;) would expect... So you need to put in place the following atrocity to find the insertion point:
<...>
int pos = 0;
while(pos < tabPane.getTabs().size() && tabPane.getTabs().get(pos).getText().compareTo(newTab.getText()) < 0) {
pos++;
}
tabPane.getTabs().add(pos, newTab);
<...>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.