【问题标题】:JavaFx Custom Controls and Spring DIJavaFx 自定义控件和 Spring DI
【发布时间】:2021-06-01 13:54:01
【问题描述】:

我正在尝试将自定义控件添加到我在此处找到的示例中: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 中添加一个自定义控件,并附带一个控制器。 所以我天真地复制了这里找到的代码 sn-ps: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 集成的文章。但是我没有找到任何关于自定义控件和弹簧的参考资料。几乎感觉就像我是唯一一个试图实现这一目标的人,我错过了什么吗?

【问题讨论】:

  • 当您在 FXML 文件中包含这样的自定义控件时会发生什么,FXMLLoader 通过反射实例化控件,调用(默认情况下)无参数构造函数。您需要安排FXMLLoader 从spring 应用程序上下文中检索控件的实例。我认为这样做的方法是在FXMLLoader 上使用自定义builder factory
  • 我认为如果无法从 Spring 获得对象,则(已删除)答案中的代码问题是返回 null。 (所以我怀疑它在 FXML 中的 ButtonLabel 上失败,而不是在自定义控件上。在您的自定义构建器工厂中创建一个 JavaFxBuilderFactory ,如果没有合格则返回 getBuilder() 的结果豆子。
  • 我也遇到了以下错误:属性“文本”不存在或者是只读的。由于我正在创建的构建器不会从我的 bean 中公开我的 getter 和 setter。您是否也有针对 @James_D 的优雅解决方案? ;o)
  • 我认为这只是将您的包打开到javafx.fxml 以便它可以使用反射的情况,但我不确定。

标签: java spring javafx dependency-injection


【解决方案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);
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-29
    • 1970-01-01
    • 2018-08-05
    • 1970-01-01
    • 1970-01-01
    • 2021-08-12
    相关资源
    最近更新 更多