前言
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 stages = new HashMap();
/**
* 将加载好的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(...)。
期间有人问关于执行流程,我就简单描述下:(注意:是简单描述!望高手再指点!)
我就顺着执行的路径大概描述下吧(可能有出入,但思维一样的):
1.在MainApp中【stageController = new StageController();】创建了一个StageController,这是借助HashMap包装的容器工厂类(上面有写)。
2.然后执行【stageController.loadStage(loginViewID, loginViewRes, StageStyle.UNDECORATED);】调用【StageController类的对象】加载了【LoginViewController】。
3.因为【LoginView】实现了【ControlledStage接口】,【StageController类的对象】在执行【loadStage(...)】方法的时候,执行了【controlledStage.setStageController(this);】,意思就是把【自己(StageController类的对象)】注入到【LoginViewController类的对象】的【myController属性】中。
4.而【LoginView】的对象则有JavaFX内部机制进行实例化,通过【FXMLLoader】中的【getController()】方法获得【LoginViewController对象】。
5.于是就能够通过【StageController.setStage(...)】(要实例对象)来控制显示和隐藏窗口。
好了,关于控制多个Stage窗口管理的设计的文章已经写完了。
谢谢大家的支持,有更好的想法或者文中不足的地方老猫非常欢迎大家提出来讨论。
此文老猫原创,转载请加本文连接:http://blog.csdn.net/nthack5730/article/details/51901593
更多有关老猫的文章:http://blog.csdn.net/nthack5730