we want to populate a ListView as a list of instances of a single JavaFX view. This is something we attempted to get working earlier and ran out of time. The fall-back solution at the time was to have a static panel (i.e. not use a ListView) with four rows. In the static version, each row is an instance of the same JavaFX (FXML) view.
Each row is an individual (instance of) a controller matching a small FXML file. The way this works, is that when the initialize() method is called, the view gets an id, this mapped to the model data and from then on the controller / 'view' manages its own data row. In the static case we can use the FXML include-directive, which makes things very simple. I'll call the individual rows, a row-view.
To use a ListView in this way we need to do the following:
- The list view controller must call a "list loader" to load/initialise the list
- We want the row-views to be displayed as individual rows in the ListView
- That seems OK to do if the ListView can take a (sub-)view / (sub-)component
- The row-view is a pre-packaged sub-view;
- Need to be able to 'load' new row-view FXML definitions as required for row changes.
- Is there a way to "clone" an existing view to make a copy?
While this question is similar to the POJO question:
The main difference is that our row can be any arbitrary FXML. I think the mechanical parts of this will work by adapting our existing fixed-size panel. The key concept is to establish the display "scaffold" and allow the view to be swapped-out, instead of having to recode a list box each time the design/requirements change.
update A ...
I've made some progress. I can load an FXML file with a class called "ViewLoader", the with the loadView(urlStr) method. (puts, just writes to a log or sysout).
public AnchorPane loadView( final String urlStr ){
AnchorPane fxmlView = null;
Parent root = null;
URL fxmlResource;
try
{
fxmlResource = getClass().getResource( urlStr );
root = FXMLLoader.load( fxmlResource, Resources.getResourceBundle() );
this.fxmlStr = urlStr;
this.rootNode = root;
fxmlView = (AnchorPane) this.rootNode;
}
catch (Exception ex)
{
Util.puts( " * Exception on FXMLLoader.load()");
Util.puts( " * "+ex.getMessage());
Util.puts( " ----------------------------------------\n");
}
return fxmlView;
}
I'm beginning with a know layout so loadView() loads and returns an AnchorPane as defined in the file named by "urlStr". So far so good. In the debugger I can observe a loaded view and it loads its controller. Each cell would have its own controller and work independently this way.
In the first pass the naive approach was to try to set view via a CellFactory. Bzzt! This invalidates the view, so can't do it that way.
Apparently what's needed is a way to load the ListView with custom cell views. I'm stumped about how to make that happen though. At present we are using a local class called:
public class CustomListView
{
:
public class CustomCellFactory implements Callback<ListView<MyObject>, ListCell<MyObject>>
{
@Override
public ListCell<MyObject> call( ListView<MyObject> listView ) {
ListCell<MyObject> cell = new ListCellType();
return cell;
}
}//CustomCellCallback class
}//CustomListView
It seems to me that the logic of a factory pattern, then the new ListCellType() ought to return the loaded AnchorPane as the view. Where ListCellType is defined as a subclass of ListCell<> ...
public class ListCellType extends ListCell<MyObject>
{
:
}//ListCellType
However, it looks to me as if we need the factory to return the loaded view, so instead of
ListCell<MyObject> cell = new ListCellType();
This application needs to return a JavaFX Node or in this specific case an AnchorPane (and we can make it more general later). Something as-if:
ListCell<AnchorPane> cell = new ListCellType();
While the theory seems OK, the fact remains that the ListView.setCellFactory(...), requires a ListView and not a node.
How to make this work, perhaps with a different factory/update scheme?