Context menu for JavaFX tree - javafx

I want to create context menu for JavaFX. This is the code that I tested. But for some reason there is no context menu when I right-click on the tree node. Can you help me to find my mistake.
import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ContextMenuBuilder;
import javafx.scene.control.MenuItemBuilder;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.TextFieldTreeCell;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
public class MainApp extends Application
{
List<Employee> employees = Arrays.<Employee>asList(
new Employee("New Chassi", "New Datacenter"),
new Employee("New Battery", "New Rack"),
new Employee("New Chassi", "New Server"),
new Employee("Anna Black", "Sales Department"),
new Employee("Rodger York", "Sales Department"),
new Employee("Susan Collins", "Sales Department"),
new Employee("Mike Graham", "IT Support"),
new Employee("Judy Mayer", "IT Support"),
new Employee("Gregory Smith", "IT Support"),
new Employee("Jacob Smith", "Accounts Department"),
new Employee("Isabella Johnson", "Accounts Department"));
TreeItem<String> rootNode = new TreeItem<>("MyCompany Human Resources");
public static void main(String[] args)
{
Application.launch(args);
}
TreeView<String> treeView = new TreeView<>(rootNode);
#Override
public void start(Stage stage)
{
rootNode.setExpanded(true);
for (Employee employee : employees)
{
TreeItem<String> empLeaf = new TreeItem<>(employee.getName());
boolean found = false;
for (TreeItem<String> depNode : rootNode.getChildren())
{
if (depNode.getValue().contentEquals(employee.getDepartment()))
{
depNode.getChildren().add(empLeaf);
found = true;
break;
}
}
if (!found)
{
TreeItem<String> depNode = new TreeItem<>(
employee.getDepartment()//,new ImageView(depIcon) // Set picture
);
rootNode.getChildren().add(depNode);
depNode.getChildren().add(empLeaf);
}
}
stage.setTitle("Tree View Sample");
VBox box = new VBox();
final Scene scene = new Scene(box, 400, 300);
scene.setFill(Color.LIGHTGRAY);
treeView.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>()
{
#Override
public TreeCell<String> call(TreeView<String> arg0)
{
// custom tree cell that defines a context menu for the root tree item
return new MyTreeCell();
}
});
box.getChildren().add(treeView);
stage.setScene(scene);
stage.show();
}
public static class Employee
{
private final SimpleStringProperty name;
private final SimpleStringProperty department;
private Employee(String name, String department)
{
this.name = new SimpleStringProperty(name);
this.department = new SimpleStringProperty(department);
}
public String getName()
{
return name.get();
}
public void setName(String fName)
{
name.set(fName);
}
public String getDepartment()
{
return department.get();
}
public void setDepartment(String fName)
{
department.set(fName);
}
}
class MyTreeCell extends TextFieldTreeCell<String>
{
private ContextMenu rootContextMenu;
public MyTreeCell()
{
// instantiate the root context menu
rootContextMenu
= ContextMenuBuilder.create()
.items(
MenuItemBuilder.create()
.text("Menu Item")
.onAction(
new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent arg0)
{
System.out.println("Menu Item Clicked!");
}
}
)
.build()
)
.build();
}
#Override
public void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
// if the item is not empty and is a root...
if (!empty && getTreeItem().getParent() == null)
{
setContextMenu(rootContextMenu);
}
}
}
}

