无法概括JavaFX的TableColumn的一些通用代码
Failed to generalize some common codes for TableColumn of JavaFX
在 this example for TableView of JavaFX 中,TableColumn
的 3 个实例几乎都在做同样的事情。于是我想写一个新的classSuperColumn extends TableColumn
来处理TableColumn
的例程。因此,我尝试将通用代码放在构造函数中,如下所示:
public class SuperColumn<Person, V> extends TableColumn<Person, V>{
String columnHead;
int columnWidth;
String variableName;
public SuperColumn(String columnHead, int columnWidth, String variableName) {
super();
this.setText(columnHead);
this.setMinWidth(columnWidth);
this.setCellValueFactory(new PropertyValueFactory<Person, V>(variableName));
this.setOnEditCommit(
new EventHandler<CellEditEvent<Person, V>>() {
@Override
public void handle(CellEditEvent<Person, V> t) {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setEmail(t.getNewValue());
}
});
}
}
在上面,我想让setEmail
依赖于variableNew
。也就是说,如果variableNew
是"setEmail"
,那么我们就调用setEmail(t.getNewValue())
。如果variableNew
是"setFirstName"
,那么我们会调用setFirstName(t.getNewValue())
.
我怎样才能做到这一点?
即使只是上面的代码,我也收到错误消息“方法 setEmail(V) 未定义类型 Person”。
public class SuperColumn<Person, V> extends TableColumn<Person, V>{
public SuperColumn(String columnHead, int columnWidth, String variableName) {
super();
this.setText(columnHead);
this.setMinWidth(columnWidth);
this.setCellValueFactory(new PropertyValueFactory<>(variableName));
this.setOnEditCommit(t -> {
int row = t.getTablePosition().getRow();
Person person = t.getTableView().getItems().get(row);
V value = t.getNewValue();
try {
Method method = person.getClass().getMethod("set" + variableName, value.getClass());
method.invoke(person, value);
} catch ( NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
});
}
}
并称它为:
SuperColumn<Person, String> column = new SuperColumn<>("Email", 100, "email");
就一般编程风格而言,subclass a class 仅用于配置创建的实例并不是特别好的做法,除非您提供额外的功能基地 class 不提供。如果 subclass 除了有一个构造函数之外什么都不做,这就是这种反模式的一个特殊标志。一种更好的方法(我将在此处展示)是使用某种创建模式,它可能与一些为您创建实例的静态方法一样简单。 (更高级的实现将使用各种工厂模式。)
无论如何,你想要做的事情的一个版本,它不依赖于反射(我稍后会解释为什么我不喜欢反射方法)需要你传递某种处理特定 Person
实例的新(编辑)值的函数。 BiConsumer<Person, V>
适用于此:
public class TableColumns {
public static <V> TableColumn<Person,V> create(String columnHead, int columnWidth, String variableName, BiConsumer<Person, V> setter) {
TableColumn<Person, V> column = new TableColumn<>(columnHead);
column.setMinWidth(columnWidth);
column.setCellValueFactory(new PropertyValueFactory<>(variableName));
column.setOnEditCommit(t -> {
int row = t.getTablePosition().getRow();
Person person = t.getTableView().getItems().get(row);
V value = t.getNewValue();
setter.consume(person, value);
});
return column ;
}
}
您将使用
创建
TableColumn<Person, String> emailColumn =
TableColumns.create("Email", 100, "email", Person::setEmail)
请注意,您链接的示例并不是一个特别好的示例(即使它是来自 Oracle 的“官方”示例:它确实非常古老)。使用 JavaFX 属性的模型 class (Person
) 应该真正提供“属性 访问器”方法:
public class Person {
private final StringProperty email = new SimpleStringProperty();
public StringProperty emailProperty() {
return email ;
}
public final String getEmail() {
return emailProperty().get();
}
public final void setEmail(String email) {
emailProperty().set(email);
}
// similarly for other properties
}
使用此版本的 Person
class,您的 table 列只需访问特定的 属性 即可获取值(在 cellValueFactory
) 和设置值(在 onEditCommit
处理程序中)。因此,您所需要的只是一个为给定 Person
实例提供 属性 的函数。您还可以轻松地将其概括为 S
:
类型的参数化行值
public class TableColumns {
public static <S,T> TableColumn<S,T> create(String title, int width, Function<S, Property<T>> property) {
TableColumn<S,T> column = new TableColumn<>(title);
column.setMinWidth(width);
column.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
column.setOnEditCommit(t -> {
int row = t.getTablePosition().getRow();
S rowValue = t.getTableView().getItems().get(row);
T value = t.getNewValue();
property.apply(rowValue).setValue(value);
});
return column ;
}
// other creational methods...
}
现在你可以
TableColumn<Person, String> emailColumn =
TableColumns.create("Email", 100, Person::emailProperty);
这种方法既避免了直接使用反射,也避免了使用遗留的 PropertyValueFactory
(它在幕后使用类似的基于反射的方法)。这里的主要好处是编译器现在将检查是否存在适当的方法,并且在编译时捕获任何错误。 (相比之下,当使用反射指定方法名称时,或 PropertyValueFactory
中的 属性 名称时,错误无法被编译器捕获,它只会在运行时失败,要么静默(没有数据出现)或有例外。)
只是为了完整起见,如果你真的想为此使用子class,它看起来像
public class SuperColumn<S,T> extends TableColumn<S,T> {
public SuperColumn(String title, int width, Function<S, Property<T>> property) {
super(title);
setMinWidth(width);
setCellValueFactory(cellData -> property.apply(cellData.getValue()));
setOnEditCommit(event -> {
int row = event.getTablePosition().getRow();
S rowValue = event.getTableView().getItems().get(row);
T value = event.getNewValue();
property.apply(rowValue).setValue(value);
});
}
}
但我要再次强调,这不是继承的好用法,您应该更喜欢基于方法(或基于工厂)的方法。
最后我设法结合了 Lê Hoàng Dững 的想法(即使用 Function<S, Property<T>>
)和 James_D(即使用 TableColumns.Create()
),我得到:
import java.util.function.Function;
import javafx.beans.property.Property;
import javafx.scene.control.TableColumn;
public class TableColumns<S,T> {
public static <S, T> TableColumn<S,T> create(String columnHead, int columnWidth, Function<S, Property<T>> myFunc) {
TableColumn<S, T> myColumn = new TableColumn<>(columnHead);
myColumn.setMinWidth(columnWidth);
myColumn.setCellValueFactory(cellData -> myFunc.apply(cellData.getValue())); // binding column (i.e. this) and the variable
myColumn.setOnEditCommit(event -> {
int row = event.getTablePosition().getRow();
S rowValue = event.getTableView().getItems().get(row);
T newValue = event.getNewValue();
myFunc.apply(rowValue).setValue(newValue);
});
return myColumn;
}
}
在 this example for TableView of JavaFX 中,TableColumn
的 3 个实例几乎都在做同样的事情。于是我想写一个新的classSuperColumn extends TableColumn
来处理TableColumn
的例程。因此,我尝试将通用代码放在构造函数中,如下所示:
public class SuperColumn<Person, V> extends TableColumn<Person, V>{
String columnHead;
int columnWidth;
String variableName;
public SuperColumn(String columnHead, int columnWidth, String variableName) {
super();
this.setText(columnHead);
this.setMinWidth(columnWidth);
this.setCellValueFactory(new PropertyValueFactory<Person, V>(variableName));
this.setOnEditCommit(
new EventHandler<CellEditEvent<Person, V>>() {
@Override
public void handle(CellEditEvent<Person, V> t) {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setEmail(t.getNewValue());
}
});
}
}
在上面,我想让setEmail
依赖于variableNew
。也就是说,如果variableNew
是"setEmail"
,那么我们就调用setEmail(t.getNewValue())
。如果variableNew
是"setFirstName"
,那么我们会调用setFirstName(t.getNewValue())
.
我怎样才能做到这一点?
即使只是上面的代码,我也收到错误消息“方法 setEmail(V) 未定义类型 Person”。
public class SuperColumn<Person, V> extends TableColumn<Person, V>{
public SuperColumn(String columnHead, int columnWidth, String variableName) {
super();
this.setText(columnHead);
this.setMinWidth(columnWidth);
this.setCellValueFactory(new PropertyValueFactory<>(variableName));
this.setOnEditCommit(t -> {
int row = t.getTablePosition().getRow();
Person person = t.getTableView().getItems().get(row);
V value = t.getNewValue();
try {
Method method = person.getClass().getMethod("set" + variableName, value.getClass());
method.invoke(person, value);
} catch ( NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
});
}
}
并称它为:
SuperColumn<Person, String> column = new SuperColumn<>("Email", 100, "email");
就一般编程风格而言,subclass a class 仅用于配置创建的实例并不是特别好的做法,除非您提供额外的功能基地 class 不提供。如果 subclass 除了有一个构造函数之外什么都不做,这就是这种反模式的一个特殊标志。一种更好的方法(我将在此处展示)是使用某种创建模式,它可能与一些为您创建实例的静态方法一样简单。 (更高级的实现将使用各种工厂模式。)
无论如何,你想要做的事情的一个版本,它不依赖于反射(我稍后会解释为什么我不喜欢反射方法)需要你传递某种处理特定 Person
实例的新(编辑)值的函数。 BiConsumer<Person, V>
适用于此:
public class TableColumns {
public static <V> TableColumn<Person,V> create(String columnHead, int columnWidth, String variableName, BiConsumer<Person, V> setter) {
TableColumn<Person, V> column = new TableColumn<>(columnHead);
column.setMinWidth(columnWidth);
column.setCellValueFactory(new PropertyValueFactory<>(variableName));
column.setOnEditCommit(t -> {
int row = t.getTablePosition().getRow();
Person person = t.getTableView().getItems().get(row);
V value = t.getNewValue();
setter.consume(person, value);
});
return column ;
}
}
您将使用
创建TableColumn<Person, String> emailColumn =
TableColumns.create("Email", 100, "email", Person::setEmail)
请注意,您链接的示例并不是一个特别好的示例(即使它是来自 Oracle 的“官方”示例:它确实非常古老)。使用 JavaFX 属性的模型 class (Person
) 应该真正提供“属性 访问器”方法:
public class Person {
private final StringProperty email = new SimpleStringProperty();
public StringProperty emailProperty() {
return email ;
}
public final String getEmail() {
return emailProperty().get();
}
public final void setEmail(String email) {
emailProperty().set(email);
}
// similarly for other properties
}
使用此版本的 Person
class,您的 table 列只需访问特定的 属性 即可获取值(在 cellValueFactory
) 和设置值(在 onEditCommit
处理程序中)。因此,您所需要的只是一个为给定 Person
实例提供 属性 的函数。您还可以轻松地将其概括为 S
:
public class TableColumns {
public static <S,T> TableColumn<S,T> create(String title, int width, Function<S, Property<T>> property) {
TableColumn<S,T> column = new TableColumn<>(title);
column.setMinWidth(width);
column.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
column.setOnEditCommit(t -> {
int row = t.getTablePosition().getRow();
S rowValue = t.getTableView().getItems().get(row);
T value = t.getNewValue();
property.apply(rowValue).setValue(value);
});
return column ;
}
// other creational methods...
}
现在你可以
TableColumn<Person, String> emailColumn =
TableColumns.create("Email", 100, Person::emailProperty);
这种方法既避免了直接使用反射,也避免了使用遗留的 PropertyValueFactory
(它在幕后使用类似的基于反射的方法)。这里的主要好处是编译器现在将检查是否存在适当的方法,并且在编译时捕获任何错误。 (相比之下,当使用反射指定方法名称时,或 PropertyValueFactory
中的 属性 名称时,错误无法被编译器捕获,它只会在运行时失败,要么静默(没有数据出现)或有例外。)
只是为了完整起见,如果你真的想为此使用子class,它看起来像
public class SuperColumn<S,T> extends TableColumn<S,T> {
public SuperColumn(String title, int width, Function<S, Property<T>> property) {
super(title);
setMinWidth(width);
setCellValueFactory(cellData -> property.apply(cellData.getValue()));
setOnEditCommit(event -> {
int row = event.getTablePosition().getRow();
S rowValue = event.getTableView().getItems().get(row);
T value = event.getNewValue();
property.apply(rowValue).setValue(value);
});
}
}
但我要再次强调,这不是继承的好用法,您应该更喜欢基于方法(或基于工厂)的方法。
最后我设法结合了 Lê Hoàng Dững 的想法(即使用 Function<S, Property<T>>
)和 James_D(即使用 TableColumns.Create()
),我得到:
import java.util.function.Function;
import javafx.beans.property.Property;
import javafx.scene.control.TableColumn;
public class TableColumns<S,T> {
public static <S, T> TableColumn<S,T> create(String columnHead, int columnWidth, Function<S, Property<T>> myFunc) {
TableColumn<S, T> myColumn = new TableColumn<>(columnHead);
myColumn.setMinWidth(columnWidth);
myColumn.setCellValueFactory(cellData -> myFunc.apply(cellData.getValue())); // binding column (i.e. this) and the variable
myColumn.setOnEditCommit(event -> {
int row = event.getTablePosition().getRow();
S rowValue = event.getTableView().getItems().get(row);
T newValue = event.getNewValue();
myFunc.apply(rowValue).setValue(newValue);
});
return myColumn;
}
}