【发布时间】:2023-03-11 16:12:01
【问题描述】:
在阅读了许多涉及 setOnEditCommit 的可编辑 TableView 解决方案后,我今天对 Oracle 非常生气,这不是正确的做法。
以下是我在挖掘 JavaFX 源代码后发现的更好、更简单的解决方案:
【问题讨论】:
在阅读了许多涉及 setOnEditCommit 的可编辑 TableView 解决方案后,我今天对 Oracle 非常生气,这不是正确的做法。
以下是我在挖掘 JavaFX 源代码后发现的更好、更简单的解决方案:
【问题讨论】:
我认为您的解决方案似乎比使用setOnEditCommit 复杂得多。例如(使用 Oracle 使用的常用联系表类型示例),给定一个标准的Person JavaBean:
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 类)不同,因此它的性能可能更好。
【讨论】:
firstNameCol.setCellValueFactory( data -> JavaBeanStringPropertyBuilder.create().bean(data.getValue()).name("firstName").build());,这也消除了“这是编辑完成后要执行的操作”代码。不过,我还没有考虑过这是否会造成内存泄漏。
.name() 部分闻起来像“魔术串”,尽可能避免。
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。
【讨论】:
PropertyReference,例如例如),这很可能在未来的版本中被删除。
setOnEditCommit 复杂得多:看我的回答。您对标准解决方案有何异议?