前言
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;
public class StageController {
//建立一個專門存儲Stage的Map,全部用于存放Stage對象
private HashMap<String, Stage> stages = new HashMap<String, Stage>();
public void addStage(String name, Stage stage) {
stages.put(name, stage);
}
public Stage getStage(String name) {
return stages.get(name);
}
public void setPrimaryStage(String primaryStageName, Stage primaryStage) {
this.addStage(primaryStageName, primaryStage);
}
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;
}
}
public boolean setStage(String name) {
this.getStage(name).show();
return true;
}
public boolean setStage(String show, String close) {
getStage(close).close();
setStage(show);
return true;
}
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容器的父類。(如下圖)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2QvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2LcZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39jN1cjNzYjMxIzMxcDM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
注:fxml資源檔案一定要綁定其對應的View控制器類,可以在SceneBuilder中的左下角綁定界面的Controller進行指定,也可以在fxml的源碼中修改fx:controller...進行綁定。
此文老貓原創,轉載請加本文連接配接:http://blog.csdn.net/nthack5730/article/details/51901593 更多有關老貓的文章:http://blog.csdn.net/nthack5730
第二步:将StageController控制器注入(注冊)到每個界面的控制器中
上面的loadStage()中大家不難發現有:
這段代碼就是将StageController對象注入到每個View控制器中,那麼要達到這個效果我們每個View控制器就必須有StageController屬性(域、字段)。同時,為了能夠将控制器對象注入,必須有一個setStageController(...)方法。 在這裡建立一個接口,所有的View控制器都去實作這個接口即可:
ControlledStage.java
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;
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;
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