JavaFX 使用彼此的方法管理不同控制器的最佳方式?

问题描述 投票:0回答:2

我是 JavaFX 新手。我在使用控制器时遇到了麻烦。

我有两个 GUI,称为 A 和 B,每个都有其控制器(ControllerA 和 ControllerB)。

我的程序非常简单:它首先打开 A,然后有一个按钮,按下时打开 B。反之亦然,B 有一个打开 A 的按钮。

ControllerA 有一种方法,称为“openA”,ControllerB 有一种方法,称为“openB”。

因此,A 需要一个 ControllerB 来打开 B,反之亦然。

我看了一个教程,他处理控制器通信的方式如下:


public class ControllerA{

public void onPressingButtonB(ActionEvent e) throws IOException{
        FXMLLoader loaderB = new FXMLLoader(getClass().getResource("class-b.fxml"));
        root = loaderB.load();
        ControllerB controllerB = loaderB.getController();
        controllerB.openB(e);
}

但这对我来说似乎“不是最佳”。每次我在 A 并想去 B 时,我都需要重新确认 ControllerB。所以,我声明ControllerA有一个ControllerB,并使用了以下代码:


public class ControllerA{

private ControllerB controllerb;
    {
        try {
            controllerb = loadControllerB();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    public ControllerB loadControllerB() throws IOException {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("class-b.fxml"));
        root = loader.load();
        return loader.getController();
    }

public void onPressingButtonB(ActionEvent e) throws IOException{
        controllerb.openB(e);
    }

这样,我的动作侦听器可以解析为一行,直接在我的类中实例化控制器,并且它的工作方式就像一个魅力。

事情是......当然我需要用ControllerB进行镜面操作,但这会导致一个主要问题:如果ControllerA在创建时实例化一个ControllerB,而ControllerB在创建时实例化一个ControllerA......这是一个循环。事实上,它会循环并在加载方法上给我错误。

我的问题是:有没有一种方法可以修复我的代码并仅创建一次控制器(这样我的动作侦听器可以只是一行代码),或者我每次必须使用控制器时都必须重新实例化控制器?

非常感谢。

java javafx
2个回答
4
投票

对我来说,所描述的设计都不是特别好的设计。控制者不应该在任何意义上公开自己的观点。两个控制器之间通信的事实上的行业标准方式是通过模型(即使用 MVC 设计)。

在这种情况下,您的模型可以包含一些表示当前视图应该是什么的状态。通常,这只是您的模型无论如何都会包含的自然属性的函数。例如,如果您有一个用户必须登录的应用程序,您的模型可能有一个用户属性。登录屏幕的控制器将验证用户凭据并更改用户属性(如果正确)。其他屏幕可能有一个注销按钮,仅将用户设置为空。如果用户更改为空,模型用户属性上的观察者将显示登录屏幕,如果用户更改为非空,则显示主屏幕。

为了简单的演示目的,这里有一个模型类,它直接有一个代表当前视图的属性:

package org.jamesd.examples.switchviews;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;

public class Model {
    public enum View {A,B}

    private final ObjectProperty<View> currentView = new SimpleObjectProperty<>(View.A);

    public View getCurrentView() {
        return currentView.get();
    }

    public ObjectProperty<View> currentViewProperty() {
        return currentView;
    }

    public void setCurrentView(View currentView) {
        this.currentView.set(currentView);
    }

    
}

现在您的控制器应该做的就是更新(共享)模型的状态:

A.fxml:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx"
      xmlns:fx="http://javafx.com/fxml"
      fx:controller="org.jamesd.examples.switchviews.ControllerA"
      alignment="CENTER"
      spacing="20"
      fx:id="root">
    <Label text="This is view A"/>
    <Button text="Go to view B" onAction="#goToB"/>

</VBox>

和控制器A:

package org.jamesd.examples.switchviews;

import javafx.fxml.FXML;

public class ControllerA {
    private Model model;
    public void setModel(Model model) {
        this.model = model;
    }
    
    @FXML
    private void goToB() {
        model.setCurrentView(Model.View.B);
    }
}

同样,B.fxml:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx"
      xmlns:fx="http://javafx.com/fxml"
      fx:controller="org.jamesd.examples.switchviews.ControllerB"
      alignment="CENTER"
      spacing="20"
      fx:id="root">
    <Label text="This is view B"/>
    <Button text="Go to view A" onAction="#goToA"/>

</VBox>

和控制器B:

package org.jamesd.examples.switchviews;

import javafx.fxml.FXML;

public class ControllerB {
    private Model model;
    public void setModel(Model model) {
        this.model = model;
    }

    @FXML
    private void goToA() {
        model.setCurrentView(Model.View.A);
    }
}

模型更改时更改实际视图的责任属于其他地方。在这个简单的示例中,我们可以在应用程序类中执行此操作,该类可以访问场景:

package org.jamesd.examples.switchviews;

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

import java.io.IOException;

public class HelloApplication extends Application {

    private Parent viewA ;
    private Parent viewB ;

    @Override
    public void start(Stage stage) throws IOException {
        Model model = new Model();

        // load both views:
        FXMLLoader loaderA = new FXMLLoader(getClass().getResource("A.fxml"));
        viewA = loaderA.load();
        ControllerA controllerA = loaderA.getController();
        controllerA.setModel(model);

        FXMLLoader loaderB = new FXMLLoader(getClass().getResource("B.fxml"));
        viewB = loaderB.load();
        ControllerB controllerB = loaderB.getController();
        controllerB.setModel(model);

        // create scene with initial view:
        Scene scene = new Scene(viewFromModel(model.getCurrentView()),320,200);

        // change view when model property changes:
        model.currentViewProperty().addListener((obs, oldView, newView) ->
            scene.setRoot(viewFromModel(newView))
        );

        stage.setScene(scene);
        stage.show();
    }

    private Parent viewFromModel(Model.View view) {
        return switch(view) {
            case A -> viewA ;
            case B -> viewB ;
        };
    }

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

这里显示了更广泛的示例


0
投票
加载新的 fxml,因此调用

root = loader.load();

 实例化指定路径的新控制器。所以不,你不能坚持使用同一个实例,因为你将无法访问声明的控制器上的
@FXML
内容,或者你可能正在调用你正在可视化的页面的旧实例。

© www.soinside.com 2019 - 2024. All rights reserved.