JavaFX - 实现管理多个Stage窗口及源码解析

前言

JavaFX相比AWT就是和Android一样通过xml文件来定义界面的设计,并且可以通过fxml资源文件结合Java代码来控制界面的变化。摒弃之前写AWT那种什么都在Java代码中定义(窗口大小,颜色,控件等等....)的设计。通过fxml+Java代码控制界面达到界面程序更加人性化(猿性化)。

但是JavaFX对于窗口的管理却不是那么地人性化,我目前没有发现官方人性化的解决方案。

有人就说将所有FXML界面的Controller写到同一个类里面不就好了吗?

答曰:这样和AWT有多大区别了?我们需要的是每个fxml对应上一个Controller类,这样才能进行更好的、更方便的设计。

还有人说将所有窗口的大小设计成为统一大小不就好了么,这样就可以通过管理Scene的方式进行操作所有的界面了?

Oracle的一位大神写了一个关于多个窗口管理的解决方案(本文也是根据这位大神的博客的教程进行修改),但是这位大神是基于所有的窗口都是同等大小的情况下进行操作Scene的内容切换达到多个窗口同时管理的,一旦需要的界面窗口大小不一的时候就有问题了,大家可以去参考下她的内容。

https://blogs.oracle.com/acaicedo/entry/managing_multiple_screens_in_javafx1

https://blogs.oracle.com/acaicedo/entry/managing_multiple_screens_in_javafx

如果看了她的文章的人不难发现,她的Stage、Scene对象由始至终都是一个,改变的是Scene里面的容器内容。也就是说:Stage的长宽从加载到程序结束是不会改变的,如果强行将Stage注入到每个View(FXML)的Controller中,在改变Scene里面的内容的时候改变Stage的大小,那么倒不如直接一开始直接将Stage交给控制器进行管理,这也是我今天在博客这里要写的东西。

此文老猫原创,转载请加本文连接:http://blog.csdn.net/nthack5730/article/details/51901593

更多有关老猫的文章:http://blog.csdn.net/nthack5730



在开始之前再一次谴责那些通过爬虫爬出来的垃圾程序网站!!!

本文只在本人CSDN博客发布,如果你看到本文的时候地址非CSDN或者没有注明来源的转载,或者网页内容广告很多,那么可能就是爬虫网站!

爬虫网站损害的不仅仅是我们的心血,因为爬虫或多或少都会出现内容的纰漏,对读者造成的危害更大,误人子弟。


好了,开始:

相对来说,Oracle大神创建的是Scene的管理器,而且管理的是Scene里面的内容,按照她的说法呢,就是所有的窗口都是同一个大小的,因为她的Stage从头到尾都是一个,但是JavaFX的Stage一旦显示了就不能进行大小的修改,强行修改会抛出异常。

但是很多桌面程序是大小不一的,例如:登录框、菜单主界面、提示框等等...因此我将她的内容进行修改,将管理Scene的内容改成管理Stage窗口。这样就可以通过管理不同的Stage达到我们需要的大小不一的窗口的效果。


第一步:创建一个StageController控制器

StageController控制器主要是加载fxml资源文件和对应的View控制器、生成Stage对象以及对Stage对象进行管理,因此该StageController控制器对象也需要被注入到每个fxml的View控制器中。

     下面给出StageController.java的源码:

StageController.java

package com.marer.view;

import javafx.fxml.FXMLLoader;

import javafx.scene.Scene;

import javafx.scene.layout.Pane;

import javafx.stage.Stage;

import javafx.stage.StageStyle;

import java.util.HashMap;

/**

* Created by CatScan on 2016/6/23.

*/

public class StageController {

//建立一个专门存储Stage的Map,全部用于存放Stage对象

private HashMap<String, Stage> stages = new HashMap<String, Stage>();

/**

* 将加载好的Stage放到Map中进行管理

*

* @param name  设定Stage的名称

* @param stage Stage的对象

*/

public void addStage(String name, Stage stage) {

stages.put(name, stage);

}

/**

* 通过Stage名称获取Stage对象

*

* @param name Stage的名称

* @return 对应的Stage对象

*/

public Stage getStage(String name) {

return stages.get(name);

}

/**

* 将主舞台的对象保存起来,这里只是为了以后可能需要用,目前还不知道用不用得上

*

* @param primaryStageName 设置主舞台的名称

* @param primaryStage     主舞台对象,在Start()方法中由JavaFx的API建立

*/

public void setPrimaryStage(String primaryStageName, Stage primaryStage) {

this.addStage(primaryStageName, primaryStage);

}

/**

* 加载窗口地址,需要fxml资源文件属于独立的窗口并用Pane容器或其子类继承

*

* @param name      注册好的fxml窗口的文件

* @param resources fxml资源地址

* @param styles    可变参数,init使用的初始化样式资源设置

* @return 是否加载成功

*/

public boolean loadStage(String name, String resources, StageStyle... styles) {

try {

//加载FXML资源文件

FXMLLoader loader = new FXMLLoader(getClass().getResource(resources));

Pane tempPane = (Pane) loader.load();

//通过Loader获取FXML对应的ViewCtr,并将本StageController注入到ViewCtr中

ControlledStage controlledStage = (ControlledStage) loader.getController();

controlledStage.setStageController(this);

//构造对应的Stage

Scene tempScene = new Scene(tempPane);

Stage tempStage = new Stage();

tempStage.setScene(tempScene);

//配置initStyle

for (StageStyle style : styles) {

tempStage.initStyle(style);

}

//将设置好的Stage放到HashMap中

this.addStage(name, tempStage);

return true;

} catch (Exception e) {

e.printStackTrace();

return false;

}

}

/**

* 显示Stage但不隐藏任何Stage

*

* @param name 需要显示的窗口的名称

* @return 是否显示成功

*/

public boolean setStage(String name) {

this.getStage(name).show();

return true;

}

/**

* 显示Stage并隐藏对应的窗口

*

* @param show  需要显示的窗口

* @param close 需要删除的窗口

* @return

*/

public boolean setStage(String show, String close) {

getStage(close).close();

setStage(show);

return true;

}

/**

* 在Map中删除Stage加载对象

*

* @param name 需要删除的fxml窗口文件名

* @return 是否删除成功

*/

public boolean unloadStage(String name) {

if (stages.remove(name) == null) {

System.out.println("窗口不存在,请检查名称");

return false;

} else {

System.out.println("窗口移除成功");

return true;

}

}

}

控制器在对fxml资源文件加载的时候使用的是Pane这个容器作为最基底的容器。原因是Pane是其他Pane容器的父类。(如下图)

 注:fxml资源文件一定要绑定其对应的View控制器类,可以在SceneBuilder中的左下角绑定界面的Controller进行指定,也可以在fxml的源码中修改fx:controller...进行绑定。

此文老猫原创,转载请加本文连接:http://blog.csdn.net/nthack5730/article/details/51901593

更多有关老猫的文章:http://blog.csdn.net/nthack5730


第二步:将StageController控制器注入(注册)到每个界面的控制器中

上面的loadStage()中大家不难发现有:

ControlledStage controlledStage = (ControlledStage) loader.getController();            controlledStage.setStageController(this);

这段代码就是将StageController对象注入到每个View控制器中,那么要达到这个效果我们每个View控制器就必须有StageController属性(域、字段)。同时,为了能够将控制器对象注入,必须有一个setStageController(...)方法。

 在这里创建一个接口,所有的View控制器都去实现这个接口即可:

ControlledStage.java

/**

* Created by CatScan on 2016/6/23.

*/

public interface ControlledStage {

public void setStageController(StageController stageController);

}

这样,每个View控制器在实现这个接口后都必须要重写这个方法,将StageController对象保存到自己属性中。

其实还可以将上面的方法写成抽象类的形式,每个View控制器继承这个类,也是可以的,具体喜欢怎样就看各位的选择啦。

下面给出一个栗子(例子):登陆窗口的View控制器。

LoginViewController.java

package com.marer.view;

import javafx.fxml.Initializable;

import java.net.URL;

import java.util.ResourceBundle;

/**

* Created by CatScan on 2016/6/21.

*/

public class LoginViewController implements ControlledStage, Initializable {

StageController myController;

public void setStageController(StageController stageController) {

this.myController = stageController;

}

public void initialize(URL location, ResourceBundle resources) {

}

public void goToMain(){

myController.setStage(MainApp.mainViewID);

}

}

上面的代码就实现了将StageController对象注入到LoginViewController里面,并且在goToMain()里面对StageController对象进行了调用。

此文老猫原创,转载请加本文连接:http://blog.csdn.net/nthack5730/article/details/51901593

更多有关老猫的文章:http://blog.csdn.net/nthack5730


第三步:在MainApp.java中对StageController实例化并加载所有的窗口

现在假设所有的fxml资源文件都将加载为一个独立的窗口,当然,如果你需要一个窗口里面有多个fxml资源文件也是可以的,具体的看自己的需求。现在这里每个fxml资源文件就是一个窗口作为例子。

首先,我们需要对所有的界面进行静态“留名”(将资源文件和ID写成静态),并在Start的方法中进行“加载”:

MainApp.java

package com.marer.view;

/**

* Created by CatScan on 2016/6/19.

*/

import javafx.application.Application;

import javafx.stage.Stage;

import javafx.stage.StageStyle;

public class MainApp extends Application {

public static String mainViewID = "MainView";

public static String mainViewRes = "MainView.fxml";

public static String loginViewID = "LoginView";

public static String loginViewRes = "LoginView.fxml";

private StageController stageController;

@Override

public void start(Stage primaryStage) {

//新建一个StageController控制器

stageController = new StageController();

//将主舞台交给控制器处理

stageController.setPrimaryStage("primaryStage", primaryStage);

//加载两个舞台,每个界面一个舞台

stageController.loadStage(loginViewID, loginViewRes, StageStyle.UNDECORATED);

stageController.loadStage(mainViewID, mainViewRes);

//显示MainView舞台

stageController.setStage(mainViewID);

}

public static void main(String[] args) {

launch(args);

}

}

所有的静态属性,分别是每个界面的ID和资源文件的相对路径:

    public static String mainViewID = "MainView";

public static String mainViewRes = "MainView.fxml";

public static String loginViewID = "LoginView";

public static String loginViewRes = "LoginView.fxml";

因为是静态的属性,也可以自行建立一个类或者Properties文件进行存储并读取,自然是哪个方便用哪个方式。

静态的ID属性是为了在后面调用过程中容易找到对应的ID,不会出现打错字符串出现空指针异常。例如:第二步中的goToMain()方法....

然后需要的是在start()方法中对StageController控制器进行实例化并用StageController对象加载fxml资源文件。

        //加载两个舞台,每个界面一个舞台

stageController.loadStage(loginViewID, loginViewRes, StageStyle.UNDECORATED);

stageController.loadStage(mainViewID, mainViewRes);

上面两行代码实现的就是加载两个fxml资源文件并注册为Stage窗口,loadStage(...)就是加载fxml资源文件, 并且在调用此方法的过程中已经将StageController控制器的对象注入到每个被加载的fxml对应的View控制器中,这就是每个fxml资源文件要绑定View控制器类的原因,具体可以回顾第一步。

     好了,回到这里,其中第一行在加载的时候设置了窗口无边框,该方法我使用了Java的可变参数,允许我们对窗口进行多个样式的设置,具体大家可以看回第一步StageController.java的代码。

值得我们注意的是:JavaFX的Stage在调用show()之后是不允许对舞台的外观再进行修改的,包括长、宽,否则会抛出异常,这也是催生我写这篇文章的原因之一。

最后,我们就设置需要显示的窗口即可:

        //显示MainView舞台

stageController.setStage(mainViewID);

不难发现,只要调用这个StageController对象中的setStage(...),即可显示对应的ID的Stage窗口。

在StageController控制器中的setStage(String show, String hide)方法是可以调用一个窗口后隐藏一个窗口,源码中我添加了注释。

更多方法如:showAndWait(...)这些我还没用上,会在以后用上的时候进行修改和优化这个StageController控制器。

此文老猫原创,转载请加本文连接:http://blog.csdn.net/nthack5730/article/details/51901593

更多有关老猫的文章:http://blog.csdn.net/nthack5730



总结:

首先对StageController控制器中必用的方法简单归纳下,具体解析都在源码注释中:

* loadStage(String ,String):加载fxml资源文件并对对应的fxml控制器注入StageController控制器对象。

* setStage(String):显示对应ID的窗口,该窗口已经而且必须是被加载过的,否则会抛出空指针异常。

* setStage(String ,String):显示第一个ID参数的窗口对象,隐藏第二个ID参数的窗口对象。

* unloadStage(String):根据ID卸载已经加载的窗口对象。

 其次需要注意的是:每个fxml资源文件的路径名最好用一个类静态包装起来,并赋予对应的ID值,本文就包装在MainApp.java中。方便每个View控制器调用setStage(...)。



好了,关于控制多个Stage窗口管理的设计的文章已经写完了。

谢谢大家的支持,有更好的想法或者文中不足的地方老猫非常欢迎大家提出来讨论。

此文老猫原创,转载请加本文连接:http://blog.csdn.net/nthack5730/article/details/51901593

更多有关老猫的文章:http://blog.csdn.net/nthack5730

时间: 2024-10-23 13:31:22

JavaFX - 实现管理多个Stage窗口及源码解析的相关文章

redis源码解析之内存管理

zmalloc.h的内容如下: 1 void *zmalloc(size_t size); 2 void *zcalloc(size_t size); 3 void *zrealloc(void *ptr, size_t size); 4 void zfree(void *ptr); 5 char *zstrdup(const char *s); 6 size_t zmalloc_used_memory(void); 7 void zmalloc_enable_thread_safeness(v

linux CentOS7 中安装包管理:rpm 、yum及源码包安装使用

一. 安装软件包的三种方法 yum ---python rpm工具 yum工具 源码包 二. rpm包介绍 设置光驱并挂载: [[email protected] ~]# mount /dev/cdrom /mntmount: /dev/sr0 写保护,将以只读方式挂载 [[email protected] ~]# ls /mntEULA    isolinux  repodata                      TRANS.TBL GPL     LiveOS    RPM-GPG-

Samba 源码解析之内存管理

由于工作需要想研究下Samba的源码,下载后发现目录结构还是很清晰的.一般大家可能会对source3和source4文件夹比较疑惑.这两个文件夹针对的是Samba主版本号,所以你可以暂时先看一个.这里我选择Source3. 阅读源码最好要动手编译并安装,但这里我偷个懒直接在ubuntu上安装跳过了编译步骤.首先从client开始看起.SMBclient的所有命令的对应code都在source3/client/client.c中,我们由浅入深,挑一个比较简单的命令来看下它的执行流程,将简单的命令分

android 类似360悬浮窗口实现源码

当我们在手机上安装360安全卫士时,手机屏幕上时刻都会出现一个小浮动窗口,点击该浮动窗口可跳转到安全卫士的操作界面,而且该浮动窗口不受其他activity的覆盖影响仍然可见(多米音乐也有相关的和主界面交互的悬浮小窗口).它能悬浮在手机桌面,且不受Activity界面的影响,说明该悬浮窗口是不隶属于Activity界面的,也就是说,他是隶属于启动它的应用程序所在进程.如360App所在的应用进程,当杀掉它所在的应用进程时,它才会消失.悬浮窗口的实现涉及到WindowManager(基于4.0源码分

Java门店管理系统 客户资料档案管理 库存管理 进销存 SSM项目源码

系统介绍: 1.系统采用主流的 SSM 框架 jsp JSTL bootstrap html5 (PC浏览器使用) 2.springmvc +spring4.3.7+ mybaits3.3  SSM 普通java web(非maven, 附赠pom.xml文件)  数据库:mysql 3.开发工具:myeclipse  eclipse idea 均可, 没有限制. 我这边myeclipse 2014 导出来的项目源码 ---------------------------------------

android Activity实现底部滑动弹出窗口及源码下载地址

在做微信.微博.qq等分享时,一般是点击分享按钮后会从底部弹出滑动窗口,然后选择要分享的社交平台进行分享.今日头条.腾讯新闻等内容App的评论也是从底部滑动弹出输入窗口,进行评论输入的.本篇文章就讲讲怎么通过Activity实现底部弹出滑动窗口的.实现效果是通过Animation功能实现的,效果如下: 源码下载地址 主要代码如下: 一.滑动窗口PopupShareActivity类 继承自Activity并实现了OnClickListener,方便处理Click事件.代码如下: public c

Java 门店管理系统 客户信息 档案管理 库存管理 进销存 SSM 项目源码

统介绍: 1.系统采用主流的 SSM 框架 jsp JSTL bootstrap html5 (PC浏览器使用) 2.springmvc +spring4.3.7+ mybaits3.3  SSM 普通java web(非maven, 附赠pom.xml文件)  数据库:mysql 3.开发工具:myeclipse  eclipse idea 均可, 没有限制. 我这边myeclipse 2014 导出来的项目源码 ----------------------------------------

【spring-boot 源码解析】spring-boot 依赖管理

关键词:spring-boot 依赖管理.spring-boot-dependencies.spring-boot-parent 问题 maven 工程,依赖管理是非常基本又非常重要的功能,现在的工程越来越庞大,依赖越来越多,各种二方包.三方包太多太多,依赖冲突处理起来真是让人头疼,经常需要涉及到多个地方需要调整. 微信公众号:逸飞兮(专注于java知识领域的源码分析,从源码中理解框架/工具原理.验证CS专业知识) 解决方案 使用统一的依赖管理模块来管理工程中的所有依赖. spring-boot

Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?

不知道一些同学有没有这种疑问,为什么Mybtis中要配置dataSource,Spring的事务中也要配置dataSource?那么Mybatis和Spring事务中用的Connection是同一个吗?我们常用配置如下 <!--会话工厂 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name=&qu