【问题标题】:FXML Dynamically initialize ObservableList for ComboBox and TableViewFXML为ComboBox和TableView动态初始化ObservableList
【发布时间】:2017-06-02 05:54:02
【问题描述】:

我正在尝试制作 Dan Nicks 对this questioncomment 中提出的自定义构建器。
这个想法是在构造之前设置组合的数据。
combo.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ComboBox?>

<ComboBox  fx:id="combo1" items="${itemLoader.items}"  prefWidth="150.0" 
   xmlns:fx="http://javafx.com/fxml/1">
</ComboBox>

提供数据的类:

public class ComboLoader {

    public ObservableList<Item> items;

    public ComboLoader() {

        items = FXCollections.observableArrayList(createItems());
    }

    private List<Item> createItems() {
            return IntStream.rangeClosed(0, 5)
                    .mapToObj(i -> "Item "+i)
                    .map(Item::new)
                    .collect(Collectors.toList());
        }

    public ObservableList<Item> getItems(){

        return items;
    }

    public static class Item {

        private final StringProperty name = new SimpleStringProperty();

        public Item(String name) {
            this.name.set(name);
        }

        public final StringProperty nameProperty() {
            return name;
        }
     
    }
}

还有测试:

public class ComboTest extends Application {

    @Override
    public void start(Stage primaryStage) throws IOException {

        primaryStage.setTitle("Populate combo from custom builder");

        Group group = new Group();

        GridPane grid = new GridPane();
        grid.setPadding(new Insets(25, 25, 25, 25));
        group.getChildren().add(grid);

        FXMLLoader loader = new FXMLLoader();
        ComboBox combo = loader.load(getClass().getResource("combo.fxml"));
        loader.getNamespace().put("itemLoader", new ComboLoader());
        grid.add(combo, 0, 0);

        Scene scene = new Scene(group, 450, 175);

         primaryStage.setScene(scene);
         primaryStage.show();
    }

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

没有产生错误,但没有填充组合。
缺少什么?


顺便说一句:TableView 的类似解决方案工作正常:

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

<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.cell.PropertyValueFactory?>

 <TableView items="${itemLoader.items}"   xmlns:fx="http://javafx.com/fxml/1">
     <columns>
         <TableColumn text="Item">
             <cellValueFactory><PropertyValueFactory property="name" /></cellValueFactory>
         </TableColumn>
     </columns>
 </TableView>

【问题讨论】:

    标签: javafx fxml loader items observablelist


    【解决方案1】:

    从一个挑剔的开始,我做了一些实验,以了解如何实际实现我试图在我的 cmets 中向c0der's answer 概述的内容。

    基本思想是对 listCell 遵循与数据相同的方法,即通过命名空间(我今天的学习项目)配置内容和外观。成分:

    • 一个通用的自定义 listCell 可配置一个函数来将项目转换为文本
    • 一个通用的“cellFactory 工厂”类,用于提供创建该单元的 cellFactory

    细胞/工厂:

    public class ListCellFactory<T> {
        
        private Function<T, String> textProvider;
    
        public ListCellFactory(Function<T, String> provider) {
            this.textProvider = provider;
        }
    
        public Callback<ListView<T>, ListCell<T>> getCellFactory() {
            return cc -> new CListCell<>(textProvider);
        }
        
        public ListCell<T> getButtonCell() {
            return getCellFactory().call(null);
        }
        
        public static class CListCell<T> extends ListCell<T> {
            
            private Function<T, String> converter;
    
            public CListCell(Function<T, String> converter) {
                this.converter = Objects.requireNonNull(converter, "converter must not be null");
            }
    
            @Override
            protected void updateItem(T item, boolean empty) {
                super.updateItem(item, empty);
                if (empty) {
                    setText(null);
                } else {
                    setText(converter.apply(item));
                }
            }
            
        }
    
    }
    

    用于创建和配置组合的 fxml:

    <?xml version="1.0" encoding="UTF-8"?>
    <?import javafx.scene.control.ComboBox?>
    
    <ComboBox  fx:id="combo1" items="${itemLoader.items}"  
        cellFactory="${cellFactoryProvider.cellFactory}" 
        buttonCell = "${cellFactoryProvider.buttonCell}"
        prefWidth="150.0" 
       xmlns:fx="http://javafx.com/fxml/1">
    </ComboBox>
    

    一个使用它的例子:

    public class LocaleLoaderApp extends Application {
    
        private ComboBox<Locale> loadCombo(Object itemLoader, Function<Locale, String> extractor) throws IOException {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("comboloader.fxml"));
            loader.getNamespace().put("itemLoader", itemLoader);
            loader.getNamespace().put("cellFactoryProvider", new ListCellFactory<Locale>(extractor));
            ComboBox<Locale> combo = loader.load();
            return combo;
        }
        