You should assign the context Menu to the TreeView instead of assigning it to the cell factory. Here is the code with the context menu working:
import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ContextMenuBuilder;
import javafx.scene.control.MenuItemBuilder;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.TextFieldTreeCell;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Test extends Application
{
List<Employee> employees = Arrays.<Employee>asList(
new Employee("New Chassi", "New Datacenter"),
new Employee("New Battery", "New Rack"),
new Employee("New Chassi", "New Server"),
new Employee("Anna Black", "Sales Department"),
new Employee("Rodger York", "Sales Department"),
new Employee("Susan Collins", "Sales Department"),
new Employee("Mike Graham", "IT Support"),
new Employee("Judy Mayer", "IT Support"),
new Employee("Gregory Smith", "IT Support"),
new Employee("Jacob Smith", "Accounts Department"),
new Employee("Isabella Johnson", "Accounts Department"));
TreeItem<String> rootNode = new TreeItem<>("MyCompany Human Resources");
public static void main(String[] args)
{
Application.launch(args);
}
TreeView<String> treeView = new TreeView<>(rootNode);
#Override
public void start(Stage stage)
{
// instantiate the root context menu
ContextMenu rootContextMenu
= ContextMenuBuilder.create()
.items(
MenuItemBuilder.create()
.text("Menu Item")
.onAction(
new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent arg0)
{
System.out.println("Menu Item Clicked!");
}
}
)
.build()
)
.build();
treeView.setContextMenu(rootContextMenu);
rootNode.setExpanded(true);
for (Employee employee : employees)
{
TreeItem<String> empLeaf = new TreeItem<>(employee.getName());
boolean found = false;
for (TreeItem<String> depNode : rootNode.getChildren())
{
if (depNode.getValue().contentEquals(employee.getDepartment()))
{
depNode.getChildren().add(empLeaf);
found = true;
break;
}
}
if (!found)
{
TreeItem<String> depNode = new TreeItem<>(
employee.getDepartment()//,new ImageView(depIcon) // Set picture
);
rootNode.getChildren().add(depNode);
depNode.getChildren().add(empLeaf);
}
}
stage.setTitle("Tree View Sample");
VBox box = new VBox();
final Scene scene = new Scene(box, 400, 300);
scene.setFill(Color.LIGHTGRAY);
box.getChildren().add(treeView);
stage.setScene(scene);
stage.show();
}
public static class Employee
{
private final SimpleStringProperty name;
private final SimpleStringProperty department;
private Employee(String name, String department)
{
this.name = new SimpleStringProperty(name);
this.department = new SimpleStringProperty(department);
}
public String getName()
{
return name.get();
}
public void setName(String fName)
{
name.set(fName);
}
public String getDepartment()
{
return department.get();
}
public void setDepartment(String fName)
{
department.set(fName);
}
}
}

ContextMenuBuilder and MenuItemBuilder (which are used in the accepted answer) are deprecated in JavaFX 8 and will be removed in future releases.
Following an JavaFX 8 example with two items, one with icon and one without an icon:
MenuItem entry1 = new MenuItem("Test with Icon", graphicNode);
entry1.setOnAction(ae -> ...);
MenuItem entry2 = new MenuItem("Test without Icon");
entry2.setOnAction(ae -> ...);
myTreeView.setContextMenu(new ContextMenu(entry1, entry2));

I know this was 3 years ago but I hope my solution that I just found can help others like me when I was trying to find a simpler solution to this. What I found is you can just create a Label and set that as the graphic for the TreeItem. Then just set the context menu for that label, here is the code to make it work.
//The "" is important, without it the tree item would not appear at all
TreeItem<String> childItem = new TreeItem<>("");
//Create a label with the string you would use for the tree item
childItem.setGraphic(new Label("TreeItem Label"));
//Add menu items for contextMenu
ContextMenu contextMenu = new ContextMenu();
childItem.getGraphic().setOnContextMenuRequested(e -> {
//Sets the context menu for the label.
contextMenu.show(childItem.getGraphic(), e.getScreenX(), e.getScreenY()));
}

