【问题标题】:JavaFX: Collection binding from FXML and Item templateJavaFX:来自 FXML 和项目模板的集合绑定
【发布时间】:2016-10-14 16:20:54
【问题描述】:

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

基于:JavaFX8 list bindings similar to xaml


任务

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

实施

我首先为用户汽车创建类。

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

比我为 FXML GUI 视图 准备 Controller 以及用户样本集合。

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

最后,我还包含了生成的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);
    }
}

问题

挑战在于制作 FXML GUI 视图,该视图使用 JavaFX 绑定,并显示来自对应控制器的准备好的用户集合。可能使用 ListView

我还想在 FXML不在代码中 指定 ListView 项目 设计/外观 - 因为它是 GUI 设计的一部分。 .NET XAML ItemTemplate 的适当 JavaFX FXML 替代品。 ListCell呢?


这个伪代码的某种方式:

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>

【问题讨论】:

  • 不幸的是,JavaFX 中没有像数据模板这样的东西。
  • “由于缺少教程和不完整的示例”。 FXML 的文档是here,基本上包含了您需要做的所有信息。此外,this site's documentation section 上还有许多其他示例。对于这个论坛,这似乎不像是on-topic 的问题。
  • @Clemens 你肯定设置了正确的属性并使用expression binding
  • @James_D 实际上,我通过 Oracle 浏览了一些关于 ListView 设计项的示例,但这些示例使用代码。我认为“设计”应该在一个地方(FXML)解决,而不是与代码混合。所以,问题是关于 FXML 的绑定。
  • @James_D 我认为 expression_binding 不适合这个问题。

标签: xaml javafx fxml


【解决方案1】:

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

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

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

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

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

我首先将数据重构为DataAccessor 类,并在FXML 中对其进行实例化,并将其注入控制器。这是一种在 FXML 中访问 items 的便捷方式,但如果它冒犯了您的 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 ;
    }
}

package example;
import javafx.fxml.FXML;

public class Controller {

    @FXML
    private DataAccessor dataAccessor ;


} 

基本思想将是定义一个通用单元工厂实现,该实现创建其图形属性从指定的 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);
                    }
                }
            }
        };
    }

}

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

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>

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

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>

和 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>

Main 和模型类与您的问题完全相同。

可能有一种方法可以做到这一点,而无需将单元图形的 FMXL 分解为单独的文件,例如说服(以某种方式......)FXMLLoader 将内容解析为文字字符串并将其传递给单元工厂实现;然后在单元工厂实现中将字符串转换为流并使用FXMLLoader.load(...) 方法获取流。不过,我将把它作为练习留给读者。

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

【讨论】:

  • 哇,这真是一个很好的例子。我喜欢这种方法,因为它具有完全分离的视图。谢谢你:-)!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-22
  • 1970-01-01
  • 2017-02-04
  • 1970-01-01
  • 1970-01-01
  • 2023-04-08
  • 1970-01-01
相关资源
最近更新 更多