gpt4 book ai didi

java - 通过参数将集合传递/绑定(bind)到 FXML 中的自定义组件(从 vbox 扩展)

转载 作者:行者123 更新时间:2023-12-02 08:55:50 25 4
gpt4 key购买 nike

在我的应用程序中,我声明了一个如下所示的自定义组件:

@DefaultProperty("todoItems")
public class TodoItemsVBox extends VBox {
private ObservableList<TodoItem> todoItems;

// Setter/Getter omitted
}

现在在fxml中的某个地方我想使用TodoItemsVBox组件,如下所示:

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>

<BorderPane prefHeight="600" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.todolist.controller.TodoListController"
stylesheets="@../css/app.css">
<top>
<HBox spacing="10.0">
<TextField fx:id="input" layoutX="35.0" layoutY="64.0" prefWidth="431.0" promptText="Enter todo task" HBox.hgrow="ALWAYS" onAction="#addTask"/>
<Button layoutX="216.0" layoutY="107.0" mnemonicParsing="false" onAction="#addTask" prefHeight="27.0" prefWidth="70.0" text="Add" HBox.hgrow="ALWAYS" />
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</HBox>
</top>
<center>
<ScrollPane fitToHeight="true" fitToWidth="true" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${todoTasks}"/>
</ScrollPane>
</center>

...所以我们可以看到 fxml 有它的 Controller TodoListController

public class TodoListController implements {
private final ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList(/*Fill in the collection somehow - for now doesn't matter*/);

@FXML
private TodoItemsVBox todoItemsVBox;

// Setter/Getter omitted
}

所以,我想做的是:通过这样的构造将 todoTasks 传递到 FXML 中定义的 TodoItemsVBox 中: todoItems="${todoTasks}" ---- 不幸的是,这并不像我预期的那样工作,因为 fxml 文件在 Controller 初始化之前加载,因此 todoTasks 始终为 null。我还在 TodoItemsVBox 中尝试了带有一个 arg 构造函数的 @NamedArg - 它甚至失败并出现异常:“无法绑定(bind)到无类型对象。”

有人可以建议一种解决方案,如何通过其参数将 Controller 中定义的对象集合传递到自定义组件中吗?

最佳答案

您的代码有两个问题:

  1. 对于 FXML 表达式绑定(bind),您需要公开类中的属性,而不仅仅是值本身。这适用于 ObservableList 以及常规值。因此,您的 TodoItemsVBox 类需要公开 ListProperty todoItemsProperty()
  2. FXML 表达式绑定(bind)(即 ${todoTasks})引用 FXMLLoadernamespace ,而不是 Controller 。 Controller 会自动注入(inject)到命名空间中(使用键 "controller"),因此,鉴于任务列表存储在您的 Controller 中(这不一定是一个好主意),您可以使用 ${controller.todoTasks} 此处。

这是您的应用程序的最小完整版本,可以运行。

基本的 TodoItem.java:

public class TodoItem {

private final String name ;
public TodoItem(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
}

将列表公开为属性的 TodoItemsVBox:

import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;

public class TodoItemsVBox extends VBox {

private ListProperty<TodoItem> todoItems = new SimpleListProperty<>();

public TodoItemsVBox() {
// not efficient but works for demo:
todoItems.addListener((Change<? extends TodoItem> c) -> rebuildView());
}

private void rebuildView() {
getChildren().clear();
todoItems.stream()
.map(TodoItem::getName)
.map(Label::new)
.forEach(getChildren()::add);
}

public ListProperty<TodoItem> todoItemsProperty() {
return todoItems ;
}

public ObservableList<TodoItem> getTodoItems() {
return todoItemsProperty().get() ;
}

}

一个简单的 Controller :

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;

public class TodoListController {
private ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList();

// not actually needed...
@FXML
private TodoItemsVBox todoItemsVBox;

@FXML
private TextField input ;


public ObservableList<TodoItem> getTodoTasks() {
return todoTasks;
}


@FXML
private void addTask() {
todoTasks.add(new TodoItem(input.getText()));
}
}

FXML 文件 (TodoList.fxml):

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>

<?import org.jamesd.examples.TodoItemsVBox ?>

<BorderPane prefHeight="600" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.todolist.controller.TodoListController"
>

<top>
<HBox spacing="10.0">
<TextField fx:id="input" promptText="Enter todo task" HBox.hgrow="ALWAYS" onAction="#addTask"/>
<Button onAction="#addTask" text="Add" HBox.hgrow="ALWAYS" />
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</HBox>
</top>
<center>
<ScrollPane fitToWidth="true" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${controller.todoTasks}"/>
</ScrollPane>
</center>
</BorderPane>

最后是应用程序类:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class TodoApp extends Application {

@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("TodoList.fxml"));
Scene scene = new Scene(loader.load());
primaryStage.setScene(scene);
primaryStage.show();
}

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

}
<小时/>

确实, Controller 不是存储数据的地方;它是存储数据的地方。您应该有一个单独的模型类来执行此操作,该模型类在 Controller 和 View 之间共享。在这里这样做相当简单;您只需要使用 FXMLLoader 做更多的工作(即将模型放入命名空间中,并手动创建和设置 Controller )。

例如:

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

public class TodoModel {

private ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList();

public ObservableList<TodoItem> getTodoTasks() {
return todoTasks;
}
}

然后你的 Controller 变成:

import javafx.fxml.FXML;
import javafx.scene.control.TextField;

public class TodoListController {

// not actually needed...
@FXML
private TodoItemsVBox todoItemsVBox;

@FXML
private TextField input ;

private TodoModel model ;

public TodoModel getModel() {
return model;
}

public void setModel(TodoModel model) {
this.model = model;
}

@FXML
private void addTask() {
model.getTodoTasks().add(new TodoItem(input.getText()));
}
}

修改要使用的 FXML

<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${model.todoTasks}"/>

最后组装应用程序

public void start(Stage primaryStage) throws Exception {

TodoModel model = new TodoModel();

FXMLLoader loader = new FXMLLoader(getClass().getResource("TodoList.fxml"));
loader.getNamespace().put("model", model);
Scene scene = new Scene(loader.load());

TodoListController controller = loader.getController();
controller.setModel(model);

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

这种方法的优点是,您的数据现在与 UI( View 和 Controller )分离,如果您想在 UI 的另一部分(将使用另一个 FXML 和另一个 FXML)访问相同的数据,这一点就变得至关重要。 Controller )。

关于java - 通过参数将集合传递/绑定(bind)到 FXML 中的自定义组件(从 vbox 扩展),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60489411/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com