質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
JavaFX

JavaFXとは、Java仮想マシン上で動作するリッチインターネットアプリケーション (RIA) のGUIライブラリです。Swingとは異なり、FXMLと呼ばれる XMLとCSSを併用してデザインを記述します。

Q&A

解決済

1回答

3417閲覧

JavaFXの画面遷移について

yogurt

総合スコア12

JavaFX

JavaFXとは、Java仮想マシン上で動作するリッチインターネットアプリケーション (RIA) のGUIライブラリです。Swingとは異なり、FXMLと呼ばれる XMLとCSSを併用してデザインを記述します。

0グッド

1クリップ

投稿2017/07/24 05:06

###前提・実現したいこと
JavaFXでサーバー側とクライアント側に分かれたチャットソフトを作っています
クライアント側からユーザー情報を入力(サインイン画面)→サーバー側が受信して応答→クライアント側の画面遷移(チャット画面)
といったようなことをやりたいのですが、実行時エラーが出てしまい、どのように対処したらいいのかわかりません…

自力でもJavaFXの画面遷移についての色々なWebページを見て解決を試みたのですがわかりませんでした…

どの点が悪いのか教えていただけないでしょうか…

画面遷移を実装中に以下のエラーメッセージが発生しました。

###発生している問題・エラーメッセージ

文字数制限にかかってしまったのでリンクを貼ります!
http://plsk.net/JavafxErrorMessage

#Main.java

package sample; import javafx.application.Application; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.stage.Stage; import java.io.*; import java.net.Socket; public class Main extends Application implements Runnable { @FXML TextField HostField; @FXML TextField PortField; @FXML TextField IdField; @FXML Label StatusField; @FXML PasswordField passwordField; Stage SignStage; @Override public void start(Stage Stage) throws Exception { this.SignStage = new Stage(); StageChange("UserSign.fxml", "Sign in"); this.SignStage.setResizable(false); } @Override public void stop() { if(socket == null) return; try { close(); } catch (IOException e) { e.printStackTrace(); } } //サインインボタンのイベント定義 @FXML private void Sign_in(ActionEvent a) { //入力フォームに対する空白の禁止 if(HostField.getText().equals("") || PortField.getText().equals("") || IdField.getText().equals("") || passwordField.getText().equals("")) { StatusField.setText("入力項目に空欄が存在します"); return; } this.HOST = HostField.getText(); this.PORT = Integer.valueOf(PortField.getText()); if(connectServer()) { System.out.println("サーバー接続完了"); StageChange("test.fxml", "Test"); //((Node)a.getSource()).getScene().getWindow().hide(); } passwordField.setText(""); IdField.setText(""); } public static void main(String[] args) { launch(args); } //接続先サーバーのホスト名 private static String HOST; //接続先ポート番号 private static int PORT; //このアプリケーションのクライアントソケット private Socket socket; //メッセージ受信監視用スレッド private Thread thread; private Parent root; public Main() { } public void ThreadCreate() { //メッセージ受信用に System.out.println("Main開始"); thread = new Thread(this); System.out.println("スレッド作成完了"); thread.start(); System.out.println("スレッドスタート"); } //サーバーに接続する public boolean connectServer() { try { socket = new Socket(HOST, PORT); sendMessage("sign " + passwordField.getText() + " " + IdField.getText()); ThreadCreate(); return true; } catch(Exception err) { socket = null; StatusField.setText("サーバーに接続不能な状況か、\n" + "ホスト名かポート番号が間違っています"); } return false; } //サーバーから切断する public void close() throws IOException { sendMessage("close"); socket.close(); } //メッセージをサーバーに送信する public void sendMessage(String msg) { try { OutputStream output = socket.getOutputStream(); PrintWriter writer = new PrintWriter(output); System.out.println(msg); writer.println(msg); writer.flush(); } catch(Exception err) { // msgTextArea.append("ERROR>" + err + "\n"); } } //メッセージ監視用のスレッド public void run() { try { InputStream input = socket.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(input)); while(!socket.isClosed()) { System.out.println("while開始"); String line = reader.readLine(); System.out.println("受信完了"); String[] msg = line.split(" ", 2); System.out.println("分割完了"); String msgName = msg[0]; String msgValue = (msg.length < 2 ? "" : msg[1]); reachedMessage(msgName, msgValue); System.out.println("メッセージ処理開始"); } } catch(Exception err) { } } //サーバーから送られてきたメッセージの処理 public void reachedMessage(String name, String value) { //チャットルームのリストに変更が加えられた if (name.equals("rooms")) { if (value.equals("")) { } else { String[] rooms = value.split(" "); } } //ユーザーが入退室した else if (name.equals("users")) { if (value.equals("")) { } else { String[] users = value.split(" "); } } //メッセージが送られてきた else if (name.equals("msg")) { } //処理に成功した else if (name.equals("successful")) { if (value.equals("setName")) { } else if(value.equals("sign")) { System.out.println("ログイン要求が承認されました"); } } else if (name.equals("failed")) { if (value.equals("sign")) { try { close(); } catch (IOException err) { err.printStackTrace(); } } } //エラーが発生した else if (name.equals("error")) { } } public void StageChange(String fxml, String title) { try { root = FXMLLoader.load(getClass().getResource(fxml)); System.out.println("リソース遷移"); this.SignStage.setScene(new Scene(root)); System.out.println("画面遷移"); this.SignStage.setTitle(title); System.out.println("タイトル遷移"); this.SignStage.show(); } catch (IOException e) { e.printStackTrace(); } } }

#UserSign.fxml

<?xml version="1.0" encoding="UTF-8"?> <?import javafx.geometry.Insets?> <?import javafx.scene.control.Button?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.PasswordField?> <?import javafx.scene.control.TextField?> <?import javafx.scene.layout.ColumnConstraints?> <?import javafx.scene.layout.GridPane?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.RowConstraints?> <?import javafx.scene.text.Font?> <?import javafx.scene.text.Text?> <GridPane alignment="center" hgap="10.0" maxHeight="-Infinity" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefHeight="279.0" prefWidth="280.0" vgap="15.0" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Main"> <padding><Insets bottom="5.0" left="25.0" right="25.0" top="5.0" /></padding> <Text text="Welcome!" GridPane.columnSpan="2" /> <Label fx:id="StatusField" prefHeight="38.0" prefWidth="145.0" textFill="#e10000" GridPane.columnIndex="1"> <font> <Font size="9.0" /> </font></Label> <Label prefHeight="17.0" prefWidth="66.0" text="HostName :" GridPane.rowIndex="1" /> <TextField fx:id="HostField" prefHeight="25.0" prefWidth="149.0" GridPane.columnIndex="1" GridPane.rowIndex="1" /> <Label prefHeight="17.0" prefWidth="75.0" text="PortNumber :" GridPane.rowIndex="2" /> <TextField fx:id="PortField" prefHeight="25.0" prefWidth="149.0" GridPane.columnIndex="1" GridPane.rowIndex="2" /> <Label prefHeight="9.0" prefWidth="45.0" text="User ID :" GridPane.rowIndex="4" /> <TextField fx:id="IdField" prefHeight="25.0" prefWidth="149.0" GridPane.columnIndex="1" GridPane.rowIndex="4" /> <Label text="Password:" GridPane.rowIndex="5" /> <PasswordField fx:id="passwordField" GridPane.columnIndex="1" GridPane.rowIndex="5" /> <HBox alignment="bottom_right" spacing="10" GridPane.columnIndex="1" GridPane.rowIndex="6"> <Button fx:id="Sign_Start" onAction="#Sign_in" text="Sign In" /> </HBox> <rowConstraints> <RowConstraints maxHeight="34.0" minHeight="13.0" prefHeight="26.0" /> <RowConstraints maxHeight="0.0" minHeight="0.0" prefHeight="0.0" /> </rowConstraints> <columnConstraints> </columnConstraints> </GridPane>

#test.fxml

<?xml version="1.0" encoding="UTF-8"?> <?import java.lang.*?> <?import java.util.*?> <?import javafx.scene.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <AnchorPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="sample.Main" prefHeight="400.0" prefWidth="600.0"> </AnchorPane>

###補足情報(言語/FW/ツール等のバージョンなど)
Java SE8・IntelliJ IDEA Community Edition 2017.1.5 x64

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

###直接的な例外の内容と発生個所

スタックトレースからMain.javaの188行目でNPE(NullPointerException)が発生していることはお判りでしょうか?閲覧者はスタックトレースを見るとMain.javaの188行目でNPEが発生していることはすぐに分かるのですが、コード上のどの行が188行目かは分からない点を認識しておいてください。推察は可能ですが確認するためにはコードを実行して間違いなさそうかを確認せねばなりませんので、多くの閲覧者は例外が発生している行が書いてないと回答が面倒になってしまうと思います。ゆえに質問文には188行目がどの行かを最初から明示しておくべきと思います。

さて、おそらく188行目は次の行と思います。

java

1public void StageChange(String fxml, String title) { 2 ... 3 this.SignStage.setScene(new Scene(root)); //おそらくこの行が188行目 4 ... 5}

###NPEが発生する原因

NPEが発生している直接的な原因はSignStageがnullであるためです。おそらく質問者さんはFXMLLoader#loadはFXML上に記述されたコントローラークラスのインスタンスを「自動的に生成する」という点を把握しておられないように思います。ひょっとするとクラスを定義しているが、インスタンスがいつ生成されるかを意識しておられないのかも知れません。実際には次のようにNPE発生時までにMainのインスタンスが3つ生成されます。

(1) 最初のインスタンス
JavaFXアプリケーション開始時にApplication#launchにてアプリケーションクラスのインスタンスとして生成されます。

(2) 2つ目のインスタンス
StageChangeの最初の呼び出しでUserSign.fxmlをロードする際にコントローラークラスとしてFXMLLoader#loadがインスタンスを生成します。StageChangeメソッドの最初の呼び出しではthisは(1)で生成されたインスタンスなのでSignStageはstartメソッドで設定された値が用いられ最初の画面は出ます。

(3) 3つ目のインスタンス
UserSign.fxmlの画面でSign_Startボタンのハンドラー内からのStageChangeの呼び出し時に(2)と同様に生成されます。

さて(3)のStageChangeの2回目の呼び出しの際にはthisは(2)で生成されたUserSign.fxmlのコントローラーとしてのインスタンスでありSignStageは何も設定されてないのでnullになっています。そのためNPEが発生します。

###対処

質問者さんはアプリケーションインスタンスと異なる2つの画面のコントローラーを全て単一のクラス・インスタンスとして扱おうとしているように見えます。個人的な意見としては、そのような実装は平易な設計ではないと思います。(なぜ平易でないかは回答が長くなりすぎるので省略。)よってアプリケーションクラス、個々の画面用のコントローラークラスはそれぞれ独立なクラスとして定義することをお勧めします。個別のクラスとして定義するとインスタンスも当然別々になりますのでなんらかの方法でコントローラーインスタンス間で情報のやりとりをする必要があります。

様々な方法でそれができると思いますが「こうするのが一番よい」というのが回答しにくいので質問者さんのコードの雰囲気から「アプリケーションクラスに画面遷移用の共通のメソッドを用意する」「Stageは常に単一のものを使う」という前提で考えてみました。なお、以下の点にご注意ください。

  • クラス構成そのものが変わるので元のコードとはクラス名などかなり変わっている
  • Stageインスタンスはstartメソッドへ渡されるものをそのまま使うようにした
  • 画面の遷移についてのみ実装
  • import文は省略。ソースは全て同じパッケージ(transition)にあると仮定しています

java

1// transition.MyApp.java (アプリケーションクラス) 2package transition; 3... 4public class MyApp extends Application { 5 private static MyApp theApp; 6 private Stage primaryStage; 7 8 @Override public void start(Stage primaryStage) throws Exception { 9 theApp = this; 10 this.primaryStage = primaryStage; 11 this.primaryStage = new Stage(); 12 transitionTo("Login.fxml", (stage, loginController) -> { 13 stage.setTitle("Login"); 14 }); 15 } 16 17 // ロードしてからでないとコントローラーインスタンスが決まらないため、 18 // 呼び出し元で個別処理をするためにコールバックを引数へ渡すようにしてみました 19 public static <T extends Initializable> void transitionTo(String fxmlFilename, BiConsumer<Stage, T> callback) { 20 try { 21 FXMLLoader loader = new FXMLLoader(MyApp.class.getResource(fxmlFilename)); 22 Parent root = loader.load(); 23 T controller = loader.getController(); 24 Stage stage = theApp.primaryStage; 25 stage.setScene(new Scene(root)); 26 callback.accept(stage, controller); 27 stage.show(); 28 } catch (Exception e) { 29 e.printStackTrace(); 30 Platform.exit(); 31 } 32 } 33} 34 35// transition.Login.java (ログイン画面のコントローラークラス) 36package transition; 37... 38public class LoginController implements Initializable { 39 @FXML private TextField userText; 40 @FXML private Button loginButton; 41 42 @Override public void initialize(URL location, ResourceBundle resources) { 43 loginButton.disableProperty().bind(userText.textProperty().isEmpty()); 44 } 45 46 public void setDefaultUser(String user) { 47 userText.setText(user); 48 } 49 50 @FXML private void onLogin() { 51 MyApp.transitionTo("Session.fxml", (Stage stage, SessionController sessionController) -> { 52 sessionController.setUser(userText.getText()); 53 stage.setTitle("Session"); 54 }); 55 } 56} 57 58// transition.Session.java (チャット画面のコントローラークラス) 59package transition; 60... 61public class SessionController implements Initializable { 62 @FXML private Label userLabel; 63 @Override public void initialize(URL location, ResourceBundle resources) {} 64 65 public void setUser(String user) { 66 userLabel.setText(user); 67 } 68 69 @FXML private void onLogout() { 70 MyApp.transitionTo("Login.fxml", (Stage stage, LoginController loginController) -> { 71 stage.setTitle("Login again"); 72 loginController.setDefaultUser(userLabel.getText()); 73 }); 74 } 75}

XML

1<!-- Login.fxml --> 2<?xml version="1.0" encoding="UTF-8"?> 3 4<?import javafx.scene.control.Button?> 5<?import javafx.scene.control.TextField?> 6<?import javafx.scene.layout.VBox?> 7 8<VBox xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="transition.LoginController"> 9 <TextField fx:id="userText" /> 10 <Button fx:id="loginButton" mnemonicParsing="false" onAction="#onLogin" prefHeight="100.0" prefWidth="300.0" text="Login" /> 11</VBox>

XML

1<!-- Session.fxml --> 2<?xml version="1.0" encoding="UTF-8"?> 3 4<?import javafx.scene.control.Button?> 5<?import javafx.scene.control.Label?> 6<?import javafx.scene.control.TextArea?> 7<?import javafx.scene.layout.BorderPane?> 8 9<BorderPane prefHeight="200.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="transition.SessionController"> 10 <top> 11 <Label fx:id="userLabel" BorderPane.alignment="CENTER" /> 12 </top> 13 <bottom> 14 <Button mnemonicParsing="false" onAction="#onLogout" text="Logout" BorderPane.alignment="CENTER" /> 15 </bottom> 16 <center> 17 <TextArea prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" /> 18 </center> 19</BorderPane>

投稿2017/07/24 17:10

編集2017/07/25 03:02
KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yogurt

2017/07/25 04:08

例外発生の行の件、申し訳ありませんでした! そして迅速で丁寧な回答を本当にどうもありがとうございます! まさかここまで詳細に書いてくださるとは思いませんでした… まだまだコントローラークラスについての理解が足りていないと痛感しました また、一つのクラスで様々なことを記述してしまうと、どこの処理がどの画面に対応しているのか、などがわかりづらくて仕方ありませんね… KSwordOfHaste様が書いてくださったソースコードを参考に画面遷移を実装していきたいと思います! そして、NPEの原因について一つひとつ順を追って説明して頂いたこと本当に感謝してます! ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問