首页 > 解决方案 > JavaFX的TableColumn的一些常用代码无法概括

问题描述

JavaFX 的 TableView 的这个示例中, 3 个实例TableColumn几乎都在做同样的事情。因此,我想编写一个新类SuperColumn 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 类型定义”。

标签: javagenericsinheritancejavafxtypes

解决方案


仅就一般编程风格而言,仅为了配置所创建实例的目的而对类进行子类化并不是特别好的做法,除非您提供了基类未提供的附加功能。如果子类除了有一个构造函数之外什么都不做,这是这个反模式的一个特定指标。我将在此处展示的一种更好的方法是使用某种创建模式,它可能与为您创建实例的一些静态方法一样简单。(更高级的实现将使用各种工厂模式。)

无论如何,你正在尝试做的一个版本,它不依赖于反射(我稍后会解释为什么我不喜欢反射方法)需要你传入某种处理新的函数(已编辑)特定Person实例的值。ABiConsumer<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 属性的模型类 ( 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类,您的表列只需访问特定属性即可获取值(在 中cellValueFactory)和设置值(在onEditCommit处理程序中)。因此,您只需要一个为给定Person实例提供属性的函数。您还可以轻松地将其概括为 type 的参数化行值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或 .

只是为了完整性,如果你真的想为此使用一个子类,它看起来像

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);
        });
    }
}

但再次强调,这不是很好地使用继承,您应该更喜欢基于方法(或基于工厂)的方法。


推荐阅读