首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >通过FXML (性能问题)管理JavaFX中的场景切换

通过FXML (性能问题)管理JavaFX中的场景切换
EN

Stack Overflow用户
提问于 2018-10-22 13:16:16
回答 1查看 1.4K关注 0票数 0

我正在使用JavaFX和各种由FXML加载的场景。所以我想写一个经理来处理场景切换。

到目前为止,一切都正常,但我不确定这是否是一个好的实现。

代码语言:javascript
复制
import java.io.IOException;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

public class SceneManager {
    private static final String[] fxmlFiles = {"../gui/MainWindow.fxml", "../gui/NewGameWindow.fxml"};
    private static SceneManager instance = null;
    private static Stage rootStage = null;

    private FXMLLoader[] loadedFxml;
    private Pane[] loadedPanes;
    private Scene[] scenes;

    public enum States {
        MAIN_MENU, NEW_GAME;
    }

    private SceneManager() {
        try {
            this.loadedFxml = new FXMLLoader[States.values().length];
            this.loadedPanes = new Pane[States.values().length];
            this.scenes = new Scene[States.values().length];

            for(int i = 0; i < fxmlFiles.length; i++) {
                loadedFxml[i] = new FXMLLoader(getClass().getResource(fxmlFiles[i]));
                loadedPanes[i] = loadedFxml[i].load();
                scenes[i] = new Scene(loadedPanes[i]);
            }

            rootStage.setScene(scenes[0]);
            rootStage.setResizable(false);
            rootStage.show();
        } catch(IOException e) {
            e.printStackTrace();
        }
    }

    public static SceneManager getInstance() {
        if(instance == null) {
            instance = new SceneManager();
        }
        return instance;
    }

    public static void setUp(Stage stage) {
        SceneManager.rootStage = stage;
    }

    public void switchScene(States state) {
        rootStage.setScene(scenes[state.ordinal()]);
    }
}

所以我计划做的是,通过加载器加载FXML,将它分配到一个窗格中,创建所有场景。

然后,我设置一个场景作为它的开始场景,并通过控制器中的getInstance().switchScene()方法完成其余的操作。

它工作得很好,但我不确定这是否是一个好方法。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-10-22 15:03:24

由于以下几个原因,该实现非常糟糕:

没有正确地实现单例模式。

单例模式用于通过static方法访问包含相关数据/功能的实例,但该实例应该包含所有相关数据作为实例字段。

不过,rootStagesetUpstatic

将数据存储在再也不需要的字段中。

您从未在循环之外从loadedFxmlloadedPanes中读取。相反,您可以在循环体中创建局部变量。

所有的东西都一次装好。

对于几个小场景,这可能没有多大区别,但随着添加的场景越来越多,这将增加启动时间。考虑懒洋洋地加载场景。

场景的数据保存在不同的数据结构中。

问题不大,但它会使类更难维护。enum存储用于创建/访问场景的一部分数据,fxmlFiles包含另一半。每次添加/删除场景时,都需要更新代码的两个部分。在这种情况下,最好将url数据存储在枚举本身中:

代码语言:javascript
复制
public enum States {
    MAIN_MENU("../gui/MainWindow.fxml"), NEW_GAME("../gui/NewGameWindow.fxml");
    private final url;

    States(String url) {
        this.url = url;
    }
}
代码语言:javascript
复制
for(States state : States.values()) {
    FXMLLoader loader = new FXMLLoader(getClass().getResource(state.url));
    ...
}

请注意,如果您将程序打包为jar,您将在这里的urls中使用..,这将停止工作。

但是,首先使用enum是一个值得怀疑的决定。这样,在不重新编译的情况下,您就无法添加/删除场景。

使用static似乎一点也不需要

如果可能的话,最好完全避免使用static。(见Why are static variables considered evil?)。

如果我的假设是只使用由其加载的场景中的SceneManager类(以及显示初始场景)是正确的,那么就不难将SceneManager实例传递给场景的控制器,以避免在这些类中使用SceneManager.getInstance (参见Passing Parameters JavaFX FXML):

控制器的超类

代码语言:javascript
复制
public class BaseController {
    protected SceneManager sceneManager;
    void setSceneManager(SceneManager sceneManager) { // if SceneManager and BaseController are in different packages, change visibility
        this.sceneManager = sceneManager;
    }
}
代码语言:javascript
复制
FXMLLoader loader = ...
Pane pane = loader.load();
BaseController controller = loader.getController();
controller.setSceneManager(this);

为了简单起见,可以使用url作为场景的标识符,从而改进实现:

代码语言:javascript
复制
public class SceneManager {
    
    private final Stage rootStage;

    public SceneManager(Stage rootStage) {
        if (rootStage == null) {
            throw new IllegalArgumentException();
        }
        this.rootStage = rootStage;
    }

    private final Map<String, Scene> scenes = new HashMap<>();

    public void switchScene(String url) {
        Scene scene = scenes.computeIfAbsent(url, u -> {
            FXMLLoader loader = new FXMLLoader(getClass().getResource(u));
            try {
                Pane p = loader.load();
                BaseController controller = loader.getController();
                controller.setSceneManager(this);
                return new Scene(p);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        });
        rootStage.setScene(scene);
    }
}

这让你可以

  • 为不同的阶段创建不同的经理
  • 当首先需要时加载场景
  • 动态添加更多场景
  • 防止调用switchScene但阶段为null的状态
  • 简化了对控制器类中SceneManagersceneManager.switchScene的访问
  • 在程序完成之前,SceneManager可能可用于垃圾收集,因为没有对它的静态引用。
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52930361

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档