В коде есть две проблемы, которые у вас есть:
- Для привязки выражений F XML вам необходимо предоставить свойства вашего класса, а не только сами значения. Это относится как к
ObservableList
s, так и к обычным значениям. Таким образом, ваш TodoItemsVBox
класс должен предоставить ListProperty todoItemsProperty()
- F XML привязок выражений (т.е.
${todoTasks}
), ссылающихся на FXMLLoader
s namespace
, а не на контроллер. Контроллер автоматически вводится в пространство имен (с ключом "controller"
), поэтому, учитывая, что список задач хранится в вашем контроллере (что не обязательно является хорошей идеей), вы можете использовать ${controller.todoTasks}
здесь.
Вот минимальная, полная версия вашего приложения, которая работает.
Основа c TodoItem. java:
public class TodoItem {
private final String name ;
public TodoItem(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
}
A 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() ;
}
}
Простой контроллер:
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()));
}
}
Файл F XML (TodoList.f xml):
<?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);
}
}
Действительно, контроллеру не место для хранения данных; для этого у вас должен быть отдельный класс модели, который используется совместно с контроллером и представлением. Это достаточно просто сделать здесь; вам просто нужно немного поработать с FXMLLoader
(а именно поместить модель в пространство имен и вручную создать и настроить контроллер).
Например:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class TodoModel {
private ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList();
public ObservableList<TodoItem> getTodoTasks() {
return todoTasks;
}
}
Тогда ваш контроллер становится:
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()));
}
}
Измените F XML для использования
<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();
}
Преимущество этого Подход заключается в том, что ваши данные теперь отделены от пользовательского интерфейса (как вида, так и контроллера), что становится необходимым, если вы хотите получить доступ к тем же данным в другой части пользовательского интерфейса (который будет использовать другой F XML и другой контроллер).