Minimal working example below without the unnecessary code and deprecated functions. The solution to .setContextMenu to the entire tree is not really usable since different TreeItems might have different Menus. The code below uses the standard way of using the implemented factory pattern in JavaFX and allows to use of all functions from the JavaFX API:
package javaapplication2;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.TextFieldTreeCell;
import javafx.stage.Stage;
import javafx.util.Callback;
public class JavaApplication2 extends Application {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
//Start a new TreeView
TreeView<String> tv = new TreeView<>(new TreeItem<>("Root"));
//Set the cell factory
tv.setCellFactory(new Callback() {
#Override
public Object call(Object p) {
return new TreeCellWithMenu();
}
});
//Fill tree with some things
tv.getRoot().getChildren().add(new TreeItem<>("1"));
tv.getRoot().getChildren().add(new TreeItem<>("2"));
tv.getRoot().getChildren().add(new TreeItem<>("3"));
//Stage a new scene
stage.setScene(new Scene(tv));
//Show the stage
stage.show();
}
public class TreeCellWithMenu extends TextFieldTreeCell<String> {
ContextMenu men;
public TreeCellWithMenu() {
//ContextMenu with one entry
men = new ContextMenu(new MenuItem("Right Click"));
}
#Override
public void updateItem(String t, boolean bln) {
//Call the super class so everything works as before
super.updateItem(t, bln);
//Check to show the context menu for this TreeItem
if (showMenu(t, bln)) {
setContextMenu(men);
}else{
//If no menu for this TreeItem is used, deactivate the menu
setContextMenu(null);
}
}
//Deccide if a menu should be shown or not
private boolean showMenu(String t, boolean bln){
if (t != null && !t.equals("Root")) {
return true;
}
return false;
}
}
}

Related

How to add two different attributes to one line of a combobox in javafx? [duplicate]

