How to create a ListView of complex objects and allow editing a field on the object? - javafx

I want to have a JavaFX ListView of Person objects. I want the list to display only the name and allow the name to be edited. It should also preserve the other fields in each object after committing an edit to the name. How would you do this idiomatically in JavaFX?
I have the following code, which works, but it's kind of wonky because it has a StringConverter that converts one way from Person to a String of the person's name then doesn't do the reverse conversion and instead relies on the list cell commitEdit method to take a string of the name and set it on the appropriate person.
Here's the code:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.StringConverter;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("My Custom List View");
ObservableList<Person> people = FXCollections.observableArrayList(
new Person("John Doe", "123 New York"),
new Person("Jane Doe", "456 San Francisco")
);
ListView<Person> listView = new ListView();
listView.setCellFactory(new CustomCellFactory());
listView.setEditable(true);
listView.setItems(people);
Scene scene = new Scene(listView,400,300);
stage.setScene(scene);
stage.show();
}
public static class CustomCellFactory implements Callback<ListView<Person>,ListCell<Person>> {
#Override
public ListCell<Person> call(ListView param) {
TextFieldListCell<Person> cell = new TextFieldListCell() {
#Override
public void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
System.out.println("updating item: "+item.toString());
setText(((Person) item).getName());
} else {
setText(null);
}
}
#Override
public void commitEdit(Object newName) {
((Person)getItem()).setName((String)newName);
super.commitEdit(getItem());
}
};
cell.setConverter(new StringConverter() {
#Override
public String toString(Object person) {
return ((Person)person).getName();
}
#Override
public Object fromString(String string) {
return string;
}
});
return cell;
}
}
public static class Person {
private String name;
private String address;
public Person(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String toString() {
return name+" at "+address;
}
}
}

