JavaFX:来自 FXML 和项目模板的集合绑定

Dav*_*vid -1 xaml javafx fxml

由于缺少教程和以下问题的示例不完整,我决定写这个问题。如果这个问题的答案成为解决类似问题的有效例子,我会很高兴。

基于:类似于 xaml 的 JavaFX8 列表绑定


任务

让我们使用JavaFX技术(使用FXML作为该技术的一部分来制作图形视图)制作一个GUI 应用程序,例如显示用户集合和每个用户的汽车集合。让我们还使用JavaFX 属性bindig机制将模型(数据)与 GUI 同步。

执行

我从为用户汽车创建类开始。

用户.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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

比我为FXML GUI 视图准备控制器,并带有用户示例集合。

控制器.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"),
            })
    );
} 
Run Code Online (Sandbox Code Playgroud)

最后,我还包括生成的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

挑战是制作使用JavaFX 绑定的FXML GUI 视图,并显示来自相应Controller 的准备好的用户集合。可能使用ListView

此外,我想在FXML 中不是在代码中指定ListView 项目设计/外观——因为它是 GUI 设计的一部分。.NET XAML ItemTemplate 的适当 JavaFX FXML 替代品。怎么样的ListCell


这个伪代码的某种方式:

用户文件

<?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>
Run Code Online (Sandbox Code Playgroud)

Jam*_*s_D 5

我总是对以下形式的问题持怀疑态度:“我熟悉技术 A,我正在学习技术 B,并希望以与使用技术 A 完全相同的方式使用它”。每种技术(工具包、库、语言等等……)都有自己的预期用途和习惯用法,按照预期的方式使用技术总是更好。对于任何给定的技术,总会有您喜欢和不喜欢的地方:如果后者超过前者,那么就不要使用它。

JavaFX 的真正设计是在控制器中而不是在 FXML 中进行绑定,因此没有内置的模板机制。所以我可能不会真正推荐这种方法。

也就是说,您可以通过一点创造力和一点妥协来实现与您正在尝试做的事情类似的事情。特别是,该解决方案涉及:

  1. 将单元格“模板”的定义移动到不同的 FXML 文件,以及
  2. 编写一个(可重用的)Java 类将所有内容连接在一起。

这可能不是最好或最有效的方法,但它应该为您提供一些工作。

我首先只是将数据重构为一个DataAccessor类,并在 FXML 中实例化它,将其注入到控制器中。这是一种访问itemsFXML 中的方便的方法,但如果它冒犯了您的 MVC/MVP 敏感性,还有其他方法可以做到这一点:)

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 ;
    }
}
Run Code Online (Sandbox Code Playgroud)

package example;
import javafx.fxml.FXML;

public class Controller {

    @FXML
    private DataAccessor dataAccessor ;


} 
Run Code Online (Sandbox Code Playgroud)

基本思想是定义一个通用的单元工厂实现,该实现创建其图形属性从指定的 FXML 文件加载的单元:

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);
                    }
                }
            }
        };
    }

}
Run Code Online (Sandbox Code Playgroud)

现在您可以创建一个使用它的 FXML 文件。这个版本有一个“主从”用户界面(用户列表,选择一个用户,第二个列表显示他们的汽车列表)。

示例.fxml:

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 ;
    }
}
Run Code Online (Sandbox Code Playgroud)

这引用了另外两个 FXML 文件,每个列表视图一个:

userListCell.fxml:

package example;
import javafx.fxml.FXML;

public class Controller {

    @FXML
    private DataAccessor dataAccessor ;


} 
Run Code Online (Sandbox Code Playgroud)

和 carListCell.fxml:

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);
                    }
                }
            }
        };
    }

}
Run Code Online (Sandbox Code Playgroud)

Main和模型类是完全按照你的问题。

可能有一种方法可以在不将单元格图形的 FMXL 分解为单独文件的情况下执行此操作,例如说服(以某种方式...)FXMLLoader 将内容解析为文字字符串并将其传递给单元格工厂实现;然后在单元工厂实现中将字符串转换为流并使用FXMLLoader.load(...)获取流的方法。不过,我会将其作为练习留给读者。

最后请注意,在单元格的updateItem(...)方法中加载和解析 FXML 文件并不是一种特别有效的方法;我找不到快速解决这个问题的方法,尽管也有可能。