-1

I decided to write this question due to missing tutorials and incomplete examples of the following problem. I will be glad if the answer to this question becomes a working example for solving similar problems.

Based on: JavaFX8 list bindings similar to xaml


TASK

Let's make a GUI application using a JavaFX technology (with FXML as a part of this technology for making graphical view) that show collection of users and for every user also his/her collection of cars for example. Let's also use JavaFX properties and bindig mechanisms for synchronize model (data) with GUI.

IMPLEMENTATION

I started with the creation of classes for the user and the car.

User.java

package example;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

public class User {
    public StringProperty firstName;
    public StringProperty lastName;

    private ObservableList<Car> cars;

    public User(String firstName, String lastName, Car[] cars) {
        this.firstName = new SimpleStringProperty(firstName);
        this.lastName  = new SimpleStringProperty(lastName);
        this.cars      = FXCollections.observableArrayList(cars);
    }

    public String getFirstName() {
        return firstName.get();
    }

    public StringProperty firstNameProperty() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName.set(firstName);
    }

    public String getLastName() {
        return lastName.get();
    }

    public StringProperty lastNameProperty() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName.set(lastName);
    }

    public ObservableList<Car> getCars() {
        return cars;
    }
}

Car.java

package example;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Car {
    public StringProperty modelName;
    public StringProperty manufacturer;

    public Car(String modelName, String manufacturer) {
        this.modelName    = new SimpleStringProperty(modelName);
        this.manufacturer = new SimpleStringProperty(manufacturer);
    }

    public String getModelName() {
        return modelName.get();
    }

    public StringProperty modelNameProperty() {
        return modelName;
    }

    public void setModelName(String modelName) {
        this.modelName.set(modelName);
    }

    public String getManufacturer() {
        return manufacturer.get();
    }

    public StringProperty manufacturerProperty() {
        return manufacturer;
    }

    public void setManufacturer(String manufacturer) {
        this.manufacturer.set(manufacturer);
    }
}

Than I prepare Controller for FXML GUI view with sample collection of users.

Controller.java

package example;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

public class Controller {
    private ObservableList<User> users = FXCollections.observableArrayList(
            new User("John", "Smith",  new Car[] {
                    new Car("LaFerrari", "Ferrari"),
                    new Car("FG X Falcon", "Ford")
            }),
            new User("Ariel", "England", new Car[] {
                    new Car("ATS", "Cadillac"),
                    new Car("Camaro", "Chevrolet"),
                    new Car("458 MM Speciale", "Ferrari")
            }),
            new User("Owen", "Finley", new Car[] {
                    new Car("Corsa", "Chevrolet"),
            })
    );
} 

And finally, I also include the generated Main.java.

package example;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Users -- example application");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

QUESTION

The challenge is make FXML GUI view that use JavaFX binding and show prepared collection of users from coresponding Controller. Probably using ListView.

Also I would like specify ListView item design/look in FXML and not in code – because it is part of GUI design. Appropriate JavaFX FXML alternative to .NET XAML ItemTemplate. What about ListCell?


Something in a way of this pseudocode:

users.fxml

<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.Label?>

<GridPane fx:controller="example.Controller"
          xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
    <ListView items="${users}">
        <ListView.ItemTemplate>
            <VBox>
                <Label Text="${firstName}" />
                <Label Text="${lastName}" Style="-fx-background-color: yellow" />

                <ListView items="${cars}">
                    <ListView.ItemTemplate>
                        <HBox>
                            <Label Text="${manufacturer}" />
                            <Label Text=": " />
                            <Label Text="${modelName}" />
                        </HBox>
                    </ListView.ItemTemplate>
                </ListView>
            </VBox>
        </ListView.ItemTemplate>
    </ListView>
</GridPane>
Community
  • 1
  • 1