TextFieldListCell is just a convenience implementation of ListCell that provides the most common form of editing for list cells (i.e. if the items in the list are Strings, or objects that have an easy conversion to and from strings). You'll often find that you need more specific editing (e.g. you'll often want to filter the text allowed in the editing text field using a TextFormatter), and in that case you just implement the ListCell yourself. I think this is a case where, on balance, it makes more sense to implement ListCell from scratch.
It seems you can force the TextFieldListCell to work for this use case, using:
listView.setCellFactory(lv -> {
TextFieldListCell<Person> cell = new TextFieldListCell<Person>();
cell.setConverter(new StringConverter<Person>() {
#Override
public String toString(Person person) {
return person.getName();
}
#Override
public Person fromString(String string) {
Person person = cell.getItem();
person.setName(string);
return person ;
}
});
return cell;
});
(Note that in your code, your updateItem() method is equivalent to the one already implemented in TextFieldListCell, so it's redundant, and the extra functionality in commitEdit(...) is now in the (typesafe) StringConverter, so there's no longer any need for a subclass.)
This just feels a little fragile, as it relies on a particular implementation of committing the new value from the text field and its interaction with the string converter, but it seems to work fine in tests.
My preference for this, however, would be to implement the ListCell directly yourself, as it gives you full control over the interaction between the text field and the editing process. This is pretty straightforward:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
ListView<Person> listView = new ListView<>();
ObservableList<Person> people = FXCollections.observableArrayList(
new Person("John Doe", "123 New York"),
new Person("Jane Doe", "456 San Francisco")
);
listView.setEditable(true);
listView.setItems(people);
listView.setCellFactory(lv -> new ListCell<Person>() {
private TextField textField = new TextField() ;
{
textField.setOnAction(e -> {
commitEdit(getItem());
});
textField.addEventFilter(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
});
}
#Override
protected void updateItem(Person person, boolean empty) {
super.updateItem(person, empty);
if (empty) {
setText(null);
setGraphic(null);
} else if (isEditing()) {
textField.setText(person.getName());
setText(null);
setGraphic(textField);
} else {
setText(person.getName());
setGraphic(null);
}
}
#Override
public void startEdit() {
super.startEdit();
textField.setText(getItem().getName());
setText(null);
setGraphic(textField);
textField.selectAll();
textField.requestFocus();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(getItem().getName());
setGraphic(null);
}
#Override
public void commitEdit(Person person) {
super.commitEdit(person);
person.setName(textField.getText());
setText(textField.getText());
setGraphic(null);
}
});
// for debugging:
listView.setOnMouseClicked(e -> {
if (e.getClickCount() == 2) {
listView.getItems().forEach(p -> System.out.println(p.getName()));
}
});
Scene scene = new Scene(listView,400,300);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class Person {
private String name;
private String address;
public Person(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String toString() {
return name+" at "+address;
}
}
public static void main(String[] args) {
launch(args);
}
}
If you might need this kind of functionality frequently, you could easily create a reusable class:
import java.util.function.BiFunction;
import java.util.function.Function;
import javafx.scene.control.ListCell;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public class EditingListCell<T> extends ListCell<T> {
private final TextField textField ;
private final Function<T, String> propertyAccessor ;
public EditingListCell(Function<T, String> propertyAccessor, BiFunction<String, T, T> updater) {
this.propertyAccessor = propertyAccessor ;
this.textField = new TextField();
textField.setOnAction(e -> {
T newItem = updater.apply(textField.getText(), getItem());
commitEdit(newItem);
});
textField.addEventFilter(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
});
}
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else if (isEditing()) {
textField.setText(propertyAccessor.apply(item));
setText(null);
setGraphic(textField);
} else {
setText(propertyAccessor.apply(item));
setGraphic(null);
}
}
#Override
public void startEdit() {
super.startEdit();
textField.setText(propertyAccessor.apply(getItem()));
setText(null);
setGraphic(textField);
textField.selectAll();
textField.requestFocus();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(propertyAccessor.apply(getItem()));
setGraphic(null);
}
#Override
public void commitEdit(T item) {
super.commitEdit(item);
getListView().getItems().set(getIndex(), item);
setText(propertyAccessor.apply(getItem()));
setGraphic(null);
}
}
and then you just need
listView.setCellFactory(lv -> new EditingListCell<>(
Person::getName,
(text, person) -> {
person.setName(text);
return person ;
})
);

Related

JavaFx TableCellEditor

