Issue
I have a custom class inside a fxml file called TablePatientView
. I use it like <TablePatientView fx:id="tablePatientView" layoutX="20.0" layoutY="45.0" prefHeight="409.0" prefWidth="363.0" />
and it works almost fine. The fx:root
looks like the following:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<fx:root type="javafx.scene.control.TableView" xmlns:fx="http://javafx.com/fxml">
<TableView>
<columns>
<TableColumn fx:id="patId" prefWidth="101.0" text="Patient-ID" />
<TableColumn fx:id="patVorname" prefWidth="69.0" text="Vorname" />
<TableColumn fx:id="patNachname" prefWidth="98.0" text="Nachname" />
<TableColumn fx:id="patGebdat" prefWidth="96.0" text="Geburtsdatum" />
</columns>
</TableView>
</fx:root>
with the following class
public class TablePatientView extends TableView<TablePatient> {
private Parent root;
@FXML
private TableColumn<TablePatient,String> patId;
@FXML
private TableColumn<TablePatient,String> patVorname;
@FXML
private TableColumn<TablePatient,String> patNachname;
@FXML
private TableColumn<TablePatient, LocalDate> patGebdat;
public TablePatientView() {
FXMLLoader loader = new FXMLLoader(Main.class.getResource("/fxml/TablePatientView.fxml"));
loader.setController(this);
loader.setRoot(this);
try {
root = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
setCellValueFactories();
// getColumns().addAll(patId, patVorname, patNachname, patGebdat); works, but not with plain fxml
}
private void setCellValueFactories() {
patId.setCellValueFactory(param -> param.getValue().idProperty().asString());
patVorname.setCellValueFactory(param -> param.getValue().vornameProperty());
patNachname.setCellValueFactory(param -> param.getValue().nachnameProperty());
patGebdat.setCellValueFactory(param -> param.getValue().gebDatProperty());
}
}
The columns are correctly initialized, but for some reason not added as columns to the TableView. I know I can do it via java code (see the commented line in the code above) but I would like to know why this doesn't work with just fxml. Using the <TableView> directly inside fxml without a custom class and root works just fine.
Solution
The <fx:root type="TableView">
references a TableView
which is supplied via the call to loader.setRoot(this)
.
You have defined another TableView
inside that TableView
1. The columns are added to that TableView
, not the one referenced by <fx:root>
.
The correct FXML is
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<fx:root type="javafx.scene.control.TableView" xmlns:fx="http://javafx.com/fxml">
<columns>
<TableColumn fx:id="patId" prefWidth="101.0" text="Patient-ID" />
<TableColumn fx:id="patVorname" prefWidth="69.0" text="Vorname" />
<TableColumn fx:id="patNachname" prefWidth="98.0" text="Nachname" />
<TableColumn fx:id="patGebdat" prefWidth="96.0" text="Geburtsdatum" />
</columns>
</fx:root>
Alternative approach
I'm not a big fan of subclassing control classes, such as TableView
. The reason is that you're not really adding functionality to the TableView
, you're just essentially setting properties on it, and this is not a good use of inheritance.
Consider instead using a creational pattern. A simple static factory method works well, and plays nicely with FXML via the fx:factory
attribute.
TablePatientView.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<TableView xmlns:fx="http://javafx.com/fxml" fx:controller="com.jamesd.examples.TablePatientController">
<columns>
<TableColumn fx:id="patId" prefWidth="101.0" text="Patient-ID" />
<TableColumn fx:id="patVorname" prefWidth="69.0" text="Vorname" />
<TableColumn fx:id="patNachname" prefWidth="98.0" text="Nachname" />
<TableColumn fx:id="patGebdat" prefWidth="96.0" text="Geburtsdatum" />
</columns>
</TableView>
TablePatientController.java:
public class TablePatientController {
@FXML
private TableColumn<TablePatient,String> patId;
@FXML
private TableColumn<TablePatient,String> patVorname;
@FXML
private TableColumn<TablePatient,String> patNachname;
@FXML
private TableColumn<TablePatient, LocalDate> patGebdat;
@FXML
private void initialize() {
setCellValueFactories();
}
private void setCellValueFactories() {
patId.setCellValueFactory(param -> param.getValue().idProperty().asString());
patVorname.setCellValueFactory(param -> param.getValue().vornameProperty());
patNachname.setCellValueFactory(param -> param.getValue().nachnameProperty());
patGebdat.setCellValueFactory(param -> param.getValue().gebDatProperty());
}
}
Tables.java:
public class Tables {
public static TableView<TablePatient> tablePatientView() throws Exception {
FXMLLoader loader = new FXMLLoader(Main.class.getResource("/fxml/TablePatientView.fxml"));
return loader.load();
}
}
And then you can use this in FXML with
<Tables fx:factory="tablePatientView" />
Footnote
- What happens here is that the second
TableView
is added to the originalTableView
'sitems
list, since the@DefaultProperty
of aTableView
is that list. So eventually the table view would attempt to display that as a row in the table. It seems like there should be aClassCastException
at some point, but possibly it gets silently squashed since FXML can't specify generic types.
Answered By - James_D
Answer Checked By - David Goodson (JavaFixing Volunteer)