JavaFX TableView 访问聚合对象

问题描述

public class C{
B b;
A a;
int d;
String str;
}

A 类和 B 类有自己的变量。 我有这样的代码,我想在 TableView 中使用 C 类,如何访问 A 类和 B 类中的值?我必须在 column..setCellValueFactory(new PropertyValueFactory<>("???"));TableColumn<???,String> column = new TableColumn<>("Example");

中写什么

解决方法

PropertyValueFactory 是一个没人应该使用的类。它依赖于反射,这意味着如果你犯了一个错误,编译器不能通知你你的错误。它还具有默默吞下任何异常的不愉快习惯,这使得错误更难以检测。

A cell value factory is a Callback,在功能上与 Java SE Function 完全相同。任何接受 TableColumn.CellDataFeatures 作为参数并返回对应于该单元格中的 table item 的 ObservableValue 的方法或 lambda 都可以充当单元格值工厂。

因此,最好的做法是忘记 PropertyValueFactory,而是传递一个简单的 lambda。

做到这一点的最佳方法是确保您的每个数据对象的属性都采用 JavaFX 属性的样式。如果您曾经看过 JavaFX 中的 documentation for NodeWindow 或大多数其他类,您可能已经注意到,虽然普通的 JavaBean 对每个属性都有两个方法(get-method 和set-method),一个JavaFX对象通常有3个方法:

  • 获取方法
  • 设置方法
  • 一个属性访问器方法,它返回 Property 本身,而不是属性值

一些例子:

您将希望在数据对象中做同样的事情。

为了清楚起见,我将使用 Person 和 Address 而不是 A、B 和 C:

public class Address {
    private final StringProperty street = new SimpleStringProperty();
    private final StringProperty postalCode = new SimpleStringProperty();

    public StringProperty streetProperty() {
        return street;
    }

    public String getStreet() {
        return street.get();
    }

    public void setStreet(String newStreet) {
        this.street.set(newStreet);
    }

    public StringProperty postalCodeProperty() {
        return postalCode;
    }

    public String getPostalCode() {
        return postalCode.get();
    }

    public void setPostalCode(String code) {
        this.postalCode.set(code);
    }
}

public class Person {
    private final StringProperty name = new SimpleStringProperty();
    private final ObjectProperty<LocalDate> dateOfBirth = new SimpleObjectProperty<>();
    private final ObjectProperty<Address> address = new SimpleObjectProperty<>();

    public StringProperty nameProperty() { 
        return name;
    }

    public String getName() {
        return name.get();
    }

    public void setName(String newName) {
        this.name.set(newName);
    }

    public ObjectProperty<LocalDate> dateOfBirthProperty() {
        return dateOfBirth;
    }

    public LocalDate getDateOfBirth() {
        return dateOfBirth.get();
    }

    public void setDateOfBirth(LocalDate date) {
        this.dateOfBirth.set(date);
    }

    public ObjectProperty<Address> addressProperty() {
        return address;
    }

    public Address getAddress() {
        return address.get();
    }

    public void setAddress(Address newAddress) {
        this.address.set(newAddress);
    }
}

每个属性都实现了 ObservableValue,所以现在你的单元格数据值可以是属性对象本身:

TableColumn<Person,String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(cell -> cell.getValue().nameProperty());

由于上面的代码没有使用反射,编译器可以保证你显示的是合法的值。

为了解决您的原始问题,如果您想显示子对象的值,您可以使用 Bindings 的 select* 方法之一:

TableColumn<Person,String> streetColumn = new TableColumn<>();
streetColumn.setCellValueFactory(
    cell -> Bindings.selectString(cell.getValue().addressProperty(),"street"));

这确实有使用反射的缺点(虽然there is an open bug about that),但至少父对象会被编译器检查。