        @Override
        public void start(Stage primaryStage) throws IOException {
    
            primaryStage.setTitle("Populate combo from custom builder");
    
            Group group = new Group();
            GridPane grid = new GridPane();
            grid.setPadding(new Insets(25, 25, 25, 25));
            group.getChildren().add(grid);
            LocaleProvider provider = new LocaleProvider();
            grid.add(loadCombo(provider, Locale::getDisplayName), 0, 0);
            grid.add(loadCombo(provider, Locale::getLanguage), 1, 0);
            Scene scene = new Scene(group, 450, 175);
    
            primaryStage.setScene(scene);
            primaryStage.show();
        }
        
        public static class LocaleProvider {
            ObservableList<Locale> locales = FXCollections.observableArrayList(Locale.getAvailableLocales());
            
            public ObservableList<Locale> getItems() {
                return locales;
            }
        }
        
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    【讨论】:

    • 让你工作也很好(0:它比答案更广泛并且非常有用。我喜欢使用Function&lt;Locale, String&gt; extractor。在我对另一个问题的评论中,我问这个问题:@ 987654326@。所有这些好东西都记录在案了吗?感谢您的全面回答。
    【解决方案2】:

    由 kleopatra 编辑了以下 cmets:
    使用字符串加载问题中给出的combo.fxml 可以使用以下加载器完成:

    //load observable list with strings
    public class ComboStringLoader {
    
        private final ObservableList<String> items;
    
        public ComboStringLoader() {
            items = FXCollections.observableArrayList(createStrings());
        }
    
        private List<String> createStrings() {
                return IntStream.rangeClosed(0, 5)
                        .mapToObj(i -> "String "+i)
                        .map(String::new)
                        .collect(Collectors.toList());
        }
    
        //name of this method corresponds to itemLoader.items in xml.
        //if xml name was itemLoader.a this method should have been getA(). 
        public ObservableList<String> getItems(){
            return items;
        }
    }
    

    以类似的方式加载带有Item 实例的组合仅仅意味着Item#toString 用于组合中的文本:

    //load observable list with Item#toString
    public class ComboObjectLoader1 {
    
        public ObservableList<Item> items;
    
        public ComboObjectLoader1() {
            items = FXCollections.observableArrayList(createItems());
        }
    
        private List<Item> createItems() {
            return IntStream.rangeClosed(0, 5)
                    .mapToObj(i -> "Item "+i)
                    .map(Item::new)
                    .collect(Collectors.toList());
        }
    
        public ObservableList<Item> getItems(){
            return items;
        }
    }
    

    Item 定义为:

    class Item {
    
        private final StringProperty name = new SimpleStringProperty();
    
        public Item(String name) {
            this.name.set(name);
        }
    
        public final StringProperty nameProperty() {
            return name;
        }
    
        @Override
        public String toString() {
            return name.getValue();
        }
    }
    

    更好的方法是使用自定义ListCell&lt;item&gt; 加载组合:

    //load observable list with custom ListCell
    public class ComboObjectLoader2 {
    
        private final ObservableList<ItemListCell> items;
    
        public ComboObjectLoader2() {
            items =FXCollections.observableArrayList (createCells());
        }
    
        private List<ItemListCell> createCells() {
                return IntStream.rangeClosed(0, 5)
                        .mapToObj(i -> "Item "+i)
                        .map(Item::new)
                        .map(ItemListCell::new)
                        .collect(Collectors.toList());
        }
    
        public ObservableList<ItemListCell> getItems(){
            return items;
        }
    }
    
    class ItemListCell extends ListCell<Item> {
    
        private final Label text;
    
        public ItemListCell(Item item) {
            text = new Label(item.nameProperty().get());
            setGraphic(new Pane(text));
        }
    
        @Override
        public void updateItem(Item item, boolean empty) {
            super.updateItem(item, empty);
            if (empty) {
                setText(null);
                setGraphic(null);
            } else {
                text.setText(item.nameProperty().get());
            }
        }
    }
    

    最后但并非最不重要的替代方法是将自定义 ListCell&lt;Item&gt; 设置为组合的单元工厂。
    这可以通过将控制器添加到 fxml 文件来完成:
    combo2.fxml:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.ComboBox?>
    <ComboBox fx:id="combo1" items="${itemLoader.items}" prefWidth="150.0" xmlns:fx="http://javafx.com/fxml/1" 
          xmlns="http://javafx.com/javafx/10.0.1" fx:controller="test.ComboObjectLoaderAndController">
    </ComboBox>
    

    ComboObjectLoaderAndController 既是加载器又是控制器:

    //loads observable list with Items and serves as controller to set cell factory
    public class ComboObjectLoaderAndController {
    
        public ObservableList<Item> items;
        @FXML ComboBox<Item> combo1;
    
        public ComboObjectLoaderAndController() {
            items = FXCollections.observableArrayList(createItems());
        }
    
        @FXML
        public void initialize() {
            combo1.setCellFactory(l->new ItemListCell());
        }
    
        private List<Item> createItems() {
            return IntStream.rangeClosed(0, 5)
                    .mapToObj(i -> "Item "+i)
                    .map(Item::new)
                    .collect(Collectors.toList());
        }
    
        public ObservableList<Item> getItems(){
            return items;
        }
    
        class ItemListCell extends  ListCell<Item>{
    
            @Override
            public void updateItem(Item item, boolean empty) {
                super.updateItem(item, empty);
                if (empty) {
                    setText(null);
                    setGraphic(null);
                } else {
                    setText(item.nameProperty().get());
                }
            }
        }
    }
    

    编辑:
    kleopatra's answer 之后,我添加了一个通用自定义ListCell

    public class ObjectListCell<T> extends ListCell<T> {
    
        Function<T,String> textSupplier;
    
        public  ObjectListCell(Function<T,String> textSupplier) {
            this.textSupplier = textSupplier;
        }
    
        public Callback<ListView<T>, ListCell<T>> getFactory() {
            return cc -> new ObjectListCell<>(textSupplier);
        }
    
        public ListCell<T> getButtonCell() {
            return getFactory().call(null);
        }
    
        @Override
        public void updateItem(T t, boolean empty) {
            super.updateItem(t, empty);
            if (t== null || empty) {
                setText(null);
                setGraphic(null);
            } else {
                setText(textSupplier.apply(t));
            }
        }
    }
    

    工厂在fxml文件中设置:
    combo3.fxml:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.ComboBox?>
    <ComboBox fx:id="combo1" items="${itemLoader.items}"  cellFactory="${cellFactoryProvider.factory}" 
       buttonCell = "${cellFactoryProvider.buttonCell}"
       prefWidth="150.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/10.0.1">
    </ComboBox>
    

    一个测试类:

    public class ComboTest extends Application {
    
        @Override
        public void start(Stage primaryStage) throws IOException {
    
            primaryStage.setTitle("Populate combo from custom builder");
    
            //Combo of Strings
            FXMLLoader loader = new FXMLLoader(getClass().getResource("combo.fxml"));
            loader.getNamespace().put("itemLoader", new ComboStringLoader());
            ComboBox<String>stringCombo = loader.load();
    
            //Combo of Item
            loader = new FXMLLoader(getClass().getResource("combo.fxml"));
            loader.getNamespace().put("itemLoader", new ComboObjectLoader1());
            ComboBox<Item>objectsCombo1 = loader.load();
    
            //Combo of custom ListCell
            loader = new FXMLLoader(getClass().getResource("combo.fxml"));
            loader.getNamespace().put("itemLoader", new ComboObjectLoader2());
            ComboBox<ItemListCell>objectsCombo2 = loader.load();
    
            //Combo of Item with custom ListCell factory
            loader = new FXMLLoader(getClass().getResource("combo2.fxml"));
            loader.getNamespace().put("itemLoader", new ComboObjectLoaderAndController());
            ComboBox<Item>objectsCombo3 = loader.load();
    
            //Combo of Item with custom ListCell factory. Factory is set in FXML
            loader = new FXMLLoader(getClass().getResource("combo3.fxml"));
            loader.getNamespace().put("itemLoader", new ComboObjectLoader1());
            loader.getNamespace().put("cellFactoryProvider", new ObjectListCell<Item>(t -> t.nameProperty().get()));
            ComboBox<Item>objectsCombo4 = loader.load();
    
            HBox pane = new HBox(25, stringCombo, objectsCombo1,objectsCombo2, objectsCombo3, objectsCombo4);
            pane.setPadding(new Insets(25, 25, 25, 25));
    
            Scene scene = new Scene(pane, 550, 175);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    【讨论】:

    • 出于视图原因不要覆盖 toString,而是使用适当的 updateItem 实现自定义 listCell
    • @kleopatra 这意味着我必须添加一个控制器(或使用加载器)来设置工厂。或者有没有办法在 fxml 中定义工厂引用?
    • 不 - 你为什么这么认为?自定义单元格是唯一的方法
    • @kleopatra 是的。我的评论/问题是关于如何将自定义 ListCell 应用于组合。 AFAIK 它需要setCellFactory 因此我的问题是:“这意味着我必须添加一个控制器(或使用加载器作为一个)来设置工厂。或者有没有办法在 fxml 中定义工厂引用?”
    • 我的评论是(可能不够清楚,抱歉):实现一个自定义单元格并在 cellFactory 中使用它(可能需要公开为自定义类,老实说,不在乎)。我关心的是(以及为您赢得了我的反对票;)是答案中第一句话的一部分在您的答案中添加一个 toString 方法它可以正常工作 - 这是完全错误的,无论问题是什么。它不能解决任何问题,只会在上面涂抹油漆。
    猜你喜欢
    • 1970-01-01
    • 2022-01-19
    • 1970-01-01
    • 2013-10-25
    • 1970-01-01
    • 1970-01-01
    • 2013-05-13
    • 1970-01-01
    • 2012-01-07
    相关资源
    最近更新 更多