JavaFX - 将FXML包装成Java类控制器

5

我有一个包含TreeView控件的FXML文件:

<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="500.0" xmlns:fx="http://javafx.com/fxml" fx:controller="test.MyControllerClass">

<TreeView fx:id="locationTreeView" layoutX="12.0" layoutY="158.0" prefHeight="193.0" prefWidth="471.0" />

然后我的Java类控制器需要用这个TreeView包装起来,并动态地添加TreeItem。 问题在于,它没有加载那些TreeItem。下面是我的控制器中的测试代码:

public class MyControllerClass extends Application {

    @FXML
    private TreeView<String> locationTreeView;

    @Override
    public void start(Stage stage) throws Exception {

        stage.initStyle(StageStyle.TRANSPARENT);
        stage.getIcons().add(new Image(getClass().getResourceAsStream("myIcon.png")));

        Parent root = FXMLLoader.load(getClass().getResource("myInterface.fxml"));

        Scene scene = new Scene(root);
        stage.setScene(scene);

        loadTreeItems();

        stage.show();
    }

    // Just a simple example that still doesn't works
    private void loadTreeItems() {

        try {
            TreeItem<String> root = new TreeItem<String>("Root Node");
            root.setExpanded(true);
            root.getChildren().addAll(
                new TreeItem<String>("Item 1"),
                new TreeItem<String>("Item 2"),
                new TreeItem<String>("Item 3")
            );

            locationTreeView = new TreeView<String>(root);

        } catch (Exception exc) {
            System.out.println("Error: " + exc.getMessage());
        }
    }

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

有什么想法为什么它不起作用?
2个回答

8
有几个原因导致您的应用程序无法工作:
  1. 您需要将控制器和应用程序分开成不同的类。
  2. 您应该允许FXML系统注入TreeView实例,而不是创建一个新实例(正如Aaron在他的答案中指出的那样)。

您当前的应用程序结构方式会发生以下情况:

  1. Java系统将在启动时创建MyControllerClass的一个实例,并调用它的start方法。
  2. FXMLLoader将在每次加载myInterface.fxml文件时创建另一个MyControllerClass的新实例。
  3. FXMLLoader将创建一个新的TreeView实例,并在它创建的新MyControllerClass实例的locationTreeView成员上执行FXML注入。
  4. FXMLLoader将尝试在新的MyControllerClass上调用initialize方法(其中没有任何内容)。
  5. FXMLLoader不会在新的MyControllerClass上调用start方法。
  6. 您原始的MyControllerClass上的start方法调用将继续处理并创建另一个新的TreeView实例,然后将其设置为旧MyControllerClass实例的locationTreeView成员。

我更新了您的代码,以进行我上面建议的修改,现在代码可以正常工作。更新后的代码可用

运行代码的示例截图如下: dynamictreeview

MyApplicationClass.java

import javafx.animation.*;
import javafx.application.Application;
import javafx.event.*;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.stage.*;
import javafx.util.Duration;

/** Sample application to demonstrate programming an FXML interface. */ 
public class MyApplicationClass extends Application {
  @Override public void start(final Stage stage) throws Exception {
    // load the scene fxml UI.
    // grabs the UI scenegraph view from the loader.
    // grabs the UI controller for the view from the loader.
    final FXMLLoader loader = new FXMLLoader(getClass().getResource("myInterface.fxml"));
    final Parent root = (Parent) loader.load();
    final MyControllerClass controller = loader.<MyControllerClass>getController();

    // continuously refresh the TreeItems.
    // demonstrates using controller methods to manipulate the controlled UI.
    final Timeline timeline = new Timeline(
      new KeyFrame(
        Duration.seconds(3), 
        new TreeLoadingEventHandler(controller)
      )
    );
    timeline.setCycleCount(Timeline.INDEFINITE);
    timeline.play();

    // close the app if the user clicks on anywhere on the window.
    // just provides a simple way to kill the demo app.
    root.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
      @Override public void handle(MouseEvent t) {
        stage.hide();
      }
    });

    // initialize the stage.
    stage.setScene(new Scene(root));
    stage.initStyle(StageStyle.TRANSPARENT);
    stage.getIcons().add(new Image(getClass().getResourceAsStream("myIcon.png")));
    stage.show();
  }

  /** small helper class for handling tree loading events. */
  private class TreeLoadingEventHandler implements EventHandler<ActionEvent> {
    private MyControllerClass controller;
    private int idx = 0;

    TreeLoadingEventHandler(MyControllerClass controller) {
      this.controller = controller;
    }

    @Override public void handle(ActionEvent t) {
      controller.loadTreeItems("Loaded " + idx, "Loaded " + (idx + 1), "Loaded " + (idx + 2));
      idx += 3;
    }
  }

  // main method is only for legacy support - java 8 won't call it for a javafx application.
  public static void main(String[] args) { launch(args); }
}

MyControllerClass.java

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

/** Sample controller class. */ 
public class MyControllerClass {
  // the FXML annotation tells the loader to inject this variable before invoking initialize.
  @FXML private TreeView<String> locationTreeView;

  // the initialize method is automatically invoked by the FXMLLoader - it's magic
  public void initialize() {
    loadTreeItems("initial 1", "initial 2", "initial 3");
  }  

  // loads some strings into the tree in the application UI.
  public void loadTreeItems(String... rootItems) {
    TreeItem<String> root = new TreeItem<String>("Root Node");
    root.setExpanded(true);
    for (String itemString: rootItems) {
      root.getChildren().add(new TreeItem<String>(itemString));
    }

    locationTreeView.setRoot(root);
  }
}

myInterface.fxml

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

<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns:fx="http://javafx.com/fxml" fx:controller="test.MyControllerClass">
  <TreeView fx:id="locationTreeView" layoutX="0" layoutY="0" prefHeight="193.0" prefWidth="471.0" />
</AnchorPane>

你不需要将类分开。你只需要在MyControllerClass类中有一个“initialize”方法。 - Nuno Rafael Figueiredo
我同意这是一个好的实践,但是主阶段与应用程序类具有一对一的关系,两个连接点都是强制性的。因此,你写了一个多余的类...(尽管这个评论是不相关的)... - Nuno Rafael Figueiredo
@jewelsea 不好意思,你是对的。我混合了两个概念导致了副作用(initialize方法似乎运行了两次),但是现在已经通过将它们分开解决了。 - Nuno Rafael Figueiredo
我似乎无法访问示例代码。有人有备份吗? - starlord7
@starlord7,我更新了答案,将来自Github链接的示例代码嵌入其中。 - jewelsea
显示剩余4条评论

2
在你的loadTreeItems()函数中,你创建了一个新的TreeView实例。这个实例替换了在FXML文件中定义并附加到场景中的那个实例,它是场景图中的一部分。
要向通过FXMLLoader创建的TreeView添加项目,可以使用setRoot()函数。
private void loadTreeItems() {

    try {
        TreeItem<String> root = new TreeItem<String>("Root Node");
        root.setExpanded(true);
        root.getChildren().addAll(
            new TreeItem<String>("Item 1"),
            new TreeItem<String>("Item 2"),
            new TreeItem<String>("Item 3")
        );

        locationTreeView.setRoot(root);

    } catch (Exception exc) {
        System.out.println("Error: " + exc.getMessage());
    }
}

谢谢你的回答,@Aaron。但是它没有起作用。我刚刚创建了一个TreeView,没有TreeItems。这可能是问题吗? - Leonardo
我的意思是:在FXML文件中创建了一个TreeView。 - Leonardo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接