在阅读了大量涉及setOnEditCommit的可编辑TableView解决方案之后,我今天对Oracle非常生气,这不是正确的方法 .
以下是我在挖掘JavaFX源代码后发现的更好,更简单的解决方案:
我认为您的解决方案似乎比使用 setOnEditCommit 复杂得多 . 例如(使用Oracle使用的常用联系表类型示例),给定标准 Person JavaBean:
setOnEditCommit
Person
public class Person { private String firstName ; private String lastName ; public Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } @Override public String toString() { return firstName + " " + lastName ; } }
此代码创建一个可更新Java bean的可编辑表:
import javafx.application.Application; import javafx.beans.property.ReadOnlyStringWrapper; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.TextFieldTableCell; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.stage.Stage; public class Main extends Application { @Override public void start(Stage primaryStage) { BorderPane root = new BorderPane(); TableView<Person> table = new TableView<>(); TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name"); firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn()); firstNameCol.setOnEditCommit( event -> event.getRowValue().setFirstName(event.getNewValue())); firstNameCol.setCellValueFactory(data -> new ReadOnlyStringWrapper(data.getValue().getFirstName())); TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name"); lastNameCol.setCellFactory(TextFieldTableCell.forTableColumn()); lastNameCol.setOnEditCommit( event -> event.getRowValue().setLastName(event.getNewValue())); lastNameCol.setCellValueFactory(data -> new ReadOnlyStringWrapper(data.getValue().getLastName())); table.getColumns().addAll(firstNameCol, lastNameCol); table.setEditable(true); Button button = new Button("Show data"); button.setOnAction(event -> table.getItems().forEach(System.out::println)); HBox controls = new HBox(5, button); root.setCenter(table); root.setBottom(controls); table.getItems().addAll( new Person("Jacob", "Smith"), new Person("Isabella", "Johnson"), new Person("Ethan", "Williams"), new Person("Emma", "Jones"), new Person("Michael", "Brown") ); Scene scene = new Scene(root, 600, 400); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } }
此代码还避免了任何非公共API类 . 我同意构建器类更优雅,但它们已经deprecated for good reason .
此外,与 WritablePropertyValueFactory 类(或 PropertyValueFactory API类)不同,这也避免了反射,因此它可能表现更好 .
WritablePropertyValueFactory
PropertyValueFactory
BorderPaneBuilder.create() .top(ToolBarBuilder.create() .items(ButtonBuilder.create() .text("Add New Row") .onAction(e -> backendItemList.add(new MyItem("<NEW>", "<NEW>"))) .build()) .build()) .center( TableViewBuilder.<CustomColumnNameMapping> create() .items(backendItemList) .editable(true) .columns( TableColumnBuilder.<CustomColumnNameMapping, String> create() .text("Column1 for property1") .cellValueFactory(new WritablePropertyValueFactory<>("property1")) .cellFactory(TextFieldTableCell.forTableColumn()) .editable(true) .build(), TableColumnBuilder.<CustomColumnNameMapping, String> create() .text("Column2 for property2") .cellValueFactory(new WritablePropertyValueFactory<>("property2")) .cellFactory(TextFieldTableCell.forTableColumn()) .editable(true) .build()) .build()) .build();
import javafx.beans.NamedArg; import javafx.beans.value.ObservableValue; import javafx.scene.control.TableColumn.CellDataFeatures; import javafx.scene.control.cell.PropertyValueFactory; import javafx.util.Callback; import sun.util.logging.PlatformLogger; import sun.util.logging.PlatformLogger.Level; import com.sun.javafx.property.PropertyReference; import com.sun.javafx.scene.control.Logging; // Original code from PropertyValueFactory // Replacing ReadOnlyObjectWrapper with new ReadableWritableObservableValue public class WritablePropertyValueFactory<S, T> implements Callback<CellDataFeatures<S, T>, ObservableValue<T>> { private final String property; private Class<?> columnClass; private String previousProperty; private PropertyReference<T> propertyRef; public WritablePropertyValueFactory(@NamedArg("property") String property) { this.property = property; } @Override @SuppressWarnings("unchecked") public ObservableValue<T> call(CellDataFeatures<S, T> param) { return getCellDataReflectively((T) param.getValue()); } public final String getProperty() { return this.property; } private ObservableValue<T> getCellDataReflectively(T rowData) { if (getProperty() == null || getProperty().isEmpty() || rowData == null) return null; try { if (this.columnClass == null || this.previousProperty == null || !this.columnClass.equals(rowData.getClass()) || !this.previousProperty.equals(getProperty())) { this.columnClass = rowData.getClass(); this.previousProperty = getProperty(); this.propertyRef = new PropertyReference<T>(rowData.getClass(), getProperty()); } if (this.propertyRef.hasProperty()) { return this.propertyRef.getProperty(rowData); } else { // Create ReadableWritableObservableValue instead of ReadOnlyObjectWrapper return new ReadableWritableObservableValue<T>( () -> this.propertyRef.get(rowData), (value) -> this.propertyRef.set(rowData, value)); } } catch (IllegalStateException e) { final PlatformLogger logger = Logging.getControlsLogger(); if (logger.isLoggable(Level.WARNING)) { logger.finest("Can not retrieve property '" + getProperty() + "' in PropertyValueFactory: " + this + " with provided class type: " + rowData.getClass(), e); } } return null; } }
import java.util.function.Consumer; import java.util.function.Supplier; import javafx.beans.InvalidationListener; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.beans.value.WritableValue; public class ReadableWritableObservableValue<T> implements ObservableValue<T>, WritableValue<T> { protected final Supplier<T> getter; protected final Consumer<T> setter; public ReadableWritableObservableValue(Supplier<T> getter, Consumer<T> setter) { this.getter = getter; this.setter = setter; } @Override public void addListener(InvalidationListener listener) { // useless (no property to listen) } @Override public void removeListener(InvalidationListener listener) { // useless (no property to listen) } @Override public void addListener(ChangeListener<? super T> listener) { // useless (no property to listen) } @Override public void removeListener(ChangeListener<? super T> listener) { // useless (no property to listen) } @Override public T getValue() { return this.getter.get(); } @Override public void setValue(T value) { this.setter.accept(value); } }
PS:关键是从回调中返回WritableValue,参见TableColumn#DEFAULT_EDIT_COMMIT_HANDLER .
2 回答
我认为您的解决方案似乎比使用
setOnEditCommit
复杂得多 . 例如(使用Oracle使用的常用联系表类型示例),给定标准Person
JavaBean:此代码创建一个可更新Java bean的可编辑表:
此代码还避免了任何非公共API类 . 我同意构建器类更优雅,但它们已经deprecated for good reason .
此外,与
WritablePropertyValueFactory
类(或PropertyValueFactory
API类)不同,这也避免了反射,因此它可能表现更好 .要创建TableView:
WritablePropertyValueFactory.java:
ReadableWritableObservableValue.java
PS:关键是从回调中返回WritableValue,参见TableColumn#DEFAULT_EDIT_COMMIT_HANDLER .