JavaFX fxml - 如何将Spring DI与嵌套的自定义控件一起使用? [英] JavaFX fxml - How to use Spring DI with nested custom controls?

查看:173
本文介绍了JavaFX fxml - 如何将Spring DI与嵌套的自定义控件一起使用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经完成了一些关于将Spring DI与Jav​​aFx集成的教程,但是我已经碰到了简单的例子没有涉及的问题(我无法弄清楚)。

I've been through a number of tutorials on integrating Spring DI with JavaFx but I've hit a wall that the simple examples dont cover (and I cant figure out).

我想在视图层和表示层之间进行清晰的分离。我想使用fxml定义可组合视图,使用Spring将它们连接在一起。这是一个具体的例子:

I want clean separation between the view and presentation layers. I would like to use fxml to define composable views and Spring to wire it all together. Here's a concrete example:

Dashboard.fxml:

Dashboard.fxml:

<GridPane fx:id="view"
          fx:controller="com.scrub.presenters.DashboardPresenter"
          xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml">
   <children>
      <TransactionHistoryPresenter fx:id="transactionHistory"  />
   </children>
</GridPane>

Main.java:

Main.java:

public void start(Stage primaryStage) throws Exception{
    try {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppFactory.class);
        SpringFxmlLoader loader = context.getBean(SpringFxmlLoader.class);
        primaryStage.setScene(new Scene((Parent)loader.load("/views/dashboard.fxml")));
        primaryStage.setTitle("Hello World");
        primaryStage.show();
    } catch(Exception e) {
        e.printStackTrace();
    }
}

SpringFxmlLoader.java:

SpringFxmlLoader.java:

public class SpringFxmlLoader {

    @Autowired
    ApplicationContext context;

    public Object load(String url) {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource(url));
            loader.setControllerFactory(new Callback<Class<?>, Object>() {
                @Override
                public Object call(Class<?> aClass) {
                    return context.getBean(aClass);
                }
            });
            return loader.load();
        } catch(Exception e) {
            e.printStackTrace();
            throw new RuntimeException(String.format("Failed to load FXML file '%s'", url));
        }
    }
}

所以当 DashboardPresenter时加载SpringFxmlLoader正确地使用loader.setControllerFactory注入控制器。

So when DashboardPresenter gets loaded the SpringFxmlLoader correctly injects the controller with the loader.setControllerFactory.

然而,自定义 TransactionHistoryPresenter 控件加载了一个新实例,而不是来自春天的背景。它必须使用自己的FXMLLoader?

However, the custom TransactionHistoryPresenter control is loaded with a new instance and not from the spring context. It must be using its own FXMLLoader?

如何使用自定义控件的任何想法都能与Spring搭配好玩吗?我真的不想让控制器/演示者手动连接它们。

Any ideas how to make custom controls play nice with Spring? I really dont want to go down the path of having the controllers / presenters manually wiring them up.

推荐答案

这里的主要问题,确保在JavaFX应用程序的同一个线程上初始化Spring。这通常意味着必须在JavaFX应用程序线程上执行Spring代码;其他耗时的工作当然可以在他们自己的线程上执行。

The main problem here, is make sure that Spring is initialized on the same thread of the JavaFX application. This usually means that Spring code must be executed on the JavaFX application thread; other time-consuming jobs can of course be executed on their own thread.

这是我用本教程和我自己的 Spring Boot

@SpringBootApplication
@ImportResource("classpath:root-context.xml")
public class JavaFXSpringApplication extends Application {

  private static final Logger log = LoggerFactory.getLogger(JavaFXSpringApplication.class);

  private Messages messages;

  private static String[] args;

  @Override
  public void start(final Stage primaryStage) {

    // Bootstrap Spring context here.
    ApplicationContext context = SpringApplication.run(JavaFXSpringApplication.class, args);
    messages = context.getBean(Messages.class);
    MainPaneController mainPaneController = context.getBean(MainPaneController.class);

    // Create a Scene
    Scene scene = new Scene((Parent) mainPaneController.getRoot());
    scene.getStylesheets().add(getClass().getResource("/css/application.css").toExternalForm());

    // Set the scene on the primary stage
    primaryStage.setScene(scene);
    // Any other shenanigans on the primary stage...
    primaryStage.show();
  }

  public static void main(String[] args) {

    JavaFXSpringApplication.args = args;

    launch(args);
  }
}

此类是JavaFX应用程序入口点和Spring Boot初始化入口点,因此传递了varargs。导入外部配置文件可以更容易地使主类保持整洁,同时准备其他与Spring相关的东西(即设置Spring Data JPA,资源包,安全性......)

This class is both a JavaFX application entry point and a Spring Boot initialization entry point, hence the passing around of varargs. Importing an external configuration file makes it easier to keep the main class uncluttered while getting other Spring-related stuff ready (i.e. setting up Spring Data JPA, resource bundles, security...)

在JavaFXstart方法上,主ApplicationContext被初始化并生效。此时使用的任何bean都必须通过ApplicationContext.getBean()检索,但是所有其他带注释的bean(假设它位于此主类的后代包中)将始终可以访问。

On the JavaFX "start" method, the main ApplicationContext is initialized and lives. Any bean used at this point must be retrieved via ApplicationContext.getBean(), but every other annotated bean (provided it is in a descendant package of this main class) will be accessible as always.

特别是,控制器在这个其他类中声明:

In particular, Controllers are declared in this other class:

@Configuration
@ComponentScan
public class ApplicationConfiguration {

  @Bean
  public MainPaneController mainPaneController() throws IOException {
    return (MainPaneController) this.loadController("path/to/MainPane.fxml");
  }

  protected Object loadController(String url) throws IOException {
    InputStream fxmlStream = null;
    try {
      fxmlStream = getClass().getResourceAsStream(url);
      FXMLLoader loader = new FXMLLoader();
      loader.load(fxmlStream);
      return loader.getController();
    } finally {
      if (fxmlStream != null) {
        fxmlStream.close();
      }
    }
  }
}

你可以看到任何一个Controller(我只有一个,但它可以很多)用@Bean注释,整个类是一个配置。

You can see any Controller (I have just one, but it can be many) is annotated with @Bean and the whole class is a Configuration.

最后,这里是MainPaneController。

Finally, here is MainPaneController.

public class MainPaneController {

  @Autowired
  private Service aService;

  @PostConstruct
  public void init() {
    // ...stuff to do with components...
  }

  /*
   * FXML Fields
   */
  @FXML
  private Node root;

  @FXML
  private TextArea aTextArea;

  @FXML
  private TextField aTextField;

  @FXML
  private void sayButtonAction(ActionEvent event) {
    aService.doStuff(aTextArea, aTextField);
  }
}

此Controller被声明为@Bean,所以它可以@Autowired与任何其他@Beans(或服务,组件等)。现在,例如,您可以让它回答按钮按下并将其字段上执行的逻辑委托给@Service。 Spring中创建的控制器中声明的任何组件都将由Spring管理,从而了解上下文。

This Controller is declared as a @Bean, so it can be @Autowired with and from any other @Beans (or Services, Components, etc.). Now for example you can have it answer to a button press and delegate logic performed on its fields to a @Service. Any component declared into the Spring-created Controllers will be managed by Spring and thus aware of the context.

配置非常简单直接。如果您有任何疑问,请随时询问。

It is all quite easy and straightforward to configure. Feel free to ask if you have any doubts.

这篇关于JavaFX fxml - 如何将Spring DI与嵌套的自定义控件一起使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