Created
February 11, 2015 17:53
-
-
Save Roland09/7a2701f267588a2ecf31 to your computer and use it in GitHub Desktop.
This is an example about how you can customize the table menu button in a JavaFX TableView using reflection.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.lang.reflect.Field; | |
import javafx.collections.ObservableList; | |
import javafx.event.EventHandler; | |
import javafx.scene.Node; | |
import javafx.scene.control.CheckBox; | |
import javafx.scene.control.ContextMenu; | |
import javafx.scene.control.CustomMenuItem; | |
import javafx.scene.control.Label; | |
import javafx.scene.control.SeparatorMenuItem; | |
import javafx.scene.control.TableColumn; | |
import javafx.scene.control.TableView; | |
import javafx.scene.input.MouseEvent; | |
import com.sun.javafx.scene.control.skin.TableHeaderRow; | |
import com.sun.javafx.scene.control.skin.TableViewSkin; | |
public class TableUtils { | |
/** | |
* Make table menu button visible and replace the context menu with a custom context menu via reflection. | |
* The preferred height is modified so that an empty header row remains visible. This is needed in case you remove all columns, so that the menu button won't disappear with the row header. | |
* IMPORTANT: Modification is only possible AFTER the table has been made visible, otherwise you'd get a NullPointerException | |
* @param tableView | |
*/ | |
public static void addCustomTableMenu( TableView tableView) { | |
// enable table menu | |
tableView.setTableMenuButtonVisible(true); | |
// get the table header row | |
TableHeaderRow tableHeaderRow = getTableHeaderRow((TableViewSkin) tableView.getSkin()); | |
// get context menu via reflection | |
ContextMenu contextMenu = getContextMenu(tableHeaderRow); | |
// setting the preferred height for the table header row | |
// if the preferred height isn't set, then the table header would disappear if there are no visible columns | |
// and with it the table menu button | |
// by setting the preferred height the header will always be visible | |
// note: this may need adjustments in case you have different heights in columns (eg when you use grouping) | |
double defaultHeight = tableHeaderRow.getHeight(); | |
tableHeaderRow.setPrefHeight(defaultHeight); | |
// modify the table menu | |
contextMenu.getItems().clear(); | |
addCustomMenuItems( contextMenu, tableView); | |
} | |
/** | |
* Create a menu with custom items. The important thing is that the menu remains open while you click on the menu items. | |
* @param cm | |
* @param table | |
*/ | |
private static void addCustomMenuItems( ContextMenu cm, TableView table) { | |
// create new context menu | |
CustomMenuItem cmi; | |
// select all item | |
Label showAll = new Label("Show all"); | |
showAll.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(showAll); | |
cmi.setHideOnClick(false); | |
cm.getItems().add(cmi); | |
// deselect all item | |
Label hideAll = new Label("Hide all"); | |
hideAll.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(hideAll); | |
cmi.setHideOnClick(false); | |
cm.getItems().add(cmi); | |
// separator | |
cm.getItems().add(new SeparatorMenuItem()); | |
// menu item for each of the available 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); | |
} | |
} | |
/** | |
* Find the TableHeaderRow of the TableViewSkin | |
* | |
* @param tableSkin | |
* @return | |
*/ | |
private static TableHeaderRow getTableHeaderRow(TableViewSkin<?> tableSkin) { | |
// get all children of the skin | |
ObservableList<Node> children = tableSkin.getChildren(); | |
// find the TableHeaderRow child | |
for (int i = 0; i < children.size(); i++) { | |
Node node = children.get(i); | |
if (node instanceof TableHeaderRow) { | |
return (TableHeaderRow) node; | |
} | |
} | |
return null; | |
} | |
/** | |
* Get the table menu, i. e. the ContextMenu of the given TableHeaderRow via | |
* reflection | |
* | |
* @param headerRow | |
* @return | |
*/ | |
private static ContextMenu getContextMenu(TableHeaderRow headerRow) { | |
try { | |
// get columnPopupMenu field | |
Field privateContextMenuField = TableHeaderRow.class.getDeclaredField("columnPopupMenu"); | |
// make field public | |
privateContextMenuField.setAccessible(true); | |
// get field | |
ContextMenu contextMenu = (ContextMenu) privateContextMenuField.get(headerRow); | |
return contextMenu; | |
} catch (Exception ex) { | |
ex.printStackTrace(); | |
} | |
return null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For JavaFX 21 and JDK 21, here is my implementation of his code, just changed a couple of imports to align with JavaFX 21 and JDK 21.