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

时间:2016-10-14 16:20:55

标签: xaml javafx fxml

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

基于:JavaFX8 list bindings similar to xaml

  

TASK

让我们使用 JavaFX 技术制作 GUI应用 FXML 作为此技术的一部分,用于制作图形视图)例如,显示用户集合以及每个用户他/她的汽车集合。我们还使用 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.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);
    }
}
  

问题

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

另外,我想指定 ListView项目设计/查看 FXML 不在代码中 - 因为它是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>

1 个答案:

答案 0 :(得分:4)

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

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

也就是说,你可以通过一点点创造力和一点点妥协来实现类似于你想要做的事情。特别是,该解决方案涉及:

  1. 将单元格“模板”的定义移动到不同的FXML文件,
  2. 编写一个(可重用的)Java类来连接所有内容。
  3. 这可能不是最好或最有效的方法,但它可以为您提供一些工作。

    我首先将数据重构为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文件。此版本具有“主 - 细节”UI(用户列表,选择用户,第二个列表显示他们的汽车列表)。

    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文件并不是一种特别有效的方法;我找不到办法快速解决这个问题,尽管也有可能。