JavaFX 将重复 View 同步到同一 Controller (FXML 和 MVC)

标签 java model-view-controller javafx fxml

下面是一个说明问题的小应用程序:

按钮面板.fxml

<ScrollPane fx:controller="ButtonPanelController">
    <VBox>
        <Button fx:id="myButton" text="Click Me" onAction="#buttonClickedAction" />
    </VBox>
</ScrollPane>

ButtonPanelController.java

public class ButtonPanelController {

    @FXML
    Button myButton;

    boolean isRed = false;

    public void buttonClickedAction(ActionEvent event) {
        if(isRed) {
            myButton.setStyle("");
        } else {
            myButton.setStyle("-fx-background-color: red");
        }
        isRed = !isRed;
    }
}

测试应用程序.java

public class TestApp extends Application {

    ButtonPanelController buttonController;

    @Override
    public void start(Stage stage) throws Exception {
        // 1st Stage
        stage.setTitle("1st Stage");
        stage.setWidth(200);
        stage.setHeight(200);
        stage.setResizable(false);

        // Load FXML
        FXMLLoader loader = new FXMLLoader(
                ButtonPanelController.class.getResource("ButtonPanel.fxml"));
        Parent root = (Parent) loader.load();

        // Grab the instance of ButtonPanelController
        buttonController = loader.getController();

        // Show 1st Scene
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();

        // 2nd Stage
        Stage stage2 = new Stage();
        stage2.setTitle("2nd Stage");
        stage2.setWidth(200);
        stage2.setHeight(200);
        stage2.setResizable(false);

        /* Override the ControllerFactory callback to use 
         * the stored instance of ButtonPanelController
         * instead of creating a new one.
         */
        Callback<Class<?>, Object> controllerFactory = type -> {
            if(type == ButtonPanelController.class) {
                return buttonController;
            } else {
                try {
                    return type.newInstance();
                } catch(Exception e) {
                    throw new RuntimeException(e);
                }
            }
        };

        // Load FXML
        FXMLLoader loader2 = new FXMLLoader(
                ButtonPanelController.class.getResource("ButtonPanel.fxml"));
        // Set the ControllerFactory before the load takes place
        loader2.setControllerFactory(controllerFactory);
        Parent root2 = (Parent) loader2.load();

        // Show 2nd Scene
        Scene scene2 = new Scene(root2);
        stage2.setScene(scene2);
        stage2.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

基本上,我有一个 FXML,用于两个不同的场景,这两个场景可能同时在屏幕上处于 Activity 状态,也可能不处于 Activity 状态。这方面的一个实际示例是将内容停靠到侧面板加上一个按钮,该按钮可以在可以拖动/调整大小/等的单独窗口中打开相同的内容。

我试图实现的目标是保持 View 同步(对一个 View 的更改会影响另一个 View )。

我可以通过回调将两个 View 指向同一个 Controller ,但是我现在遇到的问题是 UI 更改仅反射(reflect)在第二个场景中。两个 View 都与 Controller 对话,但 Controller 只与第二个场景对话。我假设 JavaFX 的 MVC 或 IOC 实现在通过 FXMLLoader 加载时以某种 1:1 的关系将 Controller 链接到 View 。

我很清楚尝试将两个 View 链接到 1 个 Controller 是一种糟糕的 MVC 做法,但是我想避免必须实现实际上相同的单独 FXML 和 Controller 。

是否可以实现我上面列出的这种同步?

如果我需要创建一个单独的 Controller ,确保两个 UI 同步(甚至小到边栏移动)的最佳方法是什么?

提前致谢!

-史蒂夫

最佳答案

您的代码不起作用的原因是 FXMLLoader 将对 FXML 中具有 fx:id 属性的元素的引用注入(inject)到具有匹配名称的 Controller 中的字段中。因此,当您第一次加载 FXML 文件时,FXMLLoader 将字段 myButton 设置为对其在加载 FXML 时创建的按钮的引用。由于您在第二次加载 FXML 时使用完全相同的 Controller 实例,因此 FXMLLoader 现在将相同的字段(在相同的 Controller 实例中)设置为对它在 FXML 文件时创建的按钮的引用再次加载。换句话说,buttonController.myButton 现在指的是创建的第二个按钮,而不是第一个。因此,当您调用 myButton.setStyle(...) 时,它会更新第二个按钮的样式。

基本上,您总是希望每个 View 实例有一个 Controller 实例。您需要的是两个 Controller 访问相同的共享状态。

创建一个存储数据的模型类。在 MVC 架构中,View 观察模型并在模型中的数据发生变化时进行更新。 Controller 对用户与 View 的交互作出 react 并更新模型。

(可以说,FXML 给你更多的是 MVP 架构,这是相似的。这也有变体,但通常呈现器会观察模型并在模型中的数据更改时更新 View ,以及更新响应用户交互的模型。)

所以你的模型可能看起来像:

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;

public class Model {

    private final BooleanProperty red = new SimpleBooleanProperty();

    public final BooleanProperty redProperty() {
        return this.red;
    }


    public final boolean isRed() {
        return this.redProperty().get();
    }


    public final void setRed(final boolean red) {
        this.redProperty().set(red);
    }


    public void toggleRed() {
        setRed(! isRed() );
    }
}

您的 ButtonPanel.fxml 没有改变:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>

<ScrollPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="ButtonPanelController">
    <VBox >
        <Button fx:id="myButton" text="Click Me" onAction="#buttonClickedAction" />
    </VBox>
</ScrollPane>

您的 Controller 具有对模型的引用。它可以在模型属性上使用绑定(bind)或监听器来更新 UI,而处理程序方法只更新模型:

import javafx.beans.binding.Bindings;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;

public class ButtonPanelController {

    @FXML
    Button myButton;

    boolean isRed = false;

    private Model model ;

    public ButtonPanelController(Model model) {
        this.model = model ;
    }

    public void initialize() {

        myButton.styleProperty().bind(Bindings.
                when(model.redProperty()).
                then("-fx-background-color: red;").
                otherwise("")
        );
    }

    public void buttonClickedAction(ActionEvent event) {
        model.toggleRed();
    }
}

最后,您可以使所有内容保持同步,因为 View 是同一模型的 View 。换句话说,您只需创建一个模型并将其引用传递给两个 Controller 。由于我将模型作为 Controller 中的构造函数参数(这很好,因为您知道实例一创建就拥有一个模型),我们需要一个 Controller 工厂来创建 Controller 实例:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.util.Callback;

public class TestApp extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        // 1st Stage
        stage.setTitle("1st Stage");
        stage.setWidth(200);
        stage.setHeight(200);
        stage.setResizable(false);

        // The one and only model we will use for both views and controllers:
        Model model = new Model();

        /* Override the ControllerFactory callback to create 
         * the controller using the model:
         */
        Callback<Class<?>, Object> controllerFactory = type -> {
            if(type == ButtonPanelController.class) {
                return new ButtonPanelController(model);
            } else {
                try {
                    return type.newInstance();
                } catch(Exception e) {
                    throw new RuntimeException(e);
                }
            }
        };

        // Load FXML
        FXMLLoader loader = new FXMLLoader(
                ButtonPanelController.class.getResource("ButtonPanel.fxml"));
        loader.setControllerFactory(controllerFactory);

        Parent root = (Parent) loader.load();

        // Show 1st Scene
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();

        // 2nd Stage
        Stage stage2 = new Stage();
        stage2.setTitle("2nd Stage");
        stage2.setWidth(200);
        stage2.setHeight(200);
        stage2.setResizable(false);

        // Load FXML
        FXMLLoader loader2 = new FXMLLoader(
                ButtonPanelController.class.getResource("ButtonPanel.fxml"));
        // Set the ControllerFactory before the load takes place
        loader2.setControllerFactory(controllerFactory);
        Parent root2 = (Parent) loader2.load();

        // Show 2nd Scene
        Scene scene2 = new Scene(root2);
        stage2.setScene(scene2);
        stage2.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

关于JavaFX 将重复 View 同步到同一 Controller (FXML 和 MVC),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35805710/

相关文章:

java - 连接到 IP 地址 真正的文件共享程序

javascript - 从 Google App Engine (Java) 上传文件到 Google 云存储

java - 对组合框使用与 'label' 不同的值

JavaFX - 设置 TitledPane 之间的边距

java - 通过java程序从图像中读取字符值

java - 正则表达式替换字符串第一个索引处除减号字符 "-"之外的所有无效字符

silverlight - UI和View在术语上的区别以及在哪种情况下正确

model-view-controller - MVC 架构中的用户输入来自哪里?

iphone - 将手势识别器/ Action 方法附加到 View 会违反 Model View Controller 吗?

java - IntelliJ Idea JavaFX 调试问题