ListView containing mvvmFX views

Manuel Mauky edited this page Aug 18, 2015 · 6 revisions

The ListView is a JavaFX component to show a variing number of elements in the UI. By default the ListView contains simple string representations of the list entries. But it's also possible to define a ListView of more complex representations. This is done via the CellFactory of the ListView. This way we can create a ListView that contains mvvmFX Views as list entries.

As an example we can look at our books-example. It has a ListView that shows the found books for a given search string. Each list entry shows the title of the book in a big font and the author name in a smaller font in braces. Each entry is a BookListItemView with this fxml file and the BookListItemViewModel as ViewModel.

In the MainViewModel we have an observable list of BookListItemViewModels instances for each found Book instance. In the MainView there is a ListView that has the generic type of the BookListItemViewModel so we can simply use the setItems method of the ListView and connect it to the viewModels list:

@FXML
private ListView<BookListItemViewModel> bookList;
...
@InjectViewModel
private MainViewModel viewModel;

public void initialize(){
    bookList.setItems(viewModel.booksProperty());
    ...
}

With this setup JavaFX wouldn't know how to display the entries of the ListView. It would simply call the toString() method of BookListItemViewModel and show the result as String. This would result in a list of Strings like de.saxsys.mvvmfx.examples.books.BookListItemViewModel@b64511 which isn't exactly what we are looking for. To fix this we need to tell JavaFX how it should visualize the list entries.

For this purpose we have created some helper classes like the CachedViewModelCellFactory that can be used as CellFactory. The code looks like this:

@FXML
private ListView<BookListItemViewModel> bookList;
...
@InjectViewModel
private MainViewModel viewModel;

public void initialize(){
    bookList.setItems(viewModel.booksProperty());

    ViewListCellFactory<BookListItemViewModel> cellFactory =
        CachedViewModelCellFactory.createForFxmlView(BookListItemView.class));
		
    bookList.setCellFactory(cellFactory);
    ...
}

The createForFxmlView factory method takes the class type of the fxml view as argument. This code will fit for most usecases. Of cause there is also a createForJavaView factory method that is used for Views created with pure Java code without fxml. (Notice: This factory method is introduced in version 1.4.0 of mvvmFX. In previous versions you will have to use the method described below)

If you need more control over the loading process (f.e. providing a custom resourceBundle) you can use the factory method create.

See this example which is basically equivalent to the one above:

ViewListCellFactory<BookListItemViewModel> cellFactory =
        CachedViewModelCellFactory.create(
            vm -> FluentViewLoader.fxmlView(BookListItemView.class).viewModel(vm).load());

This method takes a function as argument. The function has the singature Callback<VM, ViewTuple<V, VM>> where V is the generic type of the View (BookListItemView in our example) and VM is the generic type of the ViewModel (BookListItemViewModel). This means the function takes an instance of a viewModel as argument and returns a ViewTuple. With the .viewModel() method of the FluentViewLoader we are reusing the given viewModel argument for loading the View in the lambda.

The CachedViewModelCellFactory has an internal caching mechanism that is useful because the ListView will call the CellFactory multiple times for the same items. In this case we are only loading the View a single time and return the already loaded instances afterwards.