I have JavaFx TableView and I permit user to enter some data in the table and retrieve what a user entered in the table for this reason I create also ArrayList of TextFields and use the following code but the size of the ArrayList should be 3 in my case but I found the size 7, what's wrong?
Edit the full code here
import java.util.ArrayList;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
public class TestTableView extends Application
{
private final TableView <ProductOpeningBalance> tableProductOpeningBalance = new TableView();
private final Scene scene = new Scene(tableProductOpeningBalance, 400, 200);
private final TableColumn productQuantity = new TableColumn("Product Quantity");
private final ObservableList <ProductOpeningBalance> data = FXCollections.observableArrayList(
new ProductOpeningBalance("0"),
new ProductOpeningBalance("0"));
private final ArrayList <TextField> txtProductQuantity = new ArrayList <> ();
#Override
public void start(Stage stage)
{
productQuantity.impl_setReorderable(false);
productQuantity.setEditable(true);
productQuantity.setCellValueFactory(new PropertyValueFactory("ProductQuantity"));
productQuantity.setCellFactory(column -> new TableCell()
{
#Override
public void startEdit()
{
if(!isEmpty())
{
super.startEdit();
createTextField();
setText(null);
setGraphic(txtProductQuantity.get(txtProductQuantity.size() - 1));
txtProductQuantity.get(txtProductQuantity.size() - 1).selectAll();
}
}
#Override
public void cancelEdit()
{
super.cancelEdit();
setText((String) getItem());
setGraphic(null);
}
public void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
if(empty)
{
setText(null);
setGraphic(null);
}
else
{
if(isEditing())
{
if(txtProductQuantity.get(txtProductQuantity.size() - 1) != null)
{
txtProductQuantity.get(txtProductQuantity.size() - 1).setText(getString());
}
setText(null);
setGraphic(txtProductQuantity.get(txtProductQuantity.size() - 1));
}
else
{
setText(getString());
setGraphic(null);
}
}
}
private void createTextField()
{
txtProductQuantity.add(new TextField(getString()));
txtProductQuantity.get(txtProductQuantity.size() - 1).
setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
txtProductQuantity.get(txtProductQuantity.size() - 1).setAlignment(Pos.BASELINE_RIGHT);
txtProductQuantity.get(txtProductQuantity.size() - 1).focusedProperty().
addListener((ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) ->
{
if(!arg2)
{
commitEdit(txtProductQuantity.get(txtProductQuantity.size() - 1).getText());
}
});
}
private String getString()
{
return getItem() == null ? "" : getItem().toString();
}
});
tableProductOpeningBalance.setEditable(true);
tableProductOpeningBalance.getColumns().addAll(productQuantity);
tableProductOpeningBalance.setItems(data);
stage.setScene(scene);
stage.show();
}
public class ProductOpeningBalance
{
private final SimpleStringProperty ProductQuantity;
public ProductOpeningBalance(String productQuantity)
{
this.ProductQuantity = new SimpleStringProperty(productQuantity);
}
public void setProductQuantity(String productQuantity)
{
ProductQuantity.set(productQuantity);
}
public String getProductQuantity()
{
return ProductQuantity.get();
}
}
}
Solution finally:
this code help me to find what I need after spent a lot of time in searching and trying a lot of methods
purchaseItemPrice.setCellFactory(column -> new TableCell()
{
#Override
public void startEdit()
{
if(!isEmpty())
{
if(txtPurchaseItemPrice.size() < data.size() && getGraphic() == null)
{
super.startEdit();
txtPurchaseItemPrice.add(new TextField());
txtPurchaseItemPrice.get(txtPurchaseItemPrice.size() - 1).setAlignment(Pos.BASELINE_RIGHT);
setGraphic(txtPurchaseItemPrice.get(txtPurchaseItemPrice.size() - 1));
}
}
}
public void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
if(!empty)
{
if(isEditing())
{
setGraphic(txtPurchaseItemPrice.get(txtPurchaseItemPrice.size() - 1));
}
}
}
});

JavaFX TableView get row from button cell

