FXML 为 ComboBox 和 TableView 动态初始化 ObservableList
FXML Dynamically initialize ObservableList for ComboBox and TableView
我正在尝试制作 Dan Nicks 在 对 this question 的评论 中提出的自定义生成器。
这个想法是在构造它之前设置组合的数据。
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>
提供数据的class:
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>
编辑了 kleopatra 的以下评论:
使用以下加载程序可以使用字符串加载问题中给出的 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;
}
}
其中项目定义为:
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<item>
:
//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<Item>
设置为组合的细胞工厂。
这可以通过向 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());
}
}
}
}
编辑:
在 之后我添加了一个通用自定义 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>
测试class:
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);
}
}
从吹毛求疵开始,我做了一些关于如何实际实施我在对 的评论中概述的内容的实验。
基本思想是对 listCell 遵循与数据相同的方法,即通过命名空间配置内容和外观(我今天的学习项目)。成分:
- 可配置的通用自定义 listCell,具有将项目转换为文本的功能
- 通用“cellFactory 工厂”class 用于提供创建该单元的 cellFactory
cell/factory:
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);
}
}
我正在尝试制作 Dan Nicks 在 对 this question 的评论 中提出的自定义生成器。
这个想法是在构造它之前设置组合的数据。
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>
提供数据的class:
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>
编辑了 kleopatra 的以下评论:
使用以下加载程序可以使用字符串加载问题中给出的 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;
}
}
其中项目定义为:
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<item>
:
//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<Item>
设置为组合的细胞工厂。
这可以通过向 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());
}
}
}
}
编辑:
在 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>
测试class:
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);
}
}
从吹毛求疵开始,我做了一些关于如何实际实施我在对
基本思想是对 listCell 遵循与数据相同的方法,即通过命名空间配置内容和外观(我今天的学习项目)。成分:
- 可配置的通用自定义 listCell,具有将项目转换为文本的功能
- 通用“cellFactory 工厂”class 用于提供创建该单元的 cellFactory
cell/factory:
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);
}
}