TableView does not display the content of Obser

2020-04-20 06:10发布

问题:

I have two almost similar custom classes for storing simple String data - they are called "Patient" and "Trace".They differ from eachother only by the number of defined fields. Constructors of both are shown below (with getVariablesNames() static method):

public class Patient {

    String patientID;
    String firstName;
    String lastName;
    String gender;
    String dateOfBirth;
    String age;
    String email;
    String phoneNumber;
    String city;

    public Patient(String patientID, String firstName, String lastName, String gender, String dateOfBirth, String age, String email, String phoneNumber, String city) {
        this.patientID = patientID;
        this.firstName = firstName;
        this.lastName = lastName;
        this.gender = gender;
        this.dateOfBirth = dateOfBirth;
        this.age = age;
        this.email = email;
        this.phoneNumber = phoneNumber;
        this.city = city;
    }

    public static String[] getVariablesNames() {
        Field[] fields = Patient.class.getDeclaredFields();
        String[] variablesNames = new String[fields.length];
        for (int i = 0; i < fields.length; i++) {
            variablesNames[i] = fields[i].getName();
        }
        return variablesNames;
    }

    public String getPatientID() {
        return patientID;
    }

    public void setPatientID(String value) {
        patientID = value;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String value) {
        firstName = value;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String value) {
        lastName = value;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String value) {
        gender = value;
    }

    public String getDateOfBirth() {
        return dateOfBirth;
    }

    public void setDateOfBirth(String value) {
        dateOfBirth = value;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String value) {
        age = value;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String value) {
        email = value;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String value) {
        phoneNumber = value;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String value) {
        city = value;
    }
}

And constructor for "Trace" class:

public class Trace {

    String action;
    String status;
    String message;
    String time;

    public Trace(String action, String status, String message, String time) {
        this.action = action;
        this.status = status;
        this.message = message;
        this.time = time;
    }

    public static String[] getVariablesNames() {
        Field[] fields = Trace.class.getDeclaredFields();
        String[] variablesNames = new String[fields.length];
        for (int i = 0; i < fields.length; i++) {
            variablesNames[i] = fields[i].getName();
        }
        return variablesNames;
    }

    public void setActionText(String value) {
        action = value;
    }

    public String getActionText() {
        return action;
    }

    public void setStatusText(String value) {
        status = value;
    }

    public String getStatusText() {
        return status;
    }

    public void setMessageText(String value) {
        message = value;
    }

    public String getMessageText() {
        return message;
    }

    public void setTimeText(String value) {
        time = value;
    }

    public String getTimeText() {
        return time;
    }
}

I use objects of those classes to populate custom TableView<T> instances, where <T> can be "Patient" or "Trace". The problem I encounter is that "Trace" object values are not displayed in the table, where there are no problems with objects of the "Patient" class. The custom TableView<T> class is shown below:

import javafx.collections.ObservableList;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;

public class TableViewCustom<T> extends TableView<T> {

    public TableViewCustom(String[] columnNames, String[] variablesNames, ObservableList<T> data) {
        super();

        @SuppressWarnings("unchecked")
        TableColumn<T, String>[] tableColumns = new TableColumn[variablesNames.length];
        for (int i = 0; i < tableColumns.length; i++) {
            tableColumns[i] = new TableColumn<T, String>(columnNames[i]);
            tableColumns[i].setCellValueFactory(new PropertyValueFactory<T, String>(variablesNames[i]));
        }

        this.setItems(data);
        this.getColumns().addAll(tableColumns);

        this.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
    }
}

And the example implementation of this custom TableView with use of both "Patient" and "Trace" objects:

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Demo extends Application {

    public Parent createContent() {

        /* layout */
        BorderPane layout = new BorderPane();

        /* layout -> center */
        VBox tableViewsContainer = new VBox(5);

        /* layout -> center -> top */
        String[] patientColumnNames = new String[] {"Patient ID", "First Name", "Last Name", "Gender", "Date Of Birth", "Age", "Email", "Phone Number", "City"};
        String[] patientVariablesNames = Patient.getVariablesNames();
        ObservableList<Patient> patientData = FXCollections.observableArrayList(new Patient("Patient ID", "First Name", "Last Name", "Gender", "Date Of Birth", "Age", "Email", "Phone Number", "City"));
        TableViewCustom<Patient> patientTableView = new TableViewCustom<Patient>(patientColumnNames, patientVariablesNames, patientData);

        /* layout -> center -> bottom */
        String[] traceColumnNames = new String[] {"Action", "Status", "Message", "Time"};
        String[] traceVariablesNames = Trace.getVariablesNames();
        ObservableList<Trace> traceData = FXCollections.observableArrayList(new Trace("Action", "Status", "Message", "Time"));
        TableViewCustom<Trace> traceTableView = new TableViewCustom<Trace>(traceColumnNames, traceVariablesNames, traceData);

        /* add items to the layout */
        tableViewsContainer.getChildren().addAll(patientTableView, traceTableView);

        layout.setCenter(tableViewsContainer);
        return layout;
    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent()));
        stage.setWidth(700);
        stage.setHeight(400);
        stage.show();
    }

    public static void main(String args[]) {
        launch(args);
    }
}

The result of Demo.java app is shown below:

PS I'm not getting any errors.

回答1:

The problem is that the values you are passing to the PropertyValueFactory do not match the names of the properties, which are defined by the get...() and set...(...) methods.

Since you are using reflection to look up the names of the fields (not the properties) you have defined, for the Trace class the values passed to PropertyValueFactory are "action", "status", "message", and "time". So the table view will attempt to populate the values for the columns by calling getAction(), getStatus(), getMessage(), and getTime() on the Trace objects in each row. Since there are no such methods, you don't get any values displayed.

To fix this, you can do one of the following:

  1. Hard code the values defined in your getVariablesNames() method to return the names of the properties (i.e. return new String[] {"actionText", "statusText", "messageText", "timeText"};). Since you repeat the method in each class rather than reusing it, you don't make things more verbose, and this would potentially perform better (though you are unlikely to observe any differences in practice).
  2. Use reflection, but look up the names of the declared methods, find all those beginning "get" or "is", strip off that prefix, and lower-case the first character of what remains.
  3. Make the field names match the property names (i.e. declare the fields as actionText, etc.). This of course imposes convention requirements on your class definition.