Referring to question JavaFX TableView custom cell rendering split menu button, i'm able to render split menu button in every row. I've updated my code as suggested by James_D and in the answer by Keyur Bhanderi.
The question is about get value of the row where split menu is located without selecting row before click.
Update: Added images to view output
The images below show the output, every button i click.
Updated SplitMenuCellFactory.java
package com.example.splimenubtn;
import java.util.List;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
public class SplitMenuCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
private List<MenuItemFactory<T>> menuItems;
public SplitMenuCellFactory() {}
public SplitMenuCellFactory(List<MenuItemFactory<T>> items) {
menuItems = items;
}
#Override
public TableCell<S, T> call(TableColumn<S, T> param) {
return new TableCell<S, T>() {
#Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
setText(null);
} else {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
if (getTableRow() != null) {
setGraphic(new SplitMenuButtonFactory<>(menuItems, getTableRow().getIndex()).buildButton());
}
}
}
};
}
}
Update, adding missing class
SplitMenuButtonFactory.java
package com.example.splimenubtn;
import java.util.List;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SplitMenuButton;
public class SplitMenuButtonFactory<T> {
private List<MenuItemFactory<T>> menuItems;
private int rIndex = 0;
public SplitMenuButtonFactory(List<MenuItemFactory<T>> items) {
menuItems = items;
}
public SplitMenuButtonFactory(List<MenuItemFactory<T>> items, int rI) {
menuItems = items;
rIndex = rI;
}
public SplitMenuButton buildButton() {
SplitMenuButton menuBtn = new SplitMenuButton();
// menuBtn.getItems().addAll(menuItems);
for (MenuItemFactory<?> mIF : menuItems) {
MenuItem btn = mIF.setRowIndex(rIndex).buildMenuItem();
if (mIF.isDefault()) {
menuBtn.setText(btn.getText());
menuBtn.setOnAction(btn.getOnAction());
}
menuBtn.getItems().add(btn);
}
return menuBtn;
}
}
MenuItemsFactory.java
package com.example.splimenubtn;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableView;
public class MenuItemFactory<S> {
private MenuItemActions itemType;
private String itemLbl;
private TableView<S> table;
private boolean defaultAction;
private int rIndex = 0;
public MenuItemFactory(MenuItemActions itemType, String itemLabel, boolean dA) {
this.itemType = itemType;
itemLbl = itemLabel;
defaultAction = dA;
}
public MenuItemFactory(MenuItemActions itemType, String itemLabel, TableView<S> t, boolean dA) {
this.itemType = itemType;
itemLbl = itemLabel;
defaultAction = dA;
table = t;
}
public MenuItemFactory<S> setDataList(TableView<S> t) {
table = t;
return this;
}
public boolean isDefault() {
return defaultAction;
}
public MenuItemFactory<S> setRowIndex(int rI) {
rIndex = rI;
return this;
}
public MenuItem buildMenuItem() {
MenuItem mI = new MenuItem();
switch (itemType) {
case DETAILS:
mI.setText(itemLbl);
mI.setOnAction(handleDetails());
break;
case EDIT:
mI.setText(itemLbl);
mI.setOnAction(handleEdit());
break;
case DELETE:
mI.setText(itemLbl);
mI.setOnAction(handleDelete());
break;
default:
break;
}
return mI;
}
private EventHandler<ActionEvent> handleDetails() {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent aE) {
System.out.println("*** DETAIL REQUEST ***");
}
};
}
private EventHandler<ActionEvent> handleEdit() {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent aE) {
System.out.println("*** EDIT REQUESTED ***");
table.getSelectionModel().select(rIndex);
System.out.println("*** " + table.getSelectionModel().getSelectedItem().toString() + " ***");
}
};
}
private EventHandler<ActionEvent> handleDelete() {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent aE) {
System.out.println("*** DELETE REQUESTED ***");
System.out.println("*** " + table.getSelectionModel().getSelectedItem().toString() + " ***");
}
};
}
}
But when i click on the button, i'm getting always last value.
How can i get the object in the row where button is?
Any help or suggestion that point me to right direction is appreciated.
Simply use the TableCell to retrieve the value in the onAction event handler (or whatever you use in the product of the SplitMenuButtonFactory you're not showing to us).
Simplified example
public static SplitMenuButton createSplitMenuButton(final TableCell cell) {
SplitMenuButton result = new SplitMenuButton();
result.setOnAction(evt -> {
TableRow row = cell.getTableRow();
System.out.println("row item: " +row.getItem());
});
return result;
}
Furthermore it's better to reuse the SplitMenuButton in the cell and updating it instead of recerating it every time the cell item changes.
#Override
public TableCell<S, T> call(TableColumn<S, T> param) {
return new TableCell<S, T>() {
private final SplitMenuButton button = createSplitMenuButton(this);
{
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
updateMenuButton(button, item); // placeholder for updating the button according to the new item
setGraphic(button);
}
}
};
}

JavaFX TableView custom cell rendering split menu button

