Reputation: 87
Im currently developing a application for watching who is responsible for different Patients, however i havent been able to solve how to fill a table with different object types.
Below is my code for my TableView controller. The TableView will end up with four different object typs, all will be retrieved from a database.
I want my table to hold Patient objects, User objects (responsible) and a RelationManager object.
Below is my code, if you need more of the code, please let me know :-).
package fird.presentation;
import fird.Patient;
import fird.RelationManager;
import fird.User;
import fird.data.DAOFactory;
import fird.data.DataDAO;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
/**
* FXML Controller class
*
* @author SimonKragh
*/
public class KMAMainFrameOverviewController implements Initializable {
@FXML
private TextField txtCPRKMAMainFrame;
@FXML
private TableColumn<Patient, String> TableColumnCPR;
@FXML
private TableColumn<Patient, String> TableColumnFirstname;
@FXML
private TableColumn<Patient, String> TableColumnSurname;
@FXML
private TableColumn<User, String> TableColumnResponsible;
@FXML
private TableColumn<RelationManager, String> TableColumnLastEdited;
@FXML
private TableView<RelationManager> tblPatients;
@FXML
private Button btnShowHistory;
@FXML
private TableColumn<?, ?> TableColumnDepartment;
/**
* Initializes the controller clas @FXML private Button btnShowHistory;
*
* @FXML private TableColumn<?, ?> TableColumnDepartment; s.
*/
@Override
public void initialize(URL url, ResourceBundle rb) {
// Start of logic for the KMAMainFrameOverviewController
DataDAO dao = DAOFactory.getDataDao();
TableColumnCPR.setCellValueFactory(new PropertyValueFactory<Patient, String>("CPR"));
TableColumnFirstname.setCellValueFactory(new PropertyValueFactory<Patient, String>("Firstname"));
TableColumnSurname.setCellValueFactory(new PropertyValueFactory<Patient, String>("Surname"));
TableColumnResponsible.setCellValueFactory(new PropertyValueFactory<User, String>("Responsible"));
TableColumnLastEdited.setCellValueFactory(new PropertyValueFactory<RelationManager, String>("Last Edited"));
ObservableList<RelationManager> relationData = FXCollections.observableArrayList(dao.getAllActiveRelations());
tblPatients.setItems(relationData);
tblPatients.getColumns().addAll(TableColumnCPR, TableColumnFirstname, TableColumnSurname, TableColumnResponsible, TableColumnLastEdited);
System.out.println(tblPatients.getItems().toString());
}
}
relationData is a RelationManager object returned. This object contains a User object, a Patient object and a Responsible object.
Best, Simon.
Upvotes: 3
Views: 11573
Reputation: 209225
The exact details of how you do this depend on your requirements: for example, for a given RelationManager object, do the User, Patient, or Responsible objects associated with it ever change? Do you need the table to be editable?
But the basic idea is that each row in the table represents some RelationManager, so the table type is TableView<RelationManager>
. Each column displays a value of some type (call it S
), so each column is of type TableColumn<RelationManager, S>
, where S
might vary from one column to the next.
The cell value factory is an object that specifies how to get from the RelationManager
object to an observable value of type S
. The exact way you do this depends on how your model classes are set up.
If the individual objects associated with a given RelationManager
never change (e.g. the Patient
for a given RelationManager
is always the same), then it's pretty straightforward. Assuming you have the usual setup for Patient
:
public class Patient {
private StringProperty firstName = new SimpleStringProperty(...);
public StringProperty firstNameProperty() {
return firstName ;
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
// etc etc
}
then you can just do
TableColumn<RelationManager, String> firstNameColumn = new TableColumn<>("First Name");
firstNameColumn.setCellValueFactory(new Callback<CellDataFeatures<RelationManager,String>, ObservableValue<String>>() {
@Override
public ObservableValue<String> call(CellDataFeatures<RelationManager, String> data) {
return data.getValue() // the RelationManager
.getPatient().firstNameProperty();
}
});
If you are not using JavaFX properties, you can use the same fallback that the PropertyValueFactory
uses, i.e.:
TableColumn<RelationManager, String> firstNameColumn = new TableColumn<>("First Name");
firstNameColumn.setCellValueFactory(new Callback<CellDataFeatures<RelationManager,String>, ObservableValue<String>>() {
@Override
public ObservableValue<String> call(CellDataFeatures<RelationManager, String> data) {
return new ReadOnlyStringWrapper(data.getValue().getPatient().getFirstName());
}
});
but note that this won't update if you change the name of the patient externally to the table.
However, none of this will work if the patient object associated with the relation manager is changed (the cell will still be observing the wrong firstNameProperty()
). In that case you need an observable value that changes when either the "intermediate" patient property or the firstNameProperty change. JavaFX has a Bindings
API with some select(...)
methods that can do this: unfortunately in JavaFX 8 they spew out enormous amounts of warnings to the console if any of the objects along the way are null, which they will be in a TableView
context. In this case I would recommend looking at the EasyBind framework, which will allow you to do something like
firstNameColumn.setCellValueFactory( data ->
EasyBind.select(data.getValue().patientProperty())
.selectObject(Patient::firstNameProperty));
(EasyBind requires JavaFX 8, so you if you get to use it, you also get to use lambda expressions and method references :).)
In either case, if you want the table to be editable, there's a little extra work to do for the editable cells in terms of wiring editing commits back to the appropriate call to set a property.
Upvotes: 10