天天看點

JavaFX - 實作管理多個Stage視窗切換及源碼解析 前言 好了,開始: 第一步:建立一個StageController控制器 第二步:将StageController控制器注入(注冊)到每個界面的控制器中 第三步:在MainApp.java中對StageController執行個體化并加載所有的視窗 總結:

前言

     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容器的父類。(如下圖)

JavaFX - 實作管理多個Stage視窗切換及源碼解析 前言 好了,開始: 第一步:建立一個StageController控制器 第二步:将StageController控制器注入(注冊)到每個界面的控制器中 第三步:在MainApp.java中對StageController執行個體化并加載所有的視窗 總結:
JavaFX - 實作管理多個Stage視窗切換及源碼解析 前言 好了,開始: 第一步:建立一個StageController控制器 第二步:将StageController控制器注入(注冊)到每個界面的控制器中 第三步:在MainApp.java中對StageController執行個體化并加載所有的視窗 總結:

     注: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

繼續閱讀