Initializing TableView with columns inside fx:root using FXML

Viewed 57

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.

1 Answers

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 TableView1. 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

  1. What happens here is that the second TableView is added to the original TableView's items list, since the @DefaultProperty of a TableView 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 a ClassCastException at some point, but possibly it gets silently squashed since FXML can't specify generic types.
Related