Last active
May 20, 2023 19:20
-
-
Save haisi/0a82e17daf586c9bab52 to your computer and use it in GitHub Desktop.
Editable JavaFX TableView with textfield, datepicker and dropdown menu
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
/* | |
* Just copy and paste the code. | |
*/ | |
package editabletableview; | |
import java.time.LocalDate; | |
import java.time.ZoneId; | |
import java.time.format.DateTimeFormatter; | |
import java.time.format.FormatStyle; | |
import java.util.Date; | |
import java.util.List; | |
import javafx.application.Application; | |
import javafx.beans.property.BooleanProperty; | |
import javafx.beans.property.ObjectProperty; | |
import javafx.beans.property.SimpleListProperty; | |
import javafx.beans.property.SimpleObjectProperty; | |
import javafx.beans.property.SimpleStringProperty; | |
import javafx.beans.property.StringProperty; | |
import javafx.beans.value.ObservableValue; | |
import javafx.collections.FXCollections; | |
import javafx.collections.ObservableList; | |
import javafx.event.ActionEvent; | |
import javafx.geometry.Insets; | |
import javafx.scene.Group; | |
import javafx.scene.Scene; | |
import javafx.scene.control.Button; | |
import javafx.scene.control.CheckBox; | |
import javafx.scene.control.ComboBox; | |
import javafx.scene.control.DatePicker; | |
import javafx.scene.control.Label; | |
import javafx.scene.control.ListCell; | |
import javafx.scene.control.TableCell; | |
import javafx.scene.control.TableColumn; | |
import javafx.scene.control.TableView; | |
import javafx.scene.control.TextField; | |
import javafx.scene.layout.HBox; | |
import javafx.scene.layout.VBox; | |
import javafx.scene.text.Font; | |
import javafx.stage.Stage; | |
import javafx.util.Callback; | |
/** | |
* | |
* @author Hasan Selman Kara | |
*/ | |
public class Main extends Application { | |
private TableView<Person> table = new TableView<>(); | |
private final ObservableList<Typ> typData | |
= FXCollections.observableArrayList( | |
new Typ("Hund"), | |
new Typ("Fuchs"), | |
new Typ("Esel")); | |
private final ObservableList<Person> data | |
= FXCollections.observableArrayList( | |
new Person("Jacob", typData.get(0), new Date()), | |
new Person("Urs", typData.get(1), new Date()), | |
new Person("Hans", typData.get(2), new Date()), | |
new Person("Ueli", typData.get(2), new Date())); | |
final HBox hb = new HBox(); | |
@Override | |
public void start(Stage stage) { | |
Scene scene = new Scene(new Group()); | |
stage.setWidth(550); | |
stage.setHeight(550); | |
final Label label = new Label("Address Book"); | |
label.setFont(new Font("Arial", 20)); | |
table.setEditable(true); | |
Callback<TableColumn<Person, String>, TableCell<Person, String>> cellFactory | |
= (TableColumn<Person, String> param) -> new EditingCell(); | |
Callback<TableColumn<Person, Date>, TableCell<Person, Date>> dateCellFactory | |
= (TableColumn<Person, Date> param) -> new DateEditingCell(); | |
Callback<TableColumn<Person, Typ>, TableCell<Person, Typ>> comboBoxCellFactory | |
= (TableColumn<Person, Typ> param) -> new ComboBoxEditingCell(); | |
TableColumn<Person, String> firstNameCol = new TableColumn("Vorname"); | |
firstNameCol.setMinWidth(100); | |
firstNameCol.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty()); | |
firstNameCol.setCellFactory(cellFactory); | |
firstNameCol.setOnEditCommit( | |
(TableColumn.CellEditEvent<Person, String> t) -> { | |
((Person) t.getTableView().getItems() | |
.get(t.getTablePosition().getRow())) | |
.setFirstName(t.getNewValue()); | |
}); | |
TableColumn<Person, Typ> lastNameCol = new TableColumn("Lieblings Tier"); | |
lastNameCol.setMinWidth(100); | |
lastNameCol.setCellValueFactory(cellData -> cellData.getValue().typObjProperty()); | |
lastNameCol.setCellFactory(comboBoxCellFactory); | |
lastNameCol.setOnEditCommit( | |
(TableColumn.CellEditEvent<Person, Typ> t) -> { | |
((Person) t.getTableView().getItems() | |
.get(t.getTablePosition().getRow())) | |
.setTypObj(t.getNewValue()); | |
}); | |
TableColumn<Person, Date> emailCol = new TableColumn("Geburtstag"); | |
emailCol.setMinWidth(200); | |
emailCol.setCellValueFactory(cellData -> cellData.getValue().birthdayProperty()); | |
emailCol.setCellFactory(dateCellFactory); | |
emailCol.setOnEditCommit( | |
(TableColumn.CellEditEvent<Person, Date> t) -> { | |
((Person) t.getTableView().getItems() | |
.get(t.getTablePosition().getRow())) | |
.setBirthday(t.getNewValue()); | |
}); | |
table.setItems(data); | |
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol); | |
final TextField addFirstName = new TextField(); | |
addFirstName.setPromptText("First Name"); | |
addFirstName.setMaxWidth(firstNameCol.getPrefWidth()); | |
final TextField addLastName = new TextField(); | |
addLastName.setPromptText("Last Name"); | |
addLastName.setMaxWidth(lastNameCol.getPrefWidth()); | |
final TextField addEmail = new TextField(); | |
addEmail.setPromptText("email"); | |
addEmail.setMaxWidth(emailCol.getPrefWidth()); | |
final Button addButton = new Button("Add"); | |
addButton.setOnAction((ActionEvent e) | |
-> { | |
data.add(new Person( | |
addFirstName.getText(), | |
new Typ("Hund"), | |
new Date())); | |
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(); | |
} | |
/** | |
* @param args the command line arguments | |
*/ | |
public static void main(String[] args) { | |
launch(args); | |
} | |
class EditingCell extends TableCell<Person, String> { | |
private TextField textField; | |
private EditingCell() { | |
} | |
@Override | |
public void startEdit() { | |
if (!isEmpty()) { | |
super.startEdit(); | |
createTextField(); | |
setText(null); | |
setGraphic(textField); | |
textField.selectAll(); | |
} | |
} | |
@Override | |
public void cancelEdit() { | |
super.cancelEdit(); | |
setText((String) getItem()); | |
setGraphic(null); | |
} | |
@Override | |
public void updateItem(String item, boolean empty) { | |
super.updateItem(item, empty); | |
if (empty) { | |
setText(item); | |
setGraphic(null); | |
} else { | |
if (isEditing()) { | |
if (textField != null) { | |
textField.setText(getString()); | |
// setGraphic(null); | |
} | |
setText(null); | |
setGraphic(textField); | |
} else { | |
setText(getString()); | |
setGraphic(null); | |
} | |
} | |
} | |
private void createTextField() { | |
textField = new TextField(getString()); | |
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2); | |
textField.setOnAction((e) -> commitEdit(textField.getText())); | |
textField.focusedProperty().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { | |
if (!newValue) { | |
System.out.println("Commiting " + textField.getText()); | |
commitEdit(textField.getText()); | |
} | |
}); | |
} | |
private String getString() { | |
return getItem() == null ? "" : getItem(); | |
} | |
} | |
class DateEditingCell extends TableCell<Person, Date> { | |
private DatePicker datePicker; | |
private DateEditingCell() { | |
} | |
@Override | |
public void startEdit() { | |
if (!isEmpty()) { | |
super.startEdit(); | |
createDatePicker(); | |
setText(null); | |
setGraphic(datePicker); | |
} | |
} | |
@Override | |
public void cancelEdit() { | |
super.cancelEdit(); | |
setText(getDate().toString()); | |
setGraphic(null); | |
} | |
@Override | |
public void updateItem(Date item, boolean empty) { | |
super.updateItem(item, empty); | |
if (empty) { | |
setText(null); | |
setGraphic(null); | |
} else { | |
if (isEditing()) { | |
if (datePicker != null) { | |
datePicker.setValue(getDate()); | |
} | |
setText(null); | |
setGraphic(datePicker); | |
} else { | |
setText(getDate().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM))); | |
setGraphic(null); | |
} | |
} | |
} | |
private void createDatePicker() { | |
datePicker = new DatePicker(getDate()); | |
datePicker.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2); | |
datePicker.setOnAction((e) -> { | |
System.out.println("Committed: " + datePicker.getValue().toString()); | |
commitEdit(Date.from(datePicker.getValue().atStartOfDay(ZoneId.systemDefault()).toInstant())); | |
}); | |
// datePicker.focusedProperty().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { | |
// if (!newValue) { | |
// commitEdit(Date.from(datePicker.getValue().atStartOfDay(ZoneId.systemDefault()).toInstant())); | |
// } | |
// }); | |
} | |
private LocalDate getDate() { | |
return getItem() == null ? LocalDate.now() : getItem().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); | |
} | |
} | |
class ComboBoxEditingCell extends TableCell<Person, Typ> { | |
private ComboBox<Typ> comboBox; | |
private ComboBoxEditingCell() { | |
} | |
@Override | |
public void startEdit() { | |
if (!isEmpty()) { | |
super.startEdit(); | |
createComboBox(); | |
setText(null); | |
setGraphic(comboBox); | |
} | |
} | |
@Override | |
public void cancelEdit() { | |
super.cancelEdit(); | |
setText(getTyp().getTyp()); | |
setGraphic(null); | |
} | |
@Override | |
public void updateItem(Typ item, boolean empty) { | |
super.updateItem(item, empty); | |
if (empty) { | |
setText(null); | |
setGraphic(null); | |
} else { | |
if (isEditing()) { | |
if (comboBox != null) { | |
comboBox.setValue(getTyp()); | |
} | |
setText(getTyp().getTyp()); | |
setGraphic(comboBox); | |
} else { | |
setText(getTyp().getTyp()); | |
setGraphic(null); | |
} | |
} | |
} | |
private void createComboBox() { | |
comboBox = new ComboBox<>(typData); | |
comboBoxConverter(comboBox); | |
comboBox.valueProperty().set(getTyp()); | |
comboBox.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2); | |
comboBox.setOnAction((e) -> { | |
System.out.println("Committed: " + comboBox.getSelectionModel().getSelectedItem()); | |
commitEdit(comboBox.getSelectionModel().getSelectedItem()); | |
}); | |
// comboBox.focusedProperty().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { | |
// if (!newValue) { | |
// commitEdit(comboBox.getSelectionModel().getSelectedItem()); | |
// } | |
// }); | |
} | |
private void comboBoxConverter(ComboBox<Typ> comboBox) { | |
// Define rendering of the list of values in ComboBox drop down. | |
comboBox.setCellFactory((c) -> { | |
return new ListCell<Typ>() { | |
@Override | |
protected void updateItem(Typ item, boolean empty) { | |
super.updateItem(item, empty); | |
if (item == null || empty) { | |
setText(null); | |
} else { | |
setText(item.getTyp()); | |
} | |
} | |
}; | |
}); | |
} | |
private Typ getTyp() { | |
return getItem() == null ? new Typ("") : getItem(); | |
} | |
} | |
public static class Typ { | |
private final SimpleStringProperty typ; | |
public Typ(String typ) { | |
this.typ = new SimpleStringProperty(typ); | |
} | |
public String getTyp() { | |
return this.typ.get(); | |
} | |
public StringProperty typProperty() { | |
return this.typ; | |
} | |
public void setTyp(String typ) { | |
this.typ.set(typ); | |
} | |
@Override | |
public String toString() { | |
return typ.get(); | |
} | |
} | |
public static class Project { | |
private final SimpleStringProperty name; | |
private final SimpleListProperty<Person> persons; | |
public Project(String name, List<Person> persons) { | |
this.name = new SimpleStringProperty(name); | |
this.persons = new SimpleListProperty<>(); | |
this.persons.setAll(persons); | |
} | |
public String getName() { | |
return name.get(); | |
} | |
public StringProperty nameProperty() { | |
return this.name; | |
} | |
public void setName(String name) { | |
this.name.set(name); | |
} | |
public List<Person> getPersons() { | |
return this.persons.get(); | |
} | |
public SimpleListProperty<Person> personsProperty() { | |
return this.persons; | |
} | |
public void setPersons(List<Person> persons) { | |
this.persons.setAll(persons); | |
} | |
} | |
public static class Person { | |
private final SimpleStringProperty firstName; | |
private final SimpleObjectProperty<Typ> typ; | |
private final SimpleObjectProperty<Date> birthday; | |
public Person(String firstName, Typ typ, Date bithday) { | |
this.firstName = new SimpleStringProperty(firstName); | |
this.typ = new SimpleObjectProperty(typ); | |
this.birthday = new SimpleObjectProperty(bithday); | |
} | |
public String getFirstName() { | |
return firstName.get(); | |
} | |
public StringProperty firstNameProperty() { | |
return this.firstName; | |
} | |
public void setFirstName(String firstName) { | |
this.firstName.set(firstName); | |
} | |
public Typ getTypObj() { | |
return typ.get(); | |
} | |
public ObjectProperty<Typ> typObjProperty() { | |
return this.typ; | |
} | |
public void setTypObj(Typ typ) { | |
this.typ.set(typ); | |
} | |
public Date getBirthday() { | |
return birthday.get(); | |
} | |
public ObjectProperty<Date> birthdayProperty() { | |
return this.birthday; | |
} | |
public void setBirthday(Date birthday) { | |
this.birthday.set(birthday); | |
} | |
} | |
} |
I wonder how much time it took you to type it all
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
i have build table cell edit based upon your code. i have used editable combobox in my code as table cell
I'd like to create a table with following features
Edit on key pressed
Enter key = next Row
Tab Key = next Column
Escape Key = Cancel Edit
The Window Looks As Follows
The main problem that arises when we focus on combo box the focus moves out of the table. I want a solution that the focus remains in the table or to next cell in the table and commits the selected value of the combo box. The combo box is of the type Item
ComboBox<Item> items = new ComboBox<>();
The Table is as follows
The purchase class is as follows
The Item Class is as Follows
The purchase model class
The Controller Class is as follows
Please tell me a way to implement all the four points that i have mentioned above.
this question is been asked in stackoverflow. For comments and solution please visit:-
https://stackoverflow.com/questions/74180705/how-to-work-with-javafx-tableview-with-custom-table-cell-using-editable-combobox