提问者:小点点

JavaFx自定义控件和Spring DI


我正在尝试向我在这里找到的示例添加自定义控件:https://spring.io/blog/2019/01/16/spring-tips-javafx(代码:https://github.com/ecovaci/javafx-spring-boot/tree/master)

当然,我的目标是能够在我的控制器中自动连接业务服务:

@Service
public class TestService {
  public void test() {
    System.out.println("test");
  }
}

我可以通过非自定义控件中的构造函数注入来做到这一点,如下所示:

@Component
public class SimpleUiController {

    private final HostServices hostServices;

    @FXML
    public Label label;

    @FXML
    public Button button;

    private TestService testService;

    public SimpleUiController(HostServices hostServices, TestService testService) {
        Assert.notNull(testService);
        this.hostServices = hostServices;
        this.testService = testService;
    }

    @FXML
    public void initialize () {
        this.button.setOnAction(actionEvent -> this.testService.test());
    }
}

接下来我想在 fxml 中添加一个带有随附控制器的自定义控件。所以我停止天真地复制了这里找到的代码片段:https://docs.oracle.com/javafx/2/fxml_get_started/custom_control.htm

<!-- custom_control.fxml -->
<fx:root type="javafx.scene.layout.VBox" xmlns:fx="http://javafx.com/fxml">
  <TextField fx:id="textField"/>
  <Button text="Click Me" />
</fx:root>
@Component
public class CustomControl extends VBox {
    @FXML
    private TextField textField;

    private TestService testService;

    public CustomControl(TestService testService) {
        Assert.notNull(testService);
        this.testService = testService;

        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/custom_control.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }

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

    public void setText(String value) {
        textProperty().set(value);
    }

    public StringProperty textProperty() {
        return textField.textProperty();
    }
}

现在我可以像这样使用我的自定义控件:

<!--ui.fxml-->
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.example.javafx.SimpleUiController">
      <Button fx:id="button" text="Button" />
      <Label fx:id="label" text="Label" />

      <CustomControl text="100"/>
</VBox>

但是当我添加自定义控件时,我得到以下异常。

Exception in Application start method
Caused by: java.lang.NoSuchMethodException: com.example.javafx.CustomControl.<init>()

我是JavaFx的新手,很高兴能找到一些关于与Spring集成的文章。但我没有找到任何关于自定义控件和spring的参考资料。感觉我是唯一一个试图实现这一目标的人,我是不是错过了什么?


共1个答案

匿名用户

按照James_D的建议,通过添加自定义构建器工厂修复了该问题。

@Component
public class BeanBuilderFactory implements BuilderFactory {

  @Autowired
  private ConfigurableApplicationContext context;

  @Override
  public Builder<?> getBuilder(Class<?> type) {
    try {
      Object bean = this.context.getBean(type);
      if (bean.getClass().isAssignableFrom(type)) {

        if (bean instanceof CustomControl) {
          return new CustomControlBuilder((CustomControl) bean);
        }
      }
      JavaFXBuilderFactory javaFXBuilderFactory = new JavaFXBuilderFactory();
      return javaFXBuilderFactory.getBuilder(bean.getClass());
    } catch (BeansException e) {
      return null;
    }
  }

我必须为我的定制组件创建一个构建器。我想也有机会使用反射,但我没有得到工作。

public class CustomControlBuilder implements Builder<CustomControl> {

  private CustomControl customControl;

  public CustomControlBuilder(CustomControl customControl) {
    this.customControl = customControl;
  }

  public String getText() {
    return customControl.getText();
  }

  public void setText(String value) {
    customControl.setText(value);
  }

  @Override
  public CustomControl build() {
    return customControl;
  }
}

下面是我添加构建器工厂的原始示例中的 stageListener:

@Component
public class StageListener implements ApplicationListener<JavafxApplication.StageReadyEvent> {

    private final String applicationTitle;
    private final Resource fxml;
    private final ApplicationContext applicationContext;
    private final BeanBuilderFactory beanBuilderFactory;

    public StageListener(@Value("${spring.application.ui.title}") String applicationTitle,
                         @Value("classpath:/ui.fxml") Resource fxml, ApplicationContext applicationContext,
                         BeanBuilderFactory aBeanBuilderFactory) {
        this.applicationTitle = applicationTitle;
        this.fxml = fxml;
        this.applicationContext = applicationContext;
        this.beanBuilderFactory = aBeanBuilderFactory;
    }

    @Override
    public void onApplicationEvent(JavafxApplication.StageReadyEvent stageReadyEvent) {
        try {
            Stage stage = stageReadyEvent.getStage();
            URL url = fxml.getURL();
            FXMLLoader fxmlLoader = new FXMLLoader(url);
            fxmlLoader.setControllerFactory(applicationContext::getBean);
            fxmlLoader.setBuilderFactory(beanBuilderFactory);
            Parent root = fxmlLoader.load();
            Scene scene = new Scene(root, 600, 600);
            stage.setScene(scene);
            stage.setTitle(this.applicationTitle);
            stage.show();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}