David
  • 527
  • 1
  • 8
  • 21
  • Unfortunately, there's nothing like data templates in JavaFX. – Clemens Oct 14 '16 at 16:39
  • *"due to missing tutorials and incomplete examples"*. The documentation for FXML is [here](http://docs.oracle.com/javase/8/javafx/api/javafx/fxml/doc-files/introduction_to_fxml.html) and basically contains all the information you need to do what you are asking. Additionally, there are examples on [this site's documentation section](http://stackoverflow.com/documentation/javafx/1580/fxml-and-controllers), as well as many others. This just doesn't seem like a question that is [on-topic](http://stackoverflow.com/help/on-topic) for this forum. – James_D Oct 14 '16 at 16:39
  • @Clemens Surely you just set up the correct properties and use [expression binding](http://docs.oracle.com/javase/8/javafx/api/javafx/fxml/doc-files/introduction_to_fxml.html#expression_binding)? – James_D Oct 14 '16 at 16:46
  • @James_D Actually, I went through a few examples by Oracle about design item of ListView, but this examples use code. I think that "design" should be solved in one place (FXML) and not mixed with code. So, the question is about binding from FXML. – David Oct 14 '16 at 16:55
  • @James_D I think that **expression_binding** not fits this problem. – David Oct 14 '16 at 16:57
  • @user3621749 The cell factory is a factory, so it can't be implemented with FXML (which doesn't execute code). But there is nothing to stop you implementing the content of the cell it creates with FXML. – James_D Oct 14 '16 at 16:58
  • @Clemens Thank you. It seems that I have no choice and I must mix FXML and code for design one control. – David Oct 14 '16 at 16:59
  • @James_D I'm not talking about data binding, but data templates. [Data Templating](https://msdn.microsoft.com/en-us/library/ms742521(v=vs.110).aspx) is a concept that is missing in JavaFX, at least when you come from the XAML world. – Clemens Oct 14 '16 at 17:00
  • @James_D Do you mean something like this [http://code.makery.ch/blog/javafx-8-tableview-cell-renderer/](Cell Renderer)? In this approach, there is mix of Java code and FXML. – David Oct 14 '16 at 17:11
  • @user3621749 That link doesn't work. It looks like expression binding doesn't quite work the way I thought it did when you have "properties of properties", so this might not work as easily as I thought it would. – James_D Oct 14 '16 at 17:59
  • @Clemens My point was that you can use expression binding to implement a template. – James_D Oct 14 '16 at 19:04
  • @James_D Sure, and I was saying that there is "no baked-in templating mechanism" as you correctly state in your answer. – Clemens Oct 14 '16 at 19:23

1 Answers1

6

I am always sceptical of questions of the form: "I am familiar with technology A, I am learning technology B and want to use it exactly the same way I use technology A". Each technology (toolkit, library, language, whatever...) has its own intended usage and idioms, and it's always better to use the technology the way it was intended. There will always be things you like and things you don't like about any given technology: if the latter outweigh the former, then just don't use it.

JavaFX is really designed that bindings are made in the controller, rather than in the FXML, and consequently there is no baked-in templating mechanism. So I would probably not really recommend this approach.

That said, you can probably achieve something akin to what you are trying to do with a little creativity and a little compromise. In particular, this solution involves:

  1. moving the definitions of the cell "templates" to different FXML files, and
  2. writing one (reusable) Java class to wire everything together.

This might not be the best or most efficient approach, but it should give you something to work from.

I first just refactored the data into a DataAccessor class, and instantiated it in the FXML, injecting it into the controller. This is a convenient way to give access to the items in the FXML, but there are other ways of doing this if it offends your MVC/MVP sensibilities :)

package example;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

public class DataAccessor {
    private ObservableList<User> users = FXCollections.observableArrayList(
            new User("John", "Smith",  new Car[]{
                    new Car("LaFerrari", "Ferrari"),
                    new Car("FG X Falcon", "Ford")
            }),
            new User("Ariel", "England",new Car[]{
                    new Car("ATS", "Cadillac"),
                    new Car("Camaro", "Chevrolet"),
                    new Car("458 MM Speciale", "Ferrari")
            }),
            new User("Owen", "Finley", new Car[]{
                    new Car("Corsa", "Chevrolet")
            })
    );

    public ObservableList<User> getUsers() {
        return users ;
    }
}

and

package example;
import javafx.fxml.FXML;

public class Controller {

    @FXML
    private DataAccessor dataAccessor ;


} 

The basic idea is going to be to define a general cell factory implementation that creates cells whose graphic property is loaded from a specified FXML file:

package example;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import javafx.beans.NamedArg;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.util.Callback;

public class FXMLListCellFactory implements Callback<ListView<Object>, ListCell<Object>> {

    private final URL fxmlSource ;

    public FXMLListCellFactory(@NamedArg("fxmlSource") String fxmlSource) throws MalformedURLException {
        this.fxmlSource = new URL(fxmlSource) ;
    }

    @Override
    public ListCell<Object> call(ListView<Object> lv) {
        return new ListCell<Object>() {
            @Override
            protected void updateItem(Object item, boolean empty) {
                super.updateItem(item, empty);
                if (item == null || empty) {
                    setGraphic(null);
                } else {
                    try {
                        FXMLLoader loader = new FXMLLoader(fxmlSource);
                        loader.getNamespace().put("item", item);
                        setGraphic(loader.load());
                    } catch (IOException e) {
                        e.printStackTrace();
                        setGraphic(null);
                    }
                }
            }
        };
    }

}

And now you can create a FXML file that uses this. This version has a "master-detail" UI (list of users, select a user and the second list shows their list of cars).

sample.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.HBox?>

<?import example.DataAccessor?>
<?import example.FXMLListCellFactory?>
<?import javafx.scene.control.ListView?>

<HBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="example.Controller" spacing="10">

    <fx:define>
        <DataAccessor fx:id="dataAccessor" />
    </fx:define>

    <ListView fx:id="userList" items="${dataAccessor.users}">
        <cellFactory>
            <FXMLListCellFactory fxmlSource="@userListCell.fxml"/>
        </cellFactory>
    </ListView>

    <ListView fx:id="carList" items="${userList.selectionModel.selectedItem.cars}">
        <cellFactory>
            <FXMLListCellFactory fxmlSource="@carListCell.fxml"/>
        </cellFactory>
    </ListView>
</HBox>

This references two other FXML files, one for each of the list views:

userListCell.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Label?>
<?import javafx.beans.property.SimpleObjectProperty?>

<HBox xmlns:fx="http://javafx.com/fxml/1" spacing="5">
    <Label text="${item.firstName}"/>
    <Label text="${item.lastName}"/> 
</HBox>

and carListCell.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.HBox?>
<?import javafx.beans.property.SimpleObjectProperty?>
<?import javafx.scene.control.Label?>

<HBox xmlns:fx="http://javafx.com/fxml/1">
    <Label text="${item.manufacturer+' '+item.modelName}"/>
</HBox>

The Main and model classes are exactly as in your question.

There's probably a way to do this without factoring the FMXL for the cell graphic into separate files, e.g. persuading (somehow...) the FXMLLoader to parse the content as a literal string and pass it to the cell factory implementation; then in the cell factory implementation convert the string to a stream and use the FXMLLoader.load(...) method taking a stream. I'll leave that as an exercise to the reader though.

Finally note that loading and parsing an FXML file in the cell's updateItem(...) method is not a particularly efficient approach; I could not find a way to work around this quickly, though it may be possible too.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Wow, that's a really good example. I like this approach because it has completely separated view. Thank you :-)! – David Oct 14 '16 at 18:50