Javafx 将Spring与FXML嵌套控制器集成
我正在使用JavaFX实现一个大型应用程序,但不确定如何处理嵌套控制器和SpringJavafx 将Spring与FXML嵌套控制器集成,javafx,javafx-8,Javafx,Javafx 8,我正在使用JavaFX实现一个大型应用程序,但不确定如何处理嵌套控制器和Spring FXML已经由设计团队提供,并且使用include机制具有多达3个级别的嵌套FXML 模型将在春季定义 我需要将模型注入到所有嵌套的控制器中 同一类型的控制器有多个实例。i、 e.屏幕部分相同,但由同一型号的不同实例供电 我目前的工作 我已经阅读了一些其他的问题,但是这些问题只涉及顶级控制器 我尝试了FXMLLoader.setControllerFactory()机制,并在应用程序上下文中定义了控制器,
- FXML已经由设计团队提供,并且使用include机制具有多达3个级别的嵌套FXML
- 模型将在春季定义
- 我需要将模型注入到所有嵌套的控制器中
- 同一类型的控制器有多个实例。i、 e.屏幕部分相同,但由同一型号的不同实例供电
- 我已经阅读了一些其他的问题,但是这些问题只涉及顶级控制器
- 我尝试了FXMLLoader.setControllerFactory()机制,并在应用程序上下文中定义了控制器,但是这只提供了要创建的控制器类,这意味着无法区分相同类型但数据不同的两个控制器
loader.setControllerFactory(new Callback<Class<?>, Object>() {
@Override
public Object call(Class<?> param) {
// OK but doesn't work when multiple instances controller of same type
return context.getBean(param);
}
});
顶层控制器
public class TopLevelController {
@FXML
private NestedController aController;
@FXML
private NestedController bController;
@Autowired
@Qualifier("modelA")
private Model a;
@Autowired
@Qualifier("modelB")
private Model b;
@PostConstruct
public void init() {
aController.setModel(a);
bController.setModel(b);
}
public NestedController getAController() {
return aController;
}
public NestedController getBController() {
return bController;
}
}
我看不出有什么方法可以完全干净地做到这一点。正如您所注意到的,控制器工厂获得的唯一信息是控制器类,因此无法区分实例。您可以使用FXML向这些控制器提供更多的值,但是这些值将在FXML初始化阶段设置,这将在Spring初始化阶段之后发生。(我认为这实际上就是你的
@PostConstruct
不起作用的原因:@PostConstruct
方法将在构建之后,但在FXML注入之前被调用。)Spring注入控制器应该始终具有原型
作用域,imho,因为您永远不会希望不同的FXML加载元素共享同一个控制器
我认为这样做的方法是使用模型的原型
作用域将模型实例注入嵌套的控制器(因此每个控制器都有自己的模型),然后从顶级控制器配置这些模型。这仍然需要“手工”进行一些连接(您不能使用Spring将您想要的值直接注入到模型中),但它感觉比您上面概述的方法要干净一些。当然,不确定它是否真的适用于您的实际用例
因此:
application-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="model" class="application.Model" scope="prototype" />
<bean id="nestedController" class="application.NestedController" scope="prototype" autowire="byName"/>
<bean id="topController" class="application.TopLevelController" scope="prototype" />
</beans>
NestedController.java:
package application;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class NestedController {
@FXML
private Label label ;
private Model model ;
public Model getModel() {
return model ;
}
public void setModel(Model model) {
this.model = model ;
}
public void initialize() {
label.textProperty().bindBidirectional(model.textProperty());
}
}
Nested.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tab?>
<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.NestedController">
<center>
<Label fx:id="label"/>
</center>
</BorderPane>
应用程序集相当简单:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class NestedControllersSpring extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
try (ClassPathXmlApplicationContext context
= new ClassPathXmlApplicationContext("application-context.xml")) {
FXMLLoader loader = new FXMLLoader(getClass().getResource("TopLevel.fxml"));
loader.setControllerFactory(cls -> context.getBean(cls));
primaryStage.setScene(new Scene(loader.load(), 600, 400));
primaryStage.show();
}
}
public static void main(String[] args) {
launch(args);
}
}
我能做到的最好的约会
- 递归地遍历控制器,只要存在FXML注释,而不是节点
- 使用autowireBean连接@Inject/@Autowire字段
- 使用initializeBean()调用@PostConstruct(如果存在)
无需使用递归函数,即可实现更简单的操作:
public class GuiContext {
private static final ApplicationContext applicationContext = new AnnotationConfigApplicationContext(GuiAppConfiguration.class);
public Object loadFxml(final String fileName) throws IOException {
return FXMLLoader.load(App.class.getResource(fileName), null, null, applicationContext::getBean);
}
}
此外,还可以在类的开头添加“原型”
@Component
@Scope("prototype")
public class ExampleController implements Initializable {
谢谢你花时间回答这个问题,我知道这是一个充满文字的问题。我会接受的,但会推迟一天,看看有没有其他人有什么好主意。如果使用controller factory,我理解您关于声明控制器原型的观点,但是使模型bean成为原型并不适合我的情况-模型需要在应用程序上下文中唯一可识别-这将从其他bean(网络客户端)写入。很抱歉,我在问题中没有说清楚。我想知道你是否需要这个。如果它实际上没有回答你的问题,不要接受它(但可以编辑该问题以澄清该要求)。也许有一个聪明的把戏在某处;特别是如果您可以更改FXML的执行方式(使用自定义类包装FXML加载程序,而不是使用
,因为在这种情况下,您可以将信息从FXML传递到嵌套组件)。仅供参考,我最终提出了解决方案,请参阅我的答案。无论如何谢谢你的帮助。。。
package application;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class NestedController {
@FXML
private Label label ;
private Model model ;
public Model getModel() {
return model ;
}
public void setModel(Model model) {
this.model = model ;
}
public void initialize() {
label.textProperty().bindBidirectional(model.textProperty());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tab?>
<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.NestedController">
<center>
<Label fx:id="label"/>
</center>
</BorderPane>
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.Tab?>
<BorderPane xmlns:fx="http://javafx.com/fxml/1"
fx:controller="application.TopLevelController">
<center>
<TabPane>
<tabs>
<Tab text="First">
<content>
<fx:include fx:id="firstTab" source="Nested.fxml" />
</content>
</Tab>
<Tab>
<content>
<fx:include fx:id="secondTab" source="Nested.fxml" />
</content>
</Tab>
</tabs>
</TabPane>
</center>
</BorderPane>
package application;
import javafx.fxml.FXML;
public class TopLevelController {
@FXML
private NestedController firstTabController ;
@FXML
private NestedController secondTabController ;
public void initialize() {
firstTabController.getModel().setText("First Tab");
secondTabController.getModel().setText("Second Tab");
}
}
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class NestedControllersSpring extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
try (ClassPathXmlApplicationContext context
= new ClassPathXmlApplicationContext("application-context.xml")) {
FXMLLoader loader = new FXMLLoader(getClass().getResource("TopLevel.fxml"));
loader.setControllerFactory(cls -> context.getBean(cls));
primaryStage.setScene(new Scene(loader.load(), 600, 400));
primaryStage.show();
}
}
public static void main(String[] args) {
launch(args);
}
}
public void recursiveWire(ClassPathXmlApplicationContext context, Object root) throws Exception {
context.getAutowireCapableBeanFactory().autowireBean(root);
context.getAutowireCapableBeanFactory().initializeBean(root, null);
for (Field field : root.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(FXML.class) &&
! Node.class.isAssignableFrom(field.getType())) {
// <== assume if not a Node, must be a controller
recursiveWire(context, field.get(root));
}
}
}
@Override
public void start(Stage stage) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/app-context.xml");
FXMLLoader loader = new FXMLLoader(getClass().getResource("/top-level.fxml"));
stage.setScene(new Scene(loader.load()));
recursiveWire(context, loader.getController());
stage.show();
}
public class GuiContext {
private static final ApplicationContext applicationContext = new AnnotationConfigApplicationContext(GuiAppConfiguration.class);
public Object loadFxml(final String fileName) throws IOException {
return FXMLLoader.load(App.class.getResource(fileName), null, null, applicationContext::getBean);
}
}
@Component
@Scope("prototype")
public class ExampleController implements Initializable {