简体   繁体   中英

JavaFX TableView Objects with Maps

So I have done some digging around on the JavaFx TableView and I have found some nice solutions to simple situations.

This article provides a nice explanation of how to create a Table with a Person object and also shows how to create a Table using a Map.

Here is my problem, let's say I have an Object Person that contains some simple member data and also a Map of Assignments to Grades.

Example:

public class Person {
   String firstName;
   String lastName;
   String age;
   Map<Assignment, Grade> map;
 }

I want to display a table like the following

firstName |  lastName | age | Assignment1 | Assignment 2 | Assignment 3 | ...

If I define a Table like so:

private TableView<Student> table;

It is easy to define simple columns like:

private TableColumn<Student, String> firstNameColumn =
     firstNameColumn.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));

But how should I go about defining columns for each of the keys in the map?

Is this even possible or should I just create another table that handles the map?

You don't have to use the default PropertyValueFactory, you can write your own callback.

import java.util.HashMap;
import java.util.LinkedHashMap;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;

public class AssTable extends Application {

    @Override
    public void start(Stage primaryStage) {
        ObservableList<Student> students = FXCollections.observableArrayList(
            new Student("jack"),new Student("john"),new Student("jill"),new Student("jane"));
        TableView<Student> studentTable = new TableView(students);
        TableColumn<Student, String> firstNameColumn = new TableColumn("name");
            firstNameColumn.setCellValueFactory(new PropertyValueFactory("firstName"));
        studentTable.getColumns().add(firstNameColumn);

        int maxAss = 0;
        for (Student student : students)
            maxAss = Math.max(maxAss, student.map.size());

        Callback<TableColumn.CellDataFeatures<Student, String>, ObservableValue<String>> callBack = 
                new Callback<TableColumn.CellDataFeatures<Student, String>, ObservableValue<String>>() {
            @Override
            public ObservableValue<String> call(TableColumn.CellDataFeatures<Student, String> param) {
                return param.getValue().map.containsKey(
                        "ass"+Integer.toString((int)param.getTableColumn().getUserData()))
                        ? new SimpleStringProperty(String.format("%.1f",100d*param.getValue().map.get(
                            "ass"+Integer.toString((int)param.getTableColumn().getUserData()))))
                        :new SimpleStringProperty("");
            }
        };

        ObservableList<TableColumn<Student, String>> assCols = FXCollections.observableArrayList();
        for (int i = 1; i<=maxAss; i++){
            TableColumn<Student, String> tmpCol = new TableColumn("ass"+Integer.toString(i));
            tmpCol.setUserData(i);
            tmpCol.setCellValueFactory(callBack);
            assCols.add(tmpCol);
        }
        studentTable.getColumns().addAll(assCols);

        VBox root = new VBox(studentTable);
        Scene scene = new Scene(root, 500, 250);

        primaryStage.setTitle("Table with map");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public class Student {

        private final StringProperty firstName = new SimpleStringProperty();
        public StringProperty firstNameProperty(){return firstName;}
        public final HashMap<String, Double> map;

        public Student(String fn) {
            firstName.set(fn);
            map = new LinkedHashMap<>();
            for (int i = 1; i <= 10; i++) {
                double grade = Math.random();
                if (grade > .5) {
                    map.put("ass" + Integer.toString(i), grade);
                }
            }
        }
    }
}

屏幕截图

You can see it adds columns depending on how many assignments there are. Also nobody has done ass4 in this random sample. With this code and example you can't add an assignment like #8 without also adding a new column, or it won't show up.

I assumed that the size (# of Entries) of each Map does not change during runtime, or better: there is a fixed maximum of entries. If this is the case, the TableView can access each Entry the same way as it does with standard attributes (or a Property ). Here is a modified class of Person .

public class PersonSimple {
String firstName;
String lastName;
String age;
Map<Integer, Double> map;

public PersonSimple(String fn, String ln, String age, Double gr0, Double gr1, Double gr2)
{
    this.firstName = fn;
    this.lastName = ln;
    this.age = age;
    map = new LinkedHashMap<>();
    map.put(0, gr0);
    map.put(1, gr1);
    map.put(2, gr2);
}

public String getFirstName()
{
    return firstName;
}

public String getLastName()
{
    return firstName;
}
public String getAge()
{
    return age;
}

private Double getFromMap(Integer key)
{
    Set<Entry<Integer, Double>> s = map.entrySet();
    Iterator<Entry<Integer, Double>> iter = s.iterator();
    int index = 0;
    while(iter.hasNext())
    {
        Entry<Integer, Double> e = iter.next();
        if(index == key.intValue())
        {
            return e.getValue();
        }
        index++;
    }
    return null;
}

public Double getFM0()
{
    return getFromMap(0);
}

public Double getFM1()
{
    return getFromMap(1);
}

public Double getFM2()
{
    return getFromMap(2);
}

}

As you can see, every PersonSimple has a Map which must hold three entries. Now comes the trick. For each of these entries you have define a get-method. Be careful how you name them, because this part is crucial to the interaction with the TableView .

The following code shows how you connect these new methods to the TableView .

 TableColumn firstNameCol = new TableColumn("First Name");
    TableColumn lastNameCol = new TableColumn("Last Name");
    TableColumn ageCol = new TableColumn("Age");
    TableColumn aCol = new TableColumn("Assignment1");
    TableColumn bCol = new TableColumn("Assignment2");
    TableColumn cCol = new TableColumn("Assignment3");

    table.getColumns().addAll(firstNameCol, lastNameCol, ageCol,aCol,bCol,cCol);

    firstNameCol.setCellValueFactory(new PropertyValueFactory<Person,String>("firstName"));
    lastNameCol.setCellValueFactory(new PropertyValueFactory<Person,String>("lastName"));
    ageCol.setCellValueFactory(new PropertyValueFactory<Person,String>("age"));

    aCol.setCellValueFactory(new PropertyValueFactory<Person,Double>("FM0"));
    bCol.setCellValueFactory(new PropertyValueFactory<Person,Double>("FM1"));
    cCol.setCellValueFactory(new PropertyValueFactory<Person,Double>("FM2"));

It is highly important that each PropertyValueFactor gets a name that fits to one of the get-methods in the class PersonSimple . See the TableView-API for more information about that.

Of course, my approach does not solve the problem with getting the data from dynamic maps, because as far as i know it is not possible in Java to add new methods to a class during runtime. But there might be a trick using the reflection-api to circumvent this limitation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM