将 StringProperty 绑定到单例中的标签

Bind StringProperty to Label from a Singleton

我有一个叫MenuText的单身人士。它负责在菜单中显示正确的文本。它会动态更新。

public class MenuText implements LanguageObserver {

    private MenuText() { //I want this to be private, only one instance should exist
        Config.getInstance().subscribe(this);
    }

    private static class MenuTextHolder {
        private static final MenuText INSTANCE = new MenuText();
    }

    public static MenuText getInstance() {
        return MenuTextHolder.INSTANCE;
    }

    @Override
    public void update(Language language) {
        System.out.println("Updating...");
        switch (language) {
            case ENGLISH -> {
                setText("Play");
            }
            case GERMAN -> {
                setText("Spielen");
            }
        }
    }

    private final StringProperty text = new SimpleStringProperty("Play");

    public StringProperty textProperty() {
        return text;
    }

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

    private void setText(String text) {
        this.text.set(text);
    }
}

我有一个 fxml 文件,但 MenuText 无法引用它。 (这会与 MVVM 架构风格相矛盾)

<?import tiles.text.MenuText?>
<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
      prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="tiles.viewModel.GameMenuViewModel">

<!--here I want to bind to the text property-->
<Button text="???"/>

</VBox>

最初我使用 <fx:define> 从 fxml 文件设置对 MenuText 的引用,但这不允许私有构造函数。应该没那么难,因为 MenuText 是静态的,但我无法对它的单例进行静态引用。

我试过了<Button text="${MenuText.getInstance().text}">


更新

回答中所述,我不应该使用单例模式。 基于 this 我添加了一个 ApplicationFactory:

//Creation of items with application lifetime
public class ApplicationFactory {

    private Config config;

    public void build() {
        config = new Config();
    }

    public Config getConfig() {
        return config;
    }
}

这是正确的做法吗?我现在有一个 MenuFactory,它也是在 JavaFX start() 方法中创建的。它设置场景的父级。

public class MenuFactory {
    public Parent getMenu(Config config, String fxmlLocation) {
        MenuText menuText = new MenuText(config);
        MenuViewModel menuViewModel = new MenuViewModel(config);

        try {
            FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(getClass().getResource(fxmlLocation)));
            loader.getNamespace().put("menuText", menuText);
            return loader.load();
        } catch (IOException e) {
            //...
        }
    }
}

start() 方法如下所示:

    @Override
    public void start(Stage primaryStage) {
        ApplicationFactory applicationFactory = new ApplicationFactory();
        applicationFactory.build();

        MenuFactory menuFactory = new MenuFactory();

        Parent root = menuFactory.getMenu(applicationFactory.getConfig(), MENU);
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();

    }

这让事情变得更加复杂,我不确定这是否正确。此外,我仍然不知道如何在 fxml 文件中设置 MenuText。我试过了,但我认为这不是在 fxml 中设置命名空间的正确方法。

    <fx:define>
        <MenuText fx:id="menuText"/>
    </fx:define>

我读过 these documentations 但不明白如何设置这个自定义命名空间。

请注意单例模式是 widely considered to be an anti-pattern。但是,如果你真的想这样做:

Initially I used <fx:define> to setup a reference to the MenuText from the fxml file

这是正确的做法。您可以将它与 fx:factory 结合使用,以获取对没有 (public) 默认构造函数的 class 实例的引用:

<fx:define>
    <MenuText fx:factory="getInstance" fx:id="menuText" />
</fx:define>

然后

<Button text="${menuText.text}" />

另一个解决方案是“手动”将 MenuText 实例插入 FXML 命名空间:

MenuText menuText = ... ;
FXMLLoader loader = new FXMLLoader(getClass().getResource(...));
loader.getNamespace().put("menuText", menuText);
Parent root = loader.load();

然后

<Button text="${menuText.text}" />

无需额外的 FXML 代码即可使用。这种方法允许您避免使用单例模式(您基本上是将依赖项注入 FXML,因此您可以轻松地将它与 DI 框架结合起来)。