将Spring与FXML嵌套控制器集成

时间:2015-02-26 11:39:31

标签: javafx javafx-8

我使用JavaFX实现了一个大型应用程序,但不确定如何处理嵌套控制器和Spring。

  • FXML已由设计团队提供,使用包含机制最多可达3级嵌套FXML。

  • 模型将在Spring中定义

  • 我需要将模型注入所有嵌套控制器
  • 有多个相同类型的控制器实例。即屏幕的某些部分是相同的,但由相同型号的不同实例供电。

到目前为止我的工作

  • 我已阅读Stephen Chin's blog - JavaFX in Spring Day 2 – Configuration and FXML以及其他SO问题,但这些只涉及顶级控制人员。

  • 我尝试使用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);
    }
});
  • 到目前为止,我最好的方法是使用Stephen Chin方法连接顶层控制器。
    • 对于具有多个bean实例的情况,父控制器将通过@Autowire / @Qitifer获取对特定bean的引用,然后在相应的控制器上设置。
    • 通过将它们暴露在顶层控制器并调用autowire()
    • ,也可以连接下一级控制器。

具体问题

  • 无论如何使用控制器工厂机制,所以我可以在spring上下文中定义控制器,以便更容易连接它们?
  • 我还可以使用其他方法吗?

实施例

Spring上下文

<context:annotation-config />
<bean id="modelA" class="org.example.Model">
    <property name="value" value="a value" />
</bean>
<bean id="modelB" class="org.example.Model">
    <property name="value" value="b value" />
</bean>

顶级

<HBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.TopLevelController">
   <children>
      <TabPane tabClosingPolicy="UNAVAILABLE">
        <tabs>
          <Tab text="A">
               <content>
                  <fx:include fx:id="a" source="nested.fxml" />
               </content>
          </Tab>
          <Tab text="B">
               <content>
                  <fx:include fx:id="b" source="nested.fxml" />
               </content>
          </Tab>
        </tabs>
      </TabPane>
   </children>
</HBox>

嵌套级别

<HBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.NestedController">
   <children>
      <TextField fx:id="value" />
   </children>
</HBox>

申请主

public class NestedControllersSpring extends Application {
    public static void main(String[] args) {
        launch(args);
    }
    @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()));
        TopLevelController top = loader.getController();
        context.getAutowireCapableBeanFactory().autowireBean(top);
        context.getAutowireCapableBeanFactory().autowireBean(top.getAController());
        context.getAutowireCapableBeanFactory().autowireBean(top.getBController());
        top.init(); // needed because autowire doesn't call @PostConstruct
        stage.show();
    }
}

顶级控制器

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;
    }
}

3 个答案:

答案 0 :(得分:4)

我没有办法完全干净地完成这项工作。正如您所注意到的,控制器工厂获得的唯一信息是控制器类,因此无法区分实例。您可以使用FXML为这些控制器提供更多值,但这些值将在FXML初始化阶段设置,这将在Spring初始化阶段之后进行。 (我认为这实际上是你的@PostConstruct不起作用的原因:@PostConstruct方法将在构造之后调用,但在FXML注入之前。)弹簧注入的控制器总是< / em>拥有prototype范围,imho,因为您永远不会希望不同的FXML加载元素共享同一个控制器。

我认为我这样做的方法是使用模型的prototype范围将模型实例注入嵌套控制器(因此每个控制器都有自己的模型),然后从顶部配置这些模型级控制器。这仍然需要“手动”进行一些布线(你不能使用Spring将你想要的值直接注入到模型中),但感觉比上面概述的方法更清晰。当然,不确定它是否真的适用于您的真实用例。

所以:

应用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>

Model.java:

package application;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Model {
    private final StringProperty text = new SimpleStringProperty();

    public final StringProperty textProperty() {
        return this.text;
    }

    public final String getText() {
        return this.textProperty().get();
    }

    public final void setText(final String text) {
        this.textProperty().set(text);
    }

}

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>

TopLevel.fxml:

<?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>

TopLevelController必须将值连接到模型中,这有点痛苦。我不确定这是否比用手连接整个模型更清洁......

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);
    }
}

答案 1 :(得分:0)

我能成功约会......

  • 在存在FXML注释的任何地方递归通过控制器,而不是节点
  • 使用autowireBean连接@Inject / @Autowire字段
  • 使用initializeBean()调用@PostConstruct(如果存在)。

代码

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();
}

答案 2 :(得分:0)

此外,可以在课程开始时添加“原型”。

@Component
@Scope("prototype")
public class ExampleController implements Initializable {