0

Instead of pointing a propertyvaluefactory to a property of an object as follows:

traineeCol.setCellValueFactory(new PropertyValueFactory("sumName"));

I need it to point to a property inside a map, the map in turn is a property of the object it already is pointing to in the code above. Is this possible?

edit: added code of the object in question. So basically, i need the PropertyValueFactory to point toward the Integer with given key, the key being the column header.

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package models;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import database.DAO;
import java.util.Map;

/**
 *
 * @author Jasper
 */
public class Trainee {
    private final StringProperty firstName;
    private final StringProperty tussenVoegsel;
    private final StringProperty lastName;
    private final StringProperty branch;
    private final StringProperty sumName;
    private final Map<String, Integer> trainingPassed;

    public Trainee(String firstName, String tussenVoegsel, String lastName, String branch)
    {
        this.firstName = new SimpleStringProperty(firstName);
        this.tussenVoegsel = new SimpleStringProperty(tussenVoegsel);
        this.lastName = new SimpleStringProperty(lastName);
        this.branch = new SimpleStringProperty(branch);
        trainingPassed = new DAO().loadTrainingPassed(lastName);

        this.sumName = new SimpleStringProperty(this.firstName.get() + " " +
                this.tussenVoegsel.get() + " " + this.lastName.get());
    }

    public String getFirstName() {
        return firstName.get();
    }

    public String getTussenVoegsel() {
        return tussenVoegsel.get();
    }

    public String getLastName() {
        return lastName.get();
    }

    public String getBranch() {
        return branch.get();
    }

    public String getSumName() {
        return sumName.get();
    }

    public Map<String, Integer> getTrainingPassed() {
        return trainingPassed;
    }

}

Edit2: Added code area of tableview, including declarations etc.

@SuppressWarnings("unchecked")
    private TableView table() {
        TableView tableCourseCategories = new TableView();
        ArrayList<TableColumn> colList = new ArrayList<>();

        TableColumn traineeCol = new TableColumn("Training");
        traineeCol.setMinWidth(130);
        traineeCol.setCellValueFactory(new PropertyValueFactory("sumName"));

        for (Course course : courses) {
            if (course.getCategory().equals(this.category)) {
                TableColumn<Trainee, Number> tc = new TableColumn<>();
                tc.setEditable(true);
                tc.setMinWidth(130);
                tc.setText(course.getName());

//                tc.setCellValueFactory(new Callback<CellDataFeatures<Trainee>>, ObservableValue<Number>>() {
//                    @Override
//                    public ObservableValue<Number> call(CellDataFeatures<Trainee> data) {
//                        Integer value = data.getValue().getTrainingPassed().get("sumName");
//                        return new ReadOnlyIntegerWrapper(value);
//                    }
//                });

                colList.add(tc);
            }
        }

        tableCourseCategories.getItems().addAll(this.trainees);
        tableCourseCategories.getColumns().add(traineeCol);
        tableCourseCategories.getColumns().addAll(colList);

        return tableCourseCategories;
    }
Taerus
  • 475
  • 6
  • 20

1 Answers1

4

I would not suppress the "unchecked" warnings: there's no reason not to properly type your TableView and TableColumns.

The setCellValueFactory(...) method in TableColumn does not need to take a PropertyValueFactory: it can take any Callback<CellDataFeatures<S>, ObservableValue<T>>. Here S is the type of data displayed by each row of the table (i.e. you are using a TableView<S>), and T is the type of data displayed in this column (i.e. you are using a TableColumn<S,T>). In your case S is Trainee and T is Number.

This Callback is essentially just a function that converts a given row value (Trainee in your case) to the value to be displayed in the cell. It's made slightly more complex in that the row value is wrapped in a CellDataFeatures object, and the return type is required to be an ObservableValue wrapping the actual value to be displayed. A CellDataFeatures is provided in case you want access to other information to calculate your value (it gives you access to the TableColumn in which the value is being displayed and the TableView in which the TableColumn is held). An ObservableValue is required to be returned as the TableCell will observe the value for changes and update automatically.

If you only want the row value, and don't care about knowing which TableView or TableColumn you are in (or already know, which is usual), you get the row value (the Trainee in your example) by calling getValue() on the CellDataFeatures object.

If you do not have an ObservableValue (e.g. a Property) for your value, you can wrap a regular value in one of the ReadOnlyXXXWrapper classes.

So basically all you need is a Callback implementation whose call(...) method computes the value to display from the value for the row.

In your example, the call() method just has to refer into the map:

traineeCol.setCellValueFactory(new Callback<CellDataFeatures<Trainee>, ObservableValue<Number>>() {
    @Override
    public ObservableValue<Number> call(CellDataFeatures<Trainee> data) {
        Integer value = data.getValue().getTrainingPassed().get(course.getName());
        if (value == null) {
            value = 0 ;
        }
        return new ReadOnlyIntegerWrapper(value);
    }
});

In Java 8 this reduces to

traineeCol.setCellValueFactory(data -> 
    new ReadOnlyIntegerWrapper(data.getValue().getTrainingPassed().getOrDefault(course.getName(),0)));

The PropertyValueFactory which is used in many examples is just a pre-defined implementation that uses reflection to find an existing method in the supplied object that returns an observable value. The basic logic of what it does is outlined in the Javadocs. IMHO it's pretty redundant in Java 8 as you can replace

firstNameColumn.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));

with

firstNameColumn.setCellValueFactory(data -> data.getValue().firstNameProperty());

The latter is less error prone (everything is compiler-checked: the types of the method returns and the fact that such a method exists) as well as performing faster (reflection in general is pretty slow).

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Sorry, but it didn't help. The code is full of errors :c – Taerus Aug 08 '14 at 13:18
  • What are the errors? You will probably have to update your question to show the declarations of the `TableView` and `TableColumn` as well. – James_D Aug 08 '14 at 13:20
  • I will, it gives errors about not enough type parameters (required 2) and some missing (); while im not sure where though cos the code seemed fine. – Taerus Aug 08 '14 at 14:07
  • Oh yeah, that's a typo (an extra `>`). Will fix. – James_D Aug 08 '14 at 14:09
  • OK, so I think that should work if you remove the additional `>`, and you need the map key to be `course.getName()`. – James_D Aug 08 '14 at 14:34
  • Thanks dude, it works! Could you care to explain a bit more in depth how this actually work? I don't just want to copy the code and use it without really understanding :p – Taerus Aug 08 '14 at 15:50
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/58974/discussion-between-taerus-and-james-d). – Taerus Aug 08 '14 at 15:54