i've a problem with a custom cell render in a java fx table view component. I'm able to render the split menu button, but is only rendered from second row of table.
Below i put the code created to generate that image.
SplitMenuButtonApp.java
package com.example.splimenubtn;
import java.util.ArrayList;
import java.util.List;
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.SplitMenuButton;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class SplitMenuButtonApp extends Application {
private class Contact {
private StringProperty firstName;
private StringProperty lastName;
public Contact() {}
public Contact(String fName, String lName) {
firstName = new SimpleStringProperty(fName);
lastName = new SimpleStringProperty(lName);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public StringProperty firstName() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String lName) {
lastName.set(lName);
}
public StringProperty lastName() {
return lastName;
}
}
private ObservableList<Contact> data;
protected List<MenuItemFactory<Contact>> menuItemsList;
private TableView<Contact> table;
#Override
public void start(Stage primaryStage) throws Exception {
// Init data list
data = FXCollections.observableArrayList();
data.add(new Contact("Mickey", "Mouse"));
data.add(new Contact("Donald", "Duck"));
data.add(new Contact("Fantasy", "Name"));
initMenuButton();
SplitMenuButtonFactory<Contact> sMBtn = new SplitMenuButtonFactory<>();
sMBtn.setMenuItems(menuItemsList);
SplitMenuButton actions = sMBtn.buildButton();
// Build the list
table = new TableView<>();
TableColumn<Contact, String> col = new TableColumn<>("First Name");
col.setCellValueFactory(c -> c.getValue().firstName);
table.getColumns().add(col);
col = new TableColumn<>("Last Name");
col.setCellValueFactory(c -> c.getValue().lastName);
table.getColumns().add(col);
TableColumn<Contact, SplitMenuButton> aCol = new TableColumn<>("Action");
aCol.setCellValueFactory(new PropertyValueFactory<>(""));
aCol.setCellFactory(new ButtonCellFactory<>(actions));
table.getColumns().add(aCol);
table.setItems(data);
AnchorPane root = new AnchorPane();
AnchorPane.setTopAnchor(table, 5.0);
AnchorPane.setRightAnchor(table, 5.0);
AnchorPane.setBottomAnchor(table, 5.0);
AnchorPane.setLeftAnchor(table, 5.0);
root.getChildren().add(table);
Scene s = new Scene(root, 600d, 300d);
primaryStage.setScene(s);
primaryStage.setTitle("Split menu button on table row");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void initMenuButton() {
if (menuItemsList == null) {
menuItemsList = new ArrayList<>();
menuItemsList.add(new MenuItemFactory<Contact>(MenuItemActions.EDIT, "Edit", true).setDataList(table));
menuItemsList.add(new MenuItemFactory<Contact>(MenuItemActions.DELETE, "Delete", false).setDataList(table));
}
}
}
MenuItemActions.java
package com.example.splimenubtn;
public enum MenuItemActions {
/**
* Detail item
*/
DETAILS,
/**
* Edit/Update item
*/
EDIT,
/**
* Delete item
*/
DELETE;
}
MenuItemFactory.java
package com.example.splimenubtn;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableView;
public class MenuItemFactory<S> {
private MenuItemActions itemType;
private String itemLbl;
private TableView<S> table;
private boolean defaultAction;
public MenuItemFactory() {}
public MenuItemFactory(MenuItemActions itemType, String itemLabel, boolean dA) {
this.itemType = itemType;
itemLbl = itemLabel;
defaultAction = dA;
}
public MenuItemFactory<S> setDataList(TableView<S> t) {
table = t;
return this;
}
public boolean isDefault() {
return defaultAction;
}
public MenuItem buildMenuItem() {
MenuItem mI = new MenuItem();
switch (itemType) {
case DETAILS:
mI.setText(itemLbl);
mI.setOnAction(handleDetails());
break;
case EDIT:
mI.setText(itemLbl);
mI.setOnAction(handleEdit());
break;
case DELETE:
mI.setText(itemLbl);
mI.setOnAction(handleDelete());
break;
default:
break;
}
return mI;
}
private EventHandler<ActionEvent> handleDetails() {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent aE) {
System.out.println("*** DETAIL REQUESTED ***");
}
};
}
private EventHandler<ActionEvent> handleEdit() {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent aE) {
System.out.println("*** EDIT REQUESTED ***");
}
};
}
private EventHandler<ActionEvent> handleDelete() {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent aE) {
System.out.println("*** DELETE REQUESTED ***");
}
};
}
}
ButtonCellFactory.java
package com.example.splimenubtn;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.SplitMenuButton;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
public class ButtonCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
private SplitMenuButton btn;
public ButtonCellFactory() {}
public ButtonCellFactory(SplitMenuButton b) {
btn = b;
}
#Override
public TableCell<S, T> call(TableColumn<S, T> param) {
return new TableCell<S, T>() {
#Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
setText(null);
} else {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(btn);
}
}
};
}
}
SpliMenuButtonFactory.java
package com.example.splimenubtn;
import java.util.List;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SplitMenuButton;
public class SplitMenuButtonFactory<T> {
private List<MenuItemFactory<T>> menuItems;
public SplitMenuButtonFactory() {}
public SplitMenuButtonFactory<T> setMenuItems(List<MenuItemFactory<T>> items) {
menuItems = items;
return this;
}
public SplitMenuButton buildButton() {
SplitMenuButton menuBtn = new SplitMenuButton();
for (MenuItemFactory<?> mIF : menuItems) {
MenuItem btn = mIF.buildMenuItem();
if (mIF.isDefault()) {
menuBtn.setText(btn.getText());
menuBtn.setOnAction(btn.getOnAction());
}
menuBtn.getItems().add(btn);
}
return menuBtn;
}
}
As you see in the image, with this code i'm able to create the spli menu button and add it to ta table, but is only rendered on last row.
I need suggestion to render the split menu button in the other row, any help is appreciated.
Cause you have use the same button in every cell, So it's set a button only last of the cell Value.
Remove this line in SplitMenuButtonApp class
SplitMenuButton actions = sMBtn.buildButton();
And replace this line
aCol.setCellFactory(new ButtonCellFactory<>(actions));
To below code
Callback<TableColumn<Contact, SplitMenuButton>, TableCell<Contact, SplitMenuButton>> actionsCol = new Callback<TableColumn<Contact, SplitMenuButton>, TableCell<Contact, SplitMenuButton>>() {
#Override
public TableCell call(final TableColumn<Contact, SplitMenuButton> param) {
final TableCell<Contact, SplitMenuButton> cell = new TableCell<Contact, SplitMenuButton>() {
SplitMenuButton actions = sMBtn.buildButton();
#Override
public void updateItem(SplitMenuButton item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
setText(null);
} else {
setGraphic(actions);
setText(null);
}
}
};
return cell;
}
};
aCol.setCellFactory(actionsCol);
I hope this code is working for you:)

Adding currency symbol to tableview but remove on edit cell

I already know how I have to manipulate the table cell using a table cell factory callback. I added a currency symbol to the cell to make it look neat. (i.e. € 5,00 instead of 5,00) The thing is, when i double click on the cell i want that symbol to be removed. But for the heck of it, I'm unable to find how i am able to manipulate the textfield again to remove that currency symbol and bring it back in when the user committed the edit. Basically what I try to do is something similar when editing a cell in Excel :).
Any chance someone can help me out with a little basic example? Do I need to use the OnEditStart event?
Any time you want to configure how an item in a cell is displayed, without changing the actual data, you should use a custom TableCell. Here is an example that exhibits the behavior you want:
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.function.UnaryOperator;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.StringConverter;
public class CurrencyCell<T> extends TableCell<T, Double> {
private final TextField textField ;
private final NumberFormat format = DecimalFormat.getCurrencyInstance();
private final DecimalFormat textFieldFormat = new DecimalFormat("0.00");
public CurrencyCell() {
this.textField = new TextField();
StringConverter<Double> converter = new StringConverter<Double>() {
#Override
public String toString(Double object) {
return object == null ? "" : textFieldFormat.format(object) ;
}
#Override
public Double fromString(String string) {
try {
return string.isEmpty() ? 0.0 : textFieldFormat.parse(string).doubleValue();
} catch (ParseException e) {
e.printStackTrace();
return 0.0 ;
}
}
};
UnaryOperator<Change> filter = (Change change) -> {
String newText = change.getControlNewText() ;
if (newText.isEmpty()) {
return change ;
}
try {
textFieldFormat.parse(newText);
return change ;
} catch (ParseException exc) {
return null ;
}
};
TextFormatter<Double> textFormatter = new TextFormatter<Double>(converter, 0.0, filter);
textField.setTextFormatter(textFormatter);
textField.setOnAction(e -> commitEdit(converter.fromString(textField.getText())));
textField.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
});
setGraphic(textField);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#Override
protected void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setContentDisplay(ContentDisplay.TEXT_ONLY);
} else if (isEditing()) {
textField.setText(item.toString());
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
} else {
setText(format.format(item));
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
#Override
public void startEdit() {
super.startEdit();
textField.setText(textFieldFormat.format(getItem()));
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.requestFocus();
textField.selectAll();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(format.format(getItem()));
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#Override
public void commitEdit(Double newValue) {
super.commitEdit(newValue);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
And here's an example using it:
import java.util.Locale ;
import java.util.Random;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class CurrencyCellTest extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.setEditable(true);
table.getColumns().add(column("Item", Item::nameProperty));
TableColumn<Item, Double> priceCol = column("Price", item -> item.priceProperty().asObject());
table.getColumns().add(priceCol);
priceCol.setCellFactory(tc -> new CurrencyCell<>());
Random rng = new Random();
for (int i = 1; i <= 100; i++) {
table.getItems().add(new Item("Item "+i, rng.nextInt(10000)/100.0));
}
primaryStage.setScene(new Scene(table, 600, 600));
primaryStage.show();
}
private static <S,T> TableColumn<S,T> column(String title, Function<S, Property<T>> property) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col ;
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final DoubleProperty price = new SimpleDoubleProperty();
public Item(String name, double price) {
setName(name);
setPrice(price);
}
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 final DoubleProperty priceProperty() {
return this.price;
}
public final double getPrice() {
return this.priceProperty().get();
}
public final void setPrice(final double price) {
this.priceProperty().set(price);
}
}
public static void main(String[] args) {
// for testing:
Locale.setDefault(new Locale("NL", "nl"));
launch(args);
}
}

Refer to data fields in TreeView Type other than String

I have used the sample Tree User interface on the JAVA FX 2 site and changed the type from String to PayString and all seems to work well. My next step is in the setCellFactory 'TextFieldTreeCellImpl' I need to assign a different context menu to the cells depending upon the value of PayString.level an integer.
How do I reference the value of the data fields associated with the current cell.
Below are the two source files code in packages treeviewsample. The position I need the data is marked with +++++++++++++++++++++++++ for ease of finding.
`/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package treeviewsample;
import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.VBox;
public class TreeViewSample extends Application {
private final Node rootIcon;
private final Image depIcon;
List<Employee> employees = Arrays.<Employee>asList(
new Employee(1, "Ethan Williams", "Sales Department"),
new Employee(2, "Emma Jones", "Sales Department"),
new Employee(3, "Michael Brown", "Sales Department"),
new Employee(4, "Anna Black", "Sales Department"),
new Employee(5, "Rodger York", "Sales Department"),
new Employee(6, "Susan Collins", "Sales Department"),
new Employee(7, "Mike Graham", "IT Support"),
new Employee(8, "Judy Mayer", "IT Support"),
new Employee(9, "Gregory Smith", "IT Support"),
new Employee(10, "Jacob Smith", "Accounts Department"),
new Employee(11, "Isabella Johnson", "Accounts Department"));
TreeItem<PayString> rootNode;
Integer next;
public static void main(String[] args) {
Application.launch(args);
}
public TreeViewSample() {
this.next = 12;
this.depIcon = new Image(getClass().getResourceAsStream("department.png"));
this.rootIcon = new ImageView(new Image(getClass().getResourceAsStream("root.png")));
this.rootNode = new TreeItem<>(new PayString ("MyCompany Human Resources", 0,0), rootIcon);
}
#Override
public void start(Stage stage) {
rootNode.setExpanded(true);
for (Employee employee : employees) {
TreeItem<PayString> empLeaf = new TreeItem<>(new PayString(employee.getName(),2,employee.getId()));
boolean found = false;
for (TreeItem<PayString> depNode : rootNode.getChildren()) {
if (depNode.getValue().toString().contentEquals(employee.getDepartment())){
depNode.getChildren().add(empLeaf);
found = true;
break;
}
}
if (!found) {
TreeItem depNode = new TreeItem<>(new PayString(employee.getDepartment(),1,employee.getId()),
new ImageView(depIcon)
);
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<PayString> treeView = new TreeView<>(rootNode);
treeView.setEditable(true);
treeView.setCellFactory(new Callback<TreeView<PayString>,TreeCell<PayString>>(){
#Override
public TreeCell<PayString> call(TreeView<PayString> p) {
return new TextFieldTreeCellImpl();
}
});
box.getChildren().add(treeView);
stage.setScene(scene);
stage.show();
}
private final class TextFieldTreeCellImpl extends TreeCell<PayString> {
private TextField textField;
private ContextMenu addMenu = new ContextMenu();
/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* This is where I need to be able to extract the values in the current TreeCell<PayString>
* to be able to create the appropriate context menu.
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
private String curr = getString();
public TextFieldTreeCellImpl() {
TreeItem<PayString> paystring = treeItemProperty().getValue();
MenuItem addMenuItem = new MenuItem("Add Employee");
MenuItem addMenuItem2 = new MenuItem("Add Address");
MenuItem addMenuItem3 = new MenuItem(curr);
addMenu.getItems().add(addMenuItem2);
addMenu.getItems().add(addMenuItem3);
addMenuItem.setOnAction(new EventHandler() {
#Override
public void handle(Event t) {
TreeItem newEmployee =
new TreeItem<>(new PayString ("New Employee",3,next));
next ++;
getTreeItem().getChildren().add(newEmployee);
}
});
}
#Override
public void startEdit() {
super.startEdit();
if (textField == null) {
createTextField();
}
setText(null);
setGraphic(textField);
textField.selectAll();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(getString());
setGraphic(getTreeItem().getGraphic());
}
#Override
public void updateItem(PayString item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(getTreeItem().getGraphic());
if (
!getTreeItem().isLeaf()&&getTreeItem().getParent()!= null
){
setContextMenu(addMenu);
}
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
commitEdit(new PayString (textField.getText(),3,next));
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
public static class Employee {
private final SimpleStringProperty name;
private final SimpleStringProperty department;
private final Integer id;
private Employee(Integer id, String name, String department) {
this.name = new SimpleStringProperty(name);
this.department = new SimpleStringProperty(department);
this.id = new Integer(id);
}
public Integer getId() {
return id;
}
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);
}
}
}
package treeviewsample;
import java.util.Objects;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* #author Len
*/
public class PayString {
private final String description;
private final Integer level;
private final Integer id;
public PayString(String description, Integer level, Integer id) {
this.id = id;
this.level = level;
this.description = description;
}
public int getId() {
return id;
}
public int getLevel() {
return level;
}
public String getDescription() {
return description;
}
#Override
public int hashCode() {
int hash = 7;
return hash;
}
public boolean contentEquals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PayString other = (PayString) obj;
if (!Objects.equals(this.description, other.description)) {
return false;
}
return true;
}
#Override
public String toString() {
return description;
}
}
`
You can always access the current data associated with your cell by calling
PayString value = getTreeItem().getValue()
However there is no 1:1 relationship between a cell and its value. JavaFX creates as many TreeCells it needs to display. It then dynamically assigns them to the underlying data items by calling the method
public void updateItem(PayString item, boolean empty) { ... }
This is where you can set a context menu that depends on the current data item. And there you get the data item passed in as parameter anyway.
At the code position you marked current data item will be null.

Resources