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

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

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

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

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

2回答

4625閲覧

[JavaFX] カスタムコントローラによる画面遷移が上手くいきません

Espeon196

総合スコア11

JavaFX

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

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

0クリップ

投稿2017/10/24 07:20

JavaFXで画面が切り替わる機能を作ろうとしています.調べて見ると,カスタムコントローラと呼ばれるコントローラとルート(シーン)を一体にしたような(?)方式で実現できるとのことでした.手始めに,最初の画面表示,つまり1ページ目をこの方式で作成しようとしたのですが,ページを表示させようとするとコントローラのインスタンスが無限に作られるようになって(ループ?)しまいました(コンストラクタの中ににSystem.out.println("AAAAA")を置いてみるとコンソールにずらずらと...).原因と解決策を教えて欲しいです.
よろしくお願いします.

AAAAA AAAAA   ・   ・   ・ AAAAA AAAAA Exception in Application start method java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389) at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767) Caused by: java.lang.RuntimeException: Exception in Application start method at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917) at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$155(LauncherImpl.java:182) at java.lang.Thread.run(Thread.java:748) Caused by: java.lang.StackOverflowError at sun.misc.URLClassPath.access$100(URLClassPath.java:65) at sun.misc.URLClassPath$1.next(URLClassPath.java:266) at sun.misc.URLClassPath$1.hasMoreElements(URLClassPath.java:277) at java.net.URLClassLoader$3$1.run(URLClassLoader.java:601) at java.net.URLClassLoader$3$1.run(URLClassLoader.java:599) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader$3.next(URLClassLoader.java:598) at java.net.URLClassLoader$3.hasMoreElements(URLClassLoader.java:623) at sun.misc.CompoundEnumeration.next(CompoundEnumeration.java:45) at sun.misc.CompoundEnumeration.hasMoreElements(CompoundEnumeration.java:54) at sun.misc.CompoundEnumeration.next(CompoundEnumeration.java:45) at sun.misc.CompoundEnumeration.hasMoreElements(CompoundEnumeration.java:54) at java.util.ServiceLoader$LazyIterator.hasNextService(ServiceLoader.java:354) at java.util.ServiceLoader$LazyIterator.hasNext(ServiceLoader.java:393) at java.util.ServiceLoader$1.hasNext(ServiceLoader.java:474) at javax.xml.stream.FactoryFinder$1.run(FactoryFinder.java:352) at java.security.AccessController.doPrivileged(Native Method) at javax.xml.stream.FactoryFinder.findServiceProvider(FactoryFinder.java:341) at javax.xml.stream.FactoryFinder.find(FactoryFinder.java:313) at javax.xml.stream.FactoryFinder.find(FactoryFinder.java:227) at javax.xml.stream.XMLInputFactory.newInstance(XMLInputFactory.java:154) at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2472) at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441) at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2409) at application.TestController.loadFXML(TestController.java:21) at application.TestController.<init>(TestController.java:15) at sun.reflect.GeneratedConstructorAccessor2.newInstance(Unknown Source) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at java.lang.Class.newInstance(Class.java:442) at sun.reflect.misc.ReflectUtil.newInstance(ReflectUtil.java:51) at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:927) at javafx.fxml.FXMLLoader$RootElement.processAttribute(FXMLLoader.java:1290) at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:220) at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:744) at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707) at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527) at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441) at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2409) at application.TestController.loadFXML(TestController.java:21) at application.TestController.<init>(TestController.java:15) at sun.reflect.GeneratedConstructorAccessor2.newInstance(Unknown Source) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at java.lang.Class.newInstance(Class.java:442) at sun.reflect.misc.ReflectUtil.newInstance(ReflectUtil.java:51) at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:927) at javafx.fxml.FXMLLoader$RootElement.processAttribute(FXMLLoader.java:1290) at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:220) at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:744) at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707) at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527) at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441) at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2409) at application.TestController.loadFXML(TestController.java:21) at application.TestController.<init>(TestController.java:15) at sun.reflect.GeneratedConstructorAccessor2.newInstance(Unknown Source) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at java.lang.Class.newInstance(Class.java:442) at sun.reflect.misc.ReflectUtil.newInstance(ReflectUtil.java:51) at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:927)         ・         ・         ・

###該当のソースコード
Test.java(メイン)

Java

1package application; 2 3import javafx.application.Application; 4import javafx.scene.Parent; 5import javafx.scene.Scene; 6import javafx.stage.Stage; 7 8 9public class Test extends Application { 10 11 private static Test instance; 12 private Stage stage; 13 14 @Override 15 public void start(Stage primaryStage) { 16 try { 17 instance = this; 18 stage = primaryStage; 19 sendTestController(); 20 stage.show(); 21 22 } catch(Exception e) { 23 e.printStackTrace(); 24 } 25 } 26 27 public static void main(String[] args) { 28 launch(args); 29 } 30 31 public void sendTestController() { 32 stage.setTitle("Test"); 33 TestController controller = new TestController(); 34 this.replaceSceneContent(controller); 35 } 36 public void replaceSceneContent(Parent controller) { 37 Scene scene = stage.getScene(); 38 if(scene == null) { 39 scene = new Scene(controller); 40 stage.setScene(scene); 41 } else { 42 stage.getScene().setRoot(controller); 43 } 44 } 45 46 public static Test getInstance() { 47 return instance; 48 } 49} 50 51