I have a int value which I want to use for configuration. It can have 2 values - 0 for active and 1 for Blocked. I want to display this into friendly combo box:
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MainApp extends Application
{
#Override
public void start(Stage stage) throws Exception
{
int state = 0;
ObservableList<String> options = FXCollections.observableArrayList(
"Active",
"Blocked"
);
ComboBox comboBox = new ComboBox(options);
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
It's not clear for me how I have to implement this into JavaFX Combobox.
When I have 0 I want to display this as Active and when I have 1 I want to display Blocked and also when I change the ComboBox value to update also int state value.
There are different ways to solve this problem. I have listed three of the solutions below. You can use any one of the below solutions which you feel is apt for your scenario.
Using a custom class
Create a custom class KeyValuePair, for storing the string and its corresponding value. Exposed the getters for the required fields.
Later, I have used the setCellFactory() of the comboxbox to show the required data. Use StringConverter to show the key in place of the object.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception
{
KeyValuePair keyValuePair1 = new KeyValuePair("Active", 0);
KeyValuePair keyValuePair2 = new KeyValuePair("Blocked", 1);
ObservableList<KeyValuePair> options = FXCollections.observableArrayList();
options.addAll(keyValuePair1, keyValuePair2);
ComboBox<KeyValuePair> comboBox = new ComboBox<>(options);
// show the correct text
comboBox.setCellFactory((ListView<KeyValuePair> param) -> {
final ListCell<KeyValuePair> cell = new ListCell<KeyValuePair>(){
#Override
protected void updateItem(KeyValuePair t, boolean bln) {
super.updateItem(t, bln);
if(t != null){
setText(String.valueOf(t.getKey()));
}else{
setText(null);
}
}
};
return cell;
});
comboBox.setConverter(new StringConverter<KeyValuePair>() {
#Override
public String toString(KeyValuePair object) {
return object.getKey();
}
#Override
public KeyValuePair fromString(String string) {
return null; // No conversion fromString needed.
}
});
// print the value
comboBox.valueProperty().addListener((ov, oldVal, newVal) -> {
System.out.println(newVal.getKey() + " - " + newVal.getValue());
});
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class KeyValuePair {
private final String key;
private final int value;
public KeyValuePair(String key, int value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public int getValue() {
return value;
}
}
}
Without using an extra class
As suggested by #kleopatra, you can even do this without using an extra class.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
ObservableList<Integer> options = FXCollections.observableArrayList();
options.addAll(1, 0);
ComboBox<Integer> comboBox = new ComboBox<>(options);
// show the correct text
comboBox.setCellFactory((ListView<Integer> param) -> {
final ListCell<Integer> cell = new ListCell<Integer>(){
#Override
protected void updateItem(Integer t, boolean bln) {
super.updateItem(t, bln);
if(t != null){
setText(t == 1 ? "Active" : "Blocked");
}else{
setText(null);
}
}
};
return cell;
});
comboBox.setConverter(new StringConverter<Integer>() {
#Override
public String toString(Integer object) {
return object == 1 ? "Active" : "Blocked";
}
#Override
public Integer fromString(String string) {
return null;
}
});
// print the value
comboBox.valueProperty().addListener((ov, oldVal, newVal) -> {
System.out.println("Changed from " + oldVal + " to " + newVal);
});
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Using Bindings
You can also use Bindings if you don't want to take the pain of creating a new class and you will always have two elements i.e. Active and Blocked.
Just bind the valueProperty() of your combobox to the state, which is supposed to store the value i.e. 0 or 1.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
IntegerProperty state = new SimpleIntegerProperty();
ObservableList options = FXCollections.observableArrayList("Active", "Blocked");
ComboBox<String> comboBox = new ComboBox<>(options);
state.bind(Bindings.when(comboBox.valueProperty().isEqualTo("Active")).then(0).otherwise(1));
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Here is another solution:
declare state as BooleanProperty:
private BooleanProperty state = new SimpleBooleanProperty(false);
bind state property to the valueProperty of comboBox:
comboBox.valueProperty().bind(new When(state).then("Active").otherwise("Blocked"));
complete example:
public class ComboboxTest extends Application {
private BooleanProperty state = new SimpleBooleanProperty(false);
private Button button;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
ObservableList<String> options = FXCollections.observableArrayList(
"Active",
"Blocked"
);
ComboBox comboBox = new ComboBox(options);
button = new Button("false");
button.setOnAction(e -> setSate());
button.textProperty().bind(state.asString());
BorderPane bp = new BorderPane(comboBox);
StackPane stackpane = new StackPane(button);
stackpane.setAlignment(Pos.CENTER);
bp.setTop(stackpane);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
comboBox.valueProperty().bind(new When(state).then("Active").otherwise("Blocked"));
}
public void setSate() {
if (state.get()) {
state.set(false);
} else {
state.set(true);
}
}
}

Determine a JavaFX table row details when reusing tableview context menu

I have a requirement similar to this example.
In the EventHandler callback, how do I determine which row was clicked on?
#Override
public void handle(ActionEvent event) {
// how do I get the row details when reusing context menu and handler code?
}
I am sharing the context menu because I have to add a CheckMenuItem who's state is "global" to the table, i.e. if its selected on any row, I want to show it as checked when I click on any other row in the table.
Use a row factory and one context menu per row, as in the question you linked.
For the "global" CheckMenuItem, create a BooleanProperty and bidirectionally bind the CheckMenuItems' selected properties to it.
SSCCE:
import java.util.function.Function;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableWithContextMenu extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.getColumns().add(column("Item", Item::nameProperty));
table.getColumns().add(column("Value", Item::valueProperty));
BooleanProperty globalSelection = new SimpleBooleanProperty();
table.setRowFactory(t -> {
TableRow<Item> row = new TableRow<>();
ContextMenu contextMenu = new ContextMenu();
MenuItem item1 = new MenuItem("Do something");
item1.setOnAction(e -> System.out.println("Do something with "+row.getItem().getName()));
MenuItem item2 = new MenuItem("Do something else");
item2.setOnAction(e -> System.out.println("Do something else with "+row.getItem().getName()));
CheckMenuItem item3 = new CheckMenuItem("Global selection");
item3.selectedProperty().bindBidirectional(globalSelection);
contextMenu.getItems().addAll(item1, item2, new SeparatorMenuItem(), item3);
row.emptyProperty().addListener((obs, wasEmpty, isEmpty) -> {
if (isEmpty) {
row.setContextMenu(null);
} else {
row.setContextMenu(contextMenu);
}
});
return row ;
});
IntStream.rangeClosed(1, 25).mapToObj(i -> new Item("Item "+i, i)).forEach(table.getItems()::add);
primaryStage.setScene(new Scene(new BorderPane(table), 800, 600));
primaryStage.show();
}
private <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> property) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col ;
}
public static class Item {
private final IntegerProperty value = new SimpleIntegerProperty();
private final StringProperty name = new SimpleStringProperty();
public Item(String name, int value) {
setName(name);
setValue(value);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
}
public static void main(String[] args) {
launch(args);
}
}

Display Combobox values from numbers

I have a int value which I want to use for configuration. It can have 2 values - 0 for active and 1 for Blocked. I want to display this into friendly combo box:
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MainApp extends Application
{
#Override
public void start(Stage stage) throws Exception
{
int state = 0;
ObservableList<String> options = FXCollections.observableArrayList(
"Active",
"Blocked"
);
ComboBox comboBox = new ComboBox(options);
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
It's not clear for me how I have to implement this into JavaFX Combobox.
When I have 0 I want to display this as Active and when I have 1 I want to display Blocked and also when I change the ComboBox value to update also int state value.
There are different ways to solve this problem. I have listed three of the solutions below. You can use any one of the below solutions which you feel is apt for your scenario.
Using a custom class
Create a custom class KeyValuePair, for storing the string and its corresponding value. Exposed the getters for the required fields.
Later, I have used the setCellFactory() of the comboxbox to show the required data. Use StringConverter to show the key in place of the object.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception
{
KeyValuePair keyValuePair1 = new KeyValuePair("Active", 0);
KeyValuePair keyValuePair2 = new KeyValuePair("Blocked", 1);
ObservableList<KeyValuePair> options = FXCollections.observableArrayList();
options.addAll(keyValuePair1, keyValuePair2);
ComboBox<KeyValuePair> comboBox = new ComboBox<>(options);
// show the correct text
comboBox.setCellFactory((ListView<KeyValuePair> param) -> {
final ListCell<KeyValuePair> cell = new ListCell<KeyValuePair>(){
#Override
protected void updateItem(KeyValuePair t, boolean bln) {
super.updateItem(t, bln);
if(t != null){
setText(String.valueOf(t.getKey()));
}else{
setText(null);
}
}
};
return cell;
});
comboBox.setConverter(new StringConverter<KeyValuePair>() {
#Override
public String toString(KeyValuePair object) {
return object.getKey();
}
#Override
public KeyValuePair fromString(String string) {
return null; // No conversion fromString needed.
}
});
// print the value
comboBox.valueProperty().addListener((ov, oldVal, newVal) -> {
System.out.println(newVal.getKey() + " - " + newVal.getValue());
});
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class KeyValuePair {
private final String key;
private final int value;
public KeyValuePair(String key, int value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public int getValue() {
return value;
}
}
}
Without using an extra class
As suggested by #kleopatra, you can even do this without using an extra class.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
ObservableList<Integer> options = FXCollections.observableArrayList();
options.addAll(1, 0);
ComboBox<Integer> comboBox = new ComboBox<>(options);
// show the correct text
comboBox.setCellFactory((ListView<Integer> param) -> {
final ListCell<Integer> cell = new ListCell<Integer>(){
#Override
protected void updateItem(Integer t, boolean bln) {
super.updateItem(t, bln);
if(t != null){
setText(t == 1 ? "Active" : "Blocked");
}else{
setText(null);
}
}
};
return cell;
});
comboBox.setConverter(new StringConverter<Integer>() {
#Override
public String toString(Integer object) {
return object == 1 ? "Active" : "Blocked";
}
#Override
public Integer fromString(String string) {
return null;
}
});
// print the value
comboBox.valueProperty().addListener((ov, oldVal, newVal) -> {
System.out.println("Changed from " + oldVal + " to " + newVal);
});
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Using Bindings
You can also use Bindings if you don't want to take the pain of creating a new class and you will always have two elements i.e. Active and Blocked.
Just bind the valueProperty() of your combobox to the state, which is supposed to store the value i.e. 0 or 1.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
IntegerProperty state = new SimpleIntegerProperty();
ObservableList options = FXCollections.observableArrayList("Active", "Blocked");
ComboBox<String> comboBox = new ComboBox<>(options);
state.bind(Bindings.when(comboBox.valueProperty().isEqualTo("Active")).then(0).otherwise(1));
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Here is another solution:
declare state as BooleanProperty:
private BooleanProperty state = new SimpleBooleanProperty(false);
bind state property to the valueProperty of comboBox:
comboBox.valueProperty().bind(new When(state).then("Active").otherwise("Blocked"));
complete example:
public class ComboboxTest extends Application {
private BooleanProperty state = new SimpleBooleanProperty(false);
private Button button;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
ObservableList<String> options = FXCollections.observableArrayList(
"Active",
"Blocked"
);
ComboBox comboBox = new ComboBox(options);
button = new Button("false");
button.setOnAction(e -> setSate());
button.textProperty().bind(state.asString());
BorderPane bp = new BorderPane(comboBox);
StackPane stackpane = new StackPane(button);
stackpane.setAlignment(Pos.CENTER);
bp.setTop(stackpane);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
comboBox.valueProperty().bind(new When(state).then("Active").otherwise("Blocked"));
}
public void setSate() {
if (state.get()) {
state.set(false);
} else {
state.set(true);
}
}
}

creating RadioButton in TableView Column

I followed the tutorial about TableView in Oracle docs, and I want to do the same thing, but instead of showing a TextField to modified items, I want to show a RadioButton.
(I created the TableView with RadionButton on it)
I used the tutorial at https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/table-view.htm (see Editing Data in the Table) and extended it. As a basis for the values, which should be set by radio buttons I assume an Enumeration.
In my example I extended the Person class with the enum Participation (indicating whether or not the people of the list attending an fictive event) ...
public static enum Participation {
YES,
NO,
MAYBE;
public String toString() {
return super.toString().toLowerCase();
};
}
...
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private final SimpleObjectProperty<Participation> participation;
...
I implemented a RadioButtonCell, which takes an arbitraty EnumSet<T>. So you can use it for every Enumeration and every TableColumn, which should contain RadioButtons.
public static class RadioButtonCell<S,T extends Enum<T>> extends TableCell<S,T>{
private EnumSet<T> enumeration;
public RadioButtonCell(EnumSet<T> enumeration) {
this.enumeration = enumeration;
}
#Override
protected void updateItem(T item, boolean empty)
{
super.updateItem(item, empty);
if (!empty)
{
// gui setup
HBox hb = new HBox(7);
hb.setAlignment(Pos.CENTER);
final ToggleGroup group = new ToggleGroup();
// create a radio button for each 'element' of the enumeration
for (Enum<T> enumElement : enumeration) {
RadioButton radioButton = new RadioButton(enumElement.toString());
radioButton.setUserData(enumElement);
radioButton.setToggleGroup(group);
hb.getChildren().add(radioButton);
if (enumElement.equals(item)) {
radioButton.setSelected(true);
}
}
// issue events on change of the selected radio button
group.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
#SuppressWarnings("unchecked")
#Override
public void changed(ObservableValue<? extends Toggle> observable,
Toggle oldValue, Toggle newValue) {
getTableView().edit(getIndex(), getTableColumn());
RadioButtonCell.this.commitEdit((T) newValue.getUserData());
}
});
setGraphic(hb);
}
}
}
You now have to adjust the CellFactory of the particular TableColumn
participationColumn.setCellFactory((param) -> new RadioButtonCell<Person, Participation>(EnumSet.allOf(Participation.class)));
Finally update the actual value of your data on a commit as usual:
participationColumn.setCellValueFactory(new PropertyValueFactory<Person, Participation>("participation"));
participationColumn.setOnEditCommit(
new EventHandler<CellEditEvent<Person, Participation>>() {
#Override
public void handle(CellEditEvent<Person, Participation> t) {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setParticipation(t.getNewValue());
}
}
);
gkri, thank you for the nice code! This is very useful. I have one additional remark: Of course the new SimpleObjectProperty needs its get & set methods. Without the getter the table will not properly update, especially when sorting or, with TreeTableView, when expanding or collapsing nodes:
public void setParticipation(Participation p){
participation.set(p);
}
public Participation getParticipation(){
return participation.get();
}
So the full code sample:
import java.util.EnumSet;
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TableViewSample extends Application {
private final TableView<Person> table = new TableView<>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com",Participation.MAYBE),
new Person("Isabella", "Johnson", "isabella.johnson#example.com",Participation.MAYBE),
new Person("Ethan", "Williams", "ethan.williams#example.com",Participation.MAYBE),
new Person("Emma", "Jones", "emma.jones#example.com",Participation.MAYBE),
new Person("Michael", "Brown", "michael.brown#example.com",Participation.MAYBE));
final HBox hb = new HBox();
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(650);
stage.setHeight(550);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
table.setMinWidth(640);
TableColumn<Person,String> firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<>("firstName"));
TableColumn<Person,String>lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<>("lastName"));
TableColumn<Person,String> emailCol = new TableColumn("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(
new PropertyValueFactory<>("email"));
TableColumn<Person,Participation> participationColumn = new TableColumn("Participation");
participationColumn.setCellFactory((param) -> new RadioButtonCell<Person, Participation>(EnumSet.allOf(Participation.class)));
participationColumn.setCellValueFactory(new PropertyValueFactory<Person, Participation>("participation"));
participationColumn.setOnEditCommit(
new EventHandler<CellEditEvent<Person, Participation>>() {
#Override
public void handle(CellEditEvent<Person, Participation> t) {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setParticipation(t.getNewValue());
}
}
);
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol, participationColumn );
final TextField addFirstName = new TextField();
addFirstName.setPromptText("First Name");
addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
final TextField addLastName = new TextField();
addLastName.setMaxWidth(lastNameCol.getPrefWidth());
addLastName.setPromptText("Last Name");
final TextField addEmail = new TextField();
addEmail.setMaxWidth(emailCol.getPrefWidth());
addEmail.setPromptText("Email");
final Button addButton = new Button("Add");
addButton.setOnAction((ActionEvent e) -> {
data.add(new Person(
addFirstName.getText(),
addLastName.getText(),
addEmail.getText(),
Participation.NO
));
addFirstName.clear();
addLastName.clear();
addEmail.clear();
});
hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton);
hb.setSpacing(3);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table, hb);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
public static enum Participation {
YES,
NO,
MAYBE;
public String toString() {
return super.toString().toLowerCase();
};
}
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private final SimpleObjectProperty<Participation> participation = new SimpleObjectProperty<Participation>();
private Person(String fName, String lName, String email, Participation p ) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
this.participation.setValue(p);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String fName) {
lastName.set(fName);
}
public String getEmail() {
return email.get();
}
public void setEmail(String fName) {
email.set(fName);
}
public void setParticipation(Participation p){
participation.set(p);
}
public Participation getParticipation(){
return participation.get();
}
}
public static class RadioButtonCell<S,T extends Enum<T>> extends TableCell<S,T>{
private EnumSet<T> enumeration;
public RadioButtonCell(EnumSet<T> enumeration) {
this.enumeration = enumeration;
}
#Override
protected void updateItem(T item, boolean empty)
{
super.updateItem(item, empty);
if (!empty)
{
// gui setup
HBox hb = new HBox(7);
hb.setAlignment(Pos.CENTER);
final ToggleGroup group = new ToggleGroup();
// create a radio button for each 'element' of the enumeration
for (Enum<T> enumElement : enumeration) {
RadioButton radioButton = new RadioButton(enumElement.toString());
radioButton.setUserData(enumElement);
radioButton.setToggleGroup(group);
hb.getChildren().add(radioButton);
if (enumElement.equals(item)) {
radioButton.setSelected(true);
}
}
// issue events on change of the selected radio button
group.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
#SuppressWarnings("unchecked")
#Override
public void changed(ObservableValue<? extends Toggle> observable,
Toggle oldValue, Toggle newValue) {
getTableView().edit(getIndex(), getTableColumn());
RadioButtonCell.this.commitEdit((T) newValue.getUserData());
}
});
setGraphic(hb);
}
}
}
}
Simly do it like in the example 12-8 Editing a Table Cell.
Define a EditingCell like there(at the bottom) but simply replace the TextField with a RadioButton.
If you need further assistance, write a comment under this answer.
Happy Coding,
Kalasch

How to fetch value of selected item in choice box from a table view coloumn in javafx

How can i fetch the value of the selected choice from the choce box in the following table.
column3 has 13 choice box nodes populated using following code.I want to fetch the selected item.
final ObservableList LogLevelList=FXCollections.observableArrayList("FATAL", "ERROR", "WARN", "INFO", "INOUT", "DEBUG");
column3.setCellFactory(new Callback<TableColumn<Feature,String>,TableCell<Feature,String>>(){
#Override
public TableCell<Feature,String> call(TableColumn<Feature,String> param) {
TableCell<Feature,String> cell = new TableCell<Feature,String>(){
#Override
public void updateItem(String item, boolean empty) {
System.out.println("Inside UpdateItem");
ChoiceBox choice = new ChoiceBox(LogLevelList);
choice.getSelectionModel().select(LogLevelList.indexOf(item));
//SETTING ALL THE GRAPHICS COMPONENT FOR CELL
setGraphic(choice);
}
};
return cell;
}
});
Does the predefined ChoiceBoxTableCell do what you need?
column3.setCellFactory(ChoiceBoxTableCell.forTableColumn(logLevelList));
See if this helps:
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.ChoiceBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableChoiceBoxTest extends Application {
#Override
public void start(Stage primaryStage) {
final TableView<Feature> table = new TableView<>();
table.setEditable(true);
final TableColumn<Feature, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
final TableColumn<Feature, String> logLevelCol = new TableColumn<>("Log level");
logLevelCol.setCellValueFactory(new PropertyValueFactory<>("logLevel"));
logLevelCol.setPrefWidth(150);
final ObservableList<String> logLevelList = FXCollections.observableArrayList("FATAL", "ERROR", "WARN", "INFO", "INOUT", "DEBUG");
logLevelCol.setCellFactory(ChoiceBoxTableCell.forTableColumn(logLevelList));
table.getColumns().addAll(nameCol, logLevelCol);
table.getItems().setAll(
IntStream.rangeClosed(1, 20)
.mapToObj(i -> new Feature("Item "+i, "FATAL"))
.collect(Collectors.toList())
);
Button showDataButton = new Button("Dump data");
showDataButton.setOnAction(event -> table.getItems().forEach(System.out::println));
BorderPane root = new BorderPane();
root.setCenter(table);
root.setBottom(showDataButton);
Scene scene = new Scene(root, 400, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class Feature {
private final StringProperty name ;
private final StringProperty logLevel ;
public Feature(String name, String logLevel) {
this.name = new SimpleStringProperty(this, "name", name);
this.logLevel = new SimpleStringProperty(this, "logLevel", logLevel);
}
public StringProperty nameProperty() {
return name ;
}
public final String getName() {
return name.get();
}
public final void setName(String name) {
this.name.set(name);
}
public StringProperty logLevelProperty() {
return logLevel ;
}
public final String getLogLevel() {
return logLevel.get();
}
public final void setLogLevel(String logLevel) {
this.logLevel.set(logLevel);
}
#Override
public String toString() {
return getName() + ": " + getLogLevel();
}
}
public static void main(String[] args) {
launch(args);
}
}
The provided ChoiceBoxTableCell updates the property of the associated item for you, so there's never any need to get the value from the ChoiceBox; you can just get the value from your model object.
I think there are mistakes in your code. You do not want to display your Choice box in each and every cell of that column (i.e Emptied Row's Cell) and Also you should call super class function.
Now for getting the selected value of ChoiceBox , instead of just displaying your choicebox with the values you will have to save them in some ArrayList or Map or best options is to save inside your Feature class. So that you can finally use
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item,empty);
if(item != null){
ChoiceBox choice = new ChoiceBox(LogLevelList);
choice.getSelectionModel().select(LogLevelList.indexOf(item));
choice.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> ov, String t, String t1) {
//either use : myMap.put(getIndex(),t1);
//or : item.setChoice(t1);
}
});
//SETTING ALL THE GRAPHICS COMPONENT FOR CELL
setGraphic(choice);
}
}
Also for demo of ChoiceBox in TableView there is one blog post for you :http://blog.ngopal.com.np/2011/10/01/tableview-cell-modifiy-in-javafx/

Resources