Problem
The setTableMenuButtonVisible of a TableView provides a mechanism to change the visibility of a table column. However that functionality leaves a lot to be desired:
The menu should remain open. I have e. g. 15 table columns and it's a pain to click menu open -> click column -> click menu open -> click next column -> ... It's a pain to change the visibility of multiple columns
There should be a select all / deselect all functionality
There should be a way to extend the menu with custom items
After you deselected all columns there's no way to reselect a column because the header is gone and with it the table menu
In other words: The current implementation of the table menu is rather useless.
Question
Does anyone know of a way about how to replace the existing tableview menu with a proper one? I've seen a solution with a ".show-hide-columns-button" style lookup and adding an event filter. However that was 2 years ago, maybe things changed.
Thank you very much!
This is how I'd like to have it, demonstrated via ContextMenu (i. e. right mouse button click on table):
public class TableViewSample extends Application {
private final TableView table = new TableView();
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(300);
stage.setHeight(500);
// create table columns
TableColumn firstNameCol = new TableColumn("First Name");
TableColumn lastNameCol = new TableColumn("Last Name");
TableColumn emailCol = new TableColumn("Email");
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
// add context menu
CustomMenuItem cmi;
ContextMenu cm = new ContextMenu();
// select all item
Label selectAll = new Label( "Select all");
selectAll.addEventHandler( MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
for( Object obj: table.getColumns()) {
((TableColumn) obj).setVisible(true);
} }
});
cmi = new CustomMenuItem( selectAll);
cmi.setHideOnClick(false);
cm.getItems().add( cmi);
// deselect all item
Label deselectAll = new Label("Deselect all");
deselectAll.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
for (Object obj : table.getColumns()) {
((TableColumn) obj).setVisible(false);
}
}
});
cmi = new CustomMenuItem( deselectAll);
cmi.setHideOnClick(false);
cm.getItems().add( cmi);
// separator
cm.getItems().add( new SeparatorMenuItem());
// menu item for all columns
for( Object obj: table.getColumns()) {
TableColumn tableColumn = (TableColumn) obj;
CheckBox cb = new CheckBox( tableColumn.getText());
cb.selectedProperty().bindBidirectional( tableColumn.visibleProperty());
cmi = new CustomMenuItem( cb);
cmi.setHideOnClick(false);
cm.getItems().add( cmi);
}
// set context menu
table.setContextMenu(cm);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
}
Update
Concerning the fact that when you deselect all the columns, the header is still visible and so do the menu button. JDK 8u72
I have a table (actually a bunch of tables) where the columns aren't fixed. Every time the columns are changed the above solution was re-setting the list of columns. So if a column called "Collar Size" was hidden it would appear again when the table was refreshed with a new set of data.
This may be crude but I've added a Set to store the names of columns that were hidden last time and then re-hide them this time.
The gist is a Set:
and then the management of adding and removing items from the set. I needed to add a listener on the table columns to hide new columns that matched a name previously hidden.
Other ideas on how to accomplish this will be appreciated.
Thank you, Roland for your solution. That was great. I generalized your solution for a little bit to solve some problems:
the menu is visible will hide the menu.)
Usage:
Maybe someone find it useful, here is my implementation:
Inspired by the solution of ControlsFX I solved the problem myself using reflection. If someone has a better idea and cleaner way without reflection, I'm all ears. I created a utils class in order to distinguish from the sample code.
Example usage:
Screenshots:
Custom table menu in action, the menu remains open while you click the buttons:
Custom table menu still available, even though no columns are visible:
Edit: And here's a version that instead of reflection uses some heuristic and replaces the internal mouse event handler (see the source of JavaFX's TableHeaderRow class if you want to know more):
I tried to implement Balage1551's solution.
For my Application I had to change the listeners in TableViewContextMenuHelper(...).
Without this changes I received a NullPointerException every time i changed the actual Scene and returned afterwards to my Screen containing the tableview.
I hope someone else might find this helpful!
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^OLD!^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvNEW!vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
This adaptation allows to initialize the TableViewContextMenuHelper again when you open an other scene with: