我遇到了一个设计问题,这与Javafx控制器的事件处理和初始化的顺序有关。
每当选择"对应的选项卡"时,我想更新TABPANE。为了实现这一目标,我使用FXML进行注册事件处理程序,如下所示:
<Tab fx:id="browseCollectionTab" onSelectionChanged="#tabChanged" text="Browse Images">
如果处理代码,我得到了
之类的东西@FXML
private void tabChanged() throws IOException{
if(browseCollectionTab.isSelected())
updateImageView();
}
updateImageView依次使用依赖注入传递给控制器的数据源加载图像。
选项1:目前,此依赖注入如下:
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFile));
Parent root = fxmlLoader.load();
AbstractController ctrl = (AbstractController)fxmlLoader.getController();
ctrl.setModel(this.model);
ctrl.setUp();
选项2 我可以使用控制器的初始化方法()方法使用单例将其初始化。这确实打破了依赖注入,这不是我首选的解决方案。
选项3 我可以避免使用FXML并手动实例化所有内容。这使我可以在调用Javafx/FXML之前实例化控制器并执行依赖注入。在线上有许多示例,最终都陷入了复杂的GUI。我想坚持FXMlloader,因为这似乎是一种整洁而舒适的方式。如果这实际上不是最佳实践,请指出。
选项4 我可以在控制器的初始化方法中手动注册事件处理程序(或为此,在执行依赖项注入/设置控制器之后)。首先,这无视将事件处理程序定义为FXML的重点。
那么,选项1和2的问题是什么?在上,在控制器上执行任何初始化之前,TabChanged实际上是调用的,导致无效指针异常。现在,我可以忽略所有事件,直到控制器初始化为止 - 这可能是一个坏主意,因为只会错过一次的事件。另一个选择是在(可能)许多事件处理程序中执行初始化。这似乎也不是一个可行的选择。
我必须缺少明显的东西。我知道这与常见的设计选择/最佳实践有关;但是,我无法将正确的关键字喂给Google。
我期待您的帮助/建议 - 谢谢!
您显示的示例实际上是一个非常不寻常的示例:通常在加载过程完成之前无法调用事件处理程序。选项卡选择是一种异常,因为您确实在响应可能通过编程方式发生的属性的变化,并且当将选项卡添加到空的选项卡窗格中时确实会发生。因此,这是一个不寻常的情况,可以在加载完成之前调用事件处理程序。
简单解决方案
考虑将控制器与FXML文件关联的方式。一个选项是从FXML文件中删除fx:controller
属性,然后在代码中设置控制器。这使您有机会首先正确初始化控制器:
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFile));
AbstractController ctrl = new ConcreteControllerImplementation();
ctrl.setModel(this.model);
ctrl.setUp();
Parent root = fxmlLoader.load();
更复杂的方法
另一个更为复杂的选项是使用控制器工厂。这是将控制器类映射到实际控制器实例的函数。在这种情况下,您仍然在FXML文件中具有fx:controller
属性(创建FXML文件的标准方法可以视为受益人,因为它为Scene -Builder提供了一个检查方法和@FXML
通知字段的机会,等等)。另一个好处是,控制器工厂被传播到<fx:include>
包含的任何FXML文件,这也使您可以在使用它们的控制器之前初始化它们。
在下面我假设您的model
是类型Model
:
首先,定义您的控制器具有将Model
作为参数的构造函数,即。
public class ConcreteControllerImplementation extends AbstractController {
private final Model model ;
public ConcreteControllerImplementation(Model model) {
this.model = model ;
// do setup here, not in separate method...
}
public void initialize(URL url, ResourceBundle resources) {
// normal controller setup stuff here
// any @FXML annotated fields are now initialized
}
}
要创建可重复使用的控制器工厂,您需要一些反射:
Model model = ... ;
Callback<Class<?>, Object> controllerFactory = type -> {
try {
for (Constructor<?> c : type.getConstructors()) {
if (c.getParameterCount() == 1 && c.getParameterTypes()[0].equals(Model.class)) {
return c.newInstance(model);
}
}
// no matching constructor: just use default (no-arg) constructor:
return type.newInstance();
} catch (Exception exc) {
// fatal...
throw new RuntimeException(exc);
}
};
然后您只做
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFile));
fxmlLoader.setControllerFactory(controllerFactory);
Parent root = fxmlLoader.load();
此技术还允许您使用依赖注入框架。例如。如果您使用春季,则可以做
ApplicationContext context = ... ;
FXMLLoader loader = new FXMLLoader(getClass().getResource(...));
loader.setControllerFactory(context::getBean);
现在,您的控制器实例将由Spring Bean Factory创建和管理,您可以使用Spring依赖注入将模型注入其中。