MVVM in JavaFX. Controls that have a data model

2019-04-02 19:20发布

问题:

I defined a TableView in the fxml file, but I can't specify a columns of the table, because the table will have some knowledge of data model and it would violate the MVVM. I created a class that provides a collection of TableColumn and provide a data model. I want to change button state relative to selected item in the table. How to do it without breaking the MVVM rules?

回答1:

To explain how tables can be created with MVVM I use a "Contacts" table as example. The Contact class could look like this:

class Contact {
    private String firstName;
    private String lastName;
    // Getter/Setters
}

The Contact class is an entity and belongs to the Model. The MVVM rules say that it shouldn't be visible or used by the View/CodeBehind. Instead I would create a new class ContactTableRow that wraps a Contact:

class ContactTableRow {
    private Contact contact;
    ContactTableRow(Contact contact) {
    public String getFirstName() {
        return contact.getFirstName();
    }
    // getter for Lastname
}

Conceptually this class belongs to the ViewModel layer. For ViewModels it's legal to access and use model classes.

In your View class/CodeBehind you can now use this new class for the TableView:

class ContactsView {
    @FXML
    private TableView<ContactTableRow> table;

}

This is also satisifies the rules of MVVM because the View can use ViewModel classes. In your actual ViewModel you can now create an ObservableList<ContactTableRow> items and a ObjectProperty<ContactTableRow> selectedItem:

class ContactViewModel {
    private ObservableList<ContactTableRow> items = ...
    private ObjectProperty<ContactTableRow> selectedItem = ...

    // property accessor methods
}

In your View class you can now connect the viewModel properties with the table view like this:

class ContactsView {
    @FXML
    private TableView<ContactTableRow> table;

    private ContactsViewModel viewModel = ...

    public void initialize() {
        table.setItems(viewModel.itemsProperty());

        viewModel.selectedItemProperty().bind(
            table.getSelectionModel().selectedItemProperty());    
}

The only thing to be done now is to create some logic in your ViewModel to load actual Contacts from your database, convert the into ContactTableRow instances and put them into your items list.

With this approach your View is independent from actual model classes. In this simple example it may look like the ContactTableRow class is overhead but in real world use cases this can become really useful. It decouples the table from the actual model classes. This way you can show data from multiple entites in a single table and if the structure of your data changes in the future you only need to refactor the row class and not the whole table. The ViewModel is independent from actual UI specific classes. What the viewModel provides is an abstract definition of what the UI should do: There is a list of some data and a single item of this data can be selected. How this list of data is presented to the user is not relevant to the ViewModel. The View could replace the TableView with a ListView or some other component in the future without changing the ViewModel.

You can find an example of this approach in the examples section of the mvvmFX library: The contacts example contains a master-detail view which uses this technique.