TestController.java(コントローラ)

Java

1package application; 2 3import java.io.IOException; 4import java.net.URL; 5import java.util.ResourceBundle; 6 7import javafx.fxml.FXMLLoader; 8import javafx.fxml.Initializable; 9import javafx.scene.layout.VBox; 10 11public class TestController extends VBox implements Initializable { 12 13 public TestController() { 14 System.out.println("AAAAA"); 15 loadFXML(); 16 } 17 public void loadFXML() { 18 FXMLLoader fxmlLoader = new FXMLLoader(Test.class.getResource("../fxml/test.fxml")); 19 fxmlLoader.setRoot(this); 20 try { 21 fxmlLoader.load(); 22 23 } catch(IOException e) { 24 throw new RuntimeException(e); 25 } 26 } 27 28 @Override 29 public void initialize(URL location, ResourceBundle resources) { 30 31 } 32 33} 34

test.fxml

fxml

1<?xml version="1.0" encoding="UTF-8"?> 2 3<?import javafx.scene.control.Label?> 4<?import javafx.scene.layout.VBox?> 5<?import javafx.scene.text.Font?> 6 7 8<fx:root alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" type="VBox" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.TestController"> 9 <children> 10 <Label alignment="CENTER" text="Test"> 11 <font> 12 <Font size="36.0" /> 13 </font> 14 </Label> 15 </children> 16</fx:root> 17

###補足情報(言語/FW/ツール等のバージョンなど)
Eclipse4.7.0, SceneBuilder2.0, Mac

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

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

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

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

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

guest

回答2

0

ベストアンサー

#原因
fxmlでfx:controllerに指定しているクラスは、FXMLLoaderでfxmlをロードしたときに一緒にコントローラのインスタンスが生成されます。なので、このコードの場合、以下の処理が永遠に続きます。

  1. インスタンス生成時にFXMLLoaderでFXMLをロード
  2. FXMLロード時にfx:controllerで指定されたインスタンスが生成される
  3. インスタンス生成時にFXMLLoaderでFXMLをロード
  4. 以下繰り返し

#対処
簡単な対処はtest.fxmlのfx:controllerの記述を削除し、コード側でsetController(this)でコントローラを指定してやるとうまくいくと思います。

というより、本来コントローラのインスタンスはfxmlのfx:controllerの指定からインスタンスを生成するべきであり、new TestController()で直接インスタンスを作るのは邪道です。

邪道です、というだけではなんなので、私がコントローラを取るやり方を書いておきます。

  1. fxmlにfx:controllerを指定しておく
  2. コントローラ側にFMXLLoader関連の処理は書かない
  3. 呼び出し側で以下のコードでコントローラのインスタンスを得る

Java

1FXMLLoader loader = new FXMLLoader(getClass().getResource("test.fxml")); 2Parent root = loader.load(); // fx:rootに紐付いたノードインスタンスが得られる 3TestController controller = loader.getController(); // fx:controllerに紐付いたコントローラインスタンスが得られる

回りくどい方法ですが、これらの処理をメソッド化してしまえばそれほど気にならなくなります。

投稿2017/10/24 07:40

編集2017/10/25 00:17
masaya_ohashi

総合スコア9206

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

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

Espeon196

2017/10/27 06:48

原因の説明がとてもわかりやすく助かりました.fx:controllerはインスタンスを生成するのをわかっていませんでした.そしてこちらがもっともな方法なのですね.ご指摘の通り呼び出し側でgetController()でコントローラのインスタンスを取得できました.活用させていただきます,ありがとうございます!
guest

0

蛇足な?訂正:

既に解決済みですが、元の回答で「回避策」として書いたことを訂正させていただきます。本当にカスタムコントロールとしたい場合は以下の方法が使えたのでした。

(1) カスタムコントロールなのであくまでfx:rootとしてfxmlを定義する
mainのfxmlからカスタムコントロールのfxmlをincludeする感じでしょうか。
(2) fx:controllerも定義しておく
(3) loadするより前にFXMLLoader#setControllerFactoryでコントローラーファクトリーを設定しておく

(3)は具体的に以下のように書けます。(訂正:最初に書いたコードはカスタムコントロールっぽくなかったので書き直しました)

java

1public class TestController extends VBox implements Initializable { 2 @FXML // fx:controllerを指定できるので@FXMLに関する開発ツールのサポートが享受できる 3 Label label; 4 5 // 他のfxml内でこのクラスを利用できるようにコンストラクターでロード 6 public TestController() { 7 FXMLLoader fxmlLoader = new FXMLLoader(Test.class.getResource("...")); 8 TestController self = this; 9 fxmlLoader.setRoot(self); 10 // fx:controllerを書いて場合でもControllerFactoryを設定しておけば 11 // 新たなインスタンスが生成されない。 12 fxmlLoader.setControllerFactory((cls) -> self); 13 try { 14 fxmlLoader.load(); 15 } catch (IOException e) { 16 throw new RuntimeException(e); 17 } 18 } 19 20 // これ以降は普通に... 21 @Override 22 public void initialize(URL location, ResourceBundle resources) { 23 ... 24 } 25}

https://docs.oracle.com/javase/jp/8/javafx/fxml-tutorial/custom_control.htmにある例だとsetControllerを使ってますが、SceneBuilderなどのサポートを享受するためのfx:controllerが書けない点が何も言及されてない(?)ので今までモヤモヤしてたのですが、APIリファレンスを落ち着いて読めば答えがありました orz

以下元の回答

本件は自分もモヤモヤしていてスッキリした解決法をしらないのですが・・・

###回避策
(1) loadするより前にFXMLLoader#setControllerでコントローラーインスタンスを設定する
(2) FXML上からfx:controllerを取る

(1)をやらないとFXMLLoaderはload時にまずコントローラーインスタンスを自動生成しようとしますので、stack overflowになります。(1)を対処すると、今度はfx:root指定があるのにfx:controllerを指定しないでほしいと文句を言われてしまいます。

(1)(2)の両方をやるととりあえず動きます。しかしながら、(2)は致命的ではないものの開発する際に@FXMLインジェクションについてのSceneBuilder/IDEのサポートがなくなるので非常に不便です。コントローラークラスがFXML上に記述されなくなるので当然なのですが、SceneBuilderでfx:idを設定しようとしてもコントローラー上定義してあるフィールドの一覧が出てくれないのでフィールド名やメソッド名を手打ちせねばなりませんし、逆にFXML上でfx:idで指定されているにもかかわらず、@FXMLアノテーション付きのフィールドやメソッドが「アクセス箇所がない」という表示になってしまいます。

イメージ説明

自分はしかたないので、編集中はfx:controller付きで編集し最後にfx:controllerを取るというごまかしで我慢してます。本当にこうしかできないのかモヤモヤしますが、どうすれば(2)をやらずにロードできるかこれまで何度か調べましたがわかりませんでした。

###カスタムコントロールは必要か?

画面が切り替わる機能を作ろうとしています.調べて見ると,カスタムコントローラと呼ばれるコントローラとルート(シーン)を一体にしたような(?)方式で実現できる...

この点についてですが、カスタムコントロールにする必要はないと思います。カスタムコントロールをnewする代わりに次のようにしたほうが簡単です。

java

1public TestController implements Initializable { 2 public static TestController createInstance() { 3 try { 4 return FXMLLoader.load(TestController.class.getResource("...")); 5 } catch (...) { 6 ... 7 } 8 } 9 10 ... 11} 12 13シーンを作るとき 14new TestController() 15=> 16TestController.createInstance();

普通に作ればいいのでfx:rootは指定せず、fx:controllerを普通に指定できます。@FXMLインジェクションのSceneBuilder/IDEのサポートも受けられるのでこちらがお勧めです。

投稿2017/10/24 08:14

編集2017/10/27 08:26
KSwordOfHaste

総合スコア18392

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

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

Espeon196

2017/10/27 07:28

fx:controllerとsetControllerはどうしてもダブってしまうのですね.そのままFXMLLoader.load()を返す関数を作ってあげれば,fx:rootを指定しなくても画面遷移でき,普段SceneBuilderを使用するときと同じようにプログラムを作成できるということですね.たしかにカスタムコントローラを使用するよりこちらの方が簡潔でわかりやすく,無事プログラムが実行できました.助かりました,ありがとうございます!
KSwordOfHaste

2017/10/27 08:19

いまさらですが、自分の回答の回避策よくないので訂正させていただきました。
Espeon196

2017/10/27 10:21

fx:rootとfx:controllerを付けたまま実行できました! ですが仕組みがよくわからないです... new TestController()→コンストラクタtestController()でインスタンス生成→その際setControllerFactory()でインスタンスの生成方法をtestController()で生成するインスタンスが生成されるように設定する→fxml.load()でfx:contollerによるインスタンス生成が無効(コントローラファクトリーで決められたようにしかインスタンスが生成されないため)ということでしょうか
KSwordOfHaste

2017/10/27 10:49 編集

概ねそんな感じです。 FXMLLoaderはfx:contollerの指定に従いClassオブジェクトまでは求め、その後controllerFactoryが設定されてればそちらからインスタンスをもらい、さもなければ無条件にインスタンスをnewするというふうになっていました。controllerFactoryはfx:rootにしたときだけの機能ではなく設定されていれば自由にインスタンスの求め方をカスタマイズするためのものみたいです。
Espeon196

2017/10/27 11:24

なるほどです.とても勉強になりました,詳しくありがとうございます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問