如何使用相同的模型对象初始化JavaFX控制器?

28

场景

我正在创建一个GUI,其中多个视图引用同一个模型对象。

我习惯的做法

在Swing中,如果我希望所有视图都引用同一模型,则会将模型传递给构造函数。

我目前采取的做法

在JavaFX中,我通过在视图/控制器(菜单栏、拆分窗格、选项卡等)中具有setter方法来传递模型,在每个视图/控制器加载完成后。我认为这种方法很笨拙且繁琐。此外,我发现它行不通,因为在某些情况下,我需要在控制器部件初始化之前就已经存在于控制器中。

平淡无奇的替代方案

(注意: 我参考了下列stackoverflow问题:

  • Javafx 2.0 How-to Application.getParameters() in a Controller.java file
  • Passing Parameters JavaFX FXML
  • Multiple FXML with Controllers, share object
  • Passing parameters to a controller when loading an FXML)

  • 依赖注入

  • 将模型对象保存为公共静态变量

    • 这是一个选项,但目前我不喜欢让一个公共静态模型变得如此开放和可用。显然,我可以将其作为私有静态变量,并拥有getter和setter,但我也不太喜欢这种想法。
  • 从调用者向控制器传递参数

    • 我希望每个控制器在其构造函数中加载自身,并且希望每个自定义控制器自动注入其父控制器。例如,卡片概述选项卡会像这样加载自身:

      public CardOverviewTab() {
          FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("card_overview_tab.fxml"));
          fxmlLoader.setRoot(content);
          fxmlLoader.setController(this);
      
          try {
              fxmlLoader.load();
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
      
      而 SingleGameSetup 控制器会自动将卡牌概述选项卡注入到一个变量中:
      public class SingleGameSetupController extends AnchorPane {
      
          @FXML private CardOverviewTab cardOverviewTab;
      
          // Rest of the class
      }
      
    • 包含卡片概览标签页的fxml部分如下所示:

    • <CardOverviewTab fx:id="cardOverviewTab" />
      
    • 这种方式可以避免手动加载控制器的问题,但是我仍然需要解决设置模型的问题。

  • 在FXMLLoader上设置一个控制器

    • 这个选项类似于我习惯的方式,将模型作为参数传递到构造函数中,但仍然需要手动使用FXMLLoader加载控制器。
  • 事件总线

    • 我没有仔细研究过这个,但从我所读的内容来看,事件总线似乎已经不再活跃和过时了。
  • 单例模式

    • 这类似于拥有一个公共静态引用的模型对象,控制器可以检索该对象,但我仍在寻找更好的解决方案。此外,我不想要一个单例模型。

我的期望

有没有一种比较简便的方法来传递模型对象?我正在寻找一种像将模型传递给构造函数那样简单的方法,但我不想通过手动加载FXMLLoader来处理控制器,或者在控制器加载后设置模型。也许拥有一个类来检索模型是最好的选择,但我仅是询问是否有更好的方法。


2
通过参数传递JavaFX FXML讨论了多种将数据传递到FXML控制器的机制。请仔细查看它们。如果有任何一种方法适合您,请用您采取的方法回答您自己的问题。如果没有一个好的解决方案,则需在问题中添加更多详细信息,例如您已经尝试了什么以及为什么它不令人满意。 - jewelsea
1
将以下与编程有关的内容从英语翻译成中文。只返回翻译后的文本:研究工作和添加细节+1 - jewelsea
3个回答

16

更新

除了afterburner.fx之外,还可以查看Gluon Ignite

Gluon Ignite允许开发人员在其JavaFX应用程序中使用流行的依赖注入框架,包括在其FXML控制器内部。 Gluon Ignite在几个流行的依赖注入框架(目前是Guice、Spring和Dagger)之上创建了一个通用抽象,但我们计划随着需求的出现而添加更多的框架。通过完全支持JSR-330,Gluon Ignite使在JavaFX应用程序中使用依赖注入变得容易。

模型对象进入控制器也是通过 @Inject 进行注入,类似于 afterburner.fx。

建议的方法

由于您似乎正在寻找依赖注入框架,我认为您最好使用 afterburner.fx框架

afterburner.fx提供了一种将模型对象注入到您的JavaFX控制器中的方法,使用标准的Java @Inject 注释即可实现。

替代依赖注入系统

Spring非常庞大且复杂,除非您需要应用程序的许多其他功能,否则不应考虑使用它,因为它太过复杂。

Guice比Spring简单得多,如果您需要具有众多功能(如提供程序类)的依赖注入框架,则是一个合理的选择。但从听起来的声音来看,您并不需要Guice提供的所有功能,因为您只想找到一种在应用程序中传递对象的单例实例的方法,而无需显式查找它们。

因此,请尝试使用afterburner.fx,并查看它是否符合您的需求。

afterburner.fx示例代码

这是使用afterburner.fx将模型实例(NotesStore)注入控制器的示例。该示例直接从afterburner.fx文档中复制而来。

import com.airhacks.afterburner.views.FXMLView;

public class NoteListView extends FXMLView {
    //usually nothing to do, FXML and CSS are automatically
    //loaded and instantiated
}

public class AirpadPresenter implements Initializable {    
    @Inject // injected by afterburner, zero configuration required
    NotesStore store;

    @FXML // injected by FXML
    AnchorPane noteList;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        //view constructed from FXML
        NoteListView noteListView = new NoteListView();

        //fetching and integrating the view from FXML
        Parent view = noteListView.getView();
        this.noteList.getChildren().add(view);
    }
}

followme.fx是一个基本的示例应用程序,演示了如何使用afterburner.fx。我在使用它时遇到了一些问题,由于Maven依赖不兼容,所以forked它的代码并修复了一些问题,这些问题阻止我直接使用它。

回答评论中的其他问题

那么从NoteStore示例中,您是说我只需要添加afterburner框架依赖项并在我的模型变量上放置@Inject即可吗?

不,您还需要创建一个扩展FXMLView的关联类,并使用new调用实例化它(类似于上面示例代码中创建NotesListView)。如果您有兴趣继续研究afterburner.fx框架,则使用followme.fx项目作为基础,因为它提供了使用该框架的非常简单的可执行示例的完整源代码。

我尝试使用Google Guice并使其工作...您将在构造函数中手动注入游戏设置对象。

我认为您不应该手动使用Guice注入器。 我认为您可以在FXMLLoader实例上设置控制器工厂来启动注入。 这是afterburner.fx中FXMLView的做法。 在Guice中使用的注入机制的确切细节将与afterburner.fx机制有所不同,但我认为设置控制器工厂的基本概念仍然相似。

在答案中有一个使用FXML和Guice设置控制器工厂的演示:Associating FXML and Controller in Guice's Module configuration

很遗憾没有更直接的方法可以做到这一点而不会给您带来如此多的困难。

作为一个无关紧要的个人侧记,我对依赖注入框架总体持观望态度。当然,它们可以帮助我们解决问题,但是对于简单的事情,我通常会采用具有getInstance方法的单例模式而不是更复杂的框架。尽管如此,在大型项目中,我确实能看到它们的用处,并且在某些Java框架中它们非常受欢迎。

从NoteStore的例子来看,你是说我只需要添加afterburner框架依赖并在我的模型变量上加上@Inject注解就可以了吗?因为我尝试过了,但它没有起作用:public class MainController extends Tab { `@Inject private GameSettings gameSettings; ... } P.S. 我尝试格式化这个代码,但它似乎没有完全生效。 - j will
我尝试使用Google Guice并使其工作,但是我遇到了一些问题。例如,如果您查看此文件https://github.com/quicksilversly/Dominator/blob/master/src/main/java/dominion/application/controller/SingleGameSetupController.java,您将会看到在构造函数中手动注入了一个游戏设置对象。我认为这种方式失去了DI的好处。我不认为通过@FXML注入SingleGameSetupController的父级MainController有帮助。 - j will
根据额外的问题更新答案。 - jewelsea

1
我一直在研究这个问题,发现依赖注入加上单例模式(用于模型)是最优设计。我了解到,在每个控制器中为模型提供setter方法是一种依赖注入方式,通过构造函数传递模型也是依赖注入的一种形式。Spring和Guice是帮助实现依赖注入的框架。
我尝试使用Spring,如此处所述:http://www.zenjava.com/2011/10/23/better-controller-injection/ 但当配置类尝试加载控制器时,它无法加载成员控制器,这可能不是Spring的问题,但我认为我没有足够的时间去解决它。此外,我还必须编辑所有的控制器文件。

在搜索并阅读了大量资料后,我发现这篇文章最能表达我想要实现的内容:https://forums.oracle.com/thread/2301217?tstart=0。由于该文章提到的改进仅是建议,JavaFX中尚不存在这些改进。但是当它们存在时,我会进行实现。只拿注入模型到fxml中作为一个例子:

<usevar name="model" type="com.mycom.myapp.ModelObject"/>

2
我提醒大家不要过多地阅读关于FXML可能未来增强功能的论坛讨论。这些讨论不太可能导致对FXML的更改,因为讨论参与者已不再与JavaFX相关联。如果您希望在未来的JavaFX版本中考虑特定功能,请创建一个功能请求。 - jewelsea

0

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