JavaFXでの画面遷移(カスタムコントローラー方式)

カスタムコントロールによる画面遷移のメリット

・コントローラーのコンストラクタを使ってデータ渡しが可能
・画面遷移処理が簡単
・画面元のクラスは遷移先の画面のコントローラーだけ知ってればいい

サンプル概要

今回は画面1と画面2を作成し、画面1から画面2に遷移したり、画面2から画面1に戻れるようなサンプルを作ってみます。

FXML

まず、FXMLから見ていきましょう。FXMLをカスタムコントロールとして作成するので、以下のように最初のタグをrootにします。また、使用するレイアウトクラスをrootタグのtype属性として指定します。

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<fx:root type="javafx.scene.layout.AnchorPane" xmlns:fx="http://javafx.com/fxml">
    <children>
        <Label layoutX="126" layoutY="90" minHeight="16" minWidth="69" fx:id="label" />
        <Button layoutX="126" layoutY="150" text="Page2へ" onAction="#handleButtonAction" />
    </children>
</fx:root>

そしてページ2のfxmlは以下になります。ページ1の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.*?>

<fx:root type="javafx.scene.layout.AnchorPane" xmlns:fx="http://javafx.com/fxml">
    <children>
        <Label layoutX="126" layoutY="90" minHeight="16" minWidth="69" fx:id="label" />
        <Button layoutX="126" layoutY="150" text="Page1へ" onAction="#handleButtonAction" />
    </children>
</fx:root>

コントローラー

カスタムコントロールとして作成するため、コントローラーは使用するレイアウトクラスを継承しなければいけません。今回はfxmlでAnchorPaneレイアウトを使っているのでコントローラーはjavafx.scene.layout.AnchorPane.classを継承して作成します。

public class Page1Controller extends AnchorPane implements Initializable {
   <略>
    
    /**
     * コンストラクタ
     * @param labelText 
     */
    public Page1Controller(String labelText) {
        this.labelText = labelText;
        
        loadFXML();
    }
    
    /**
     * FXMLのロード
     */
    private void loadFXML() {

        FXMLLoader fxmlLoader = new FXMLLoader(Sample.class.getResource("page1.fxml"));
        fxmlLoader.setRoot(this);
        
        // 自分自身をコントロールとして設定
        fxmlLoader.setController(this);
        
        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }
    
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        label.setText(labelText);
    }
    
}

Page1Controller.classではコンストラクタでString型の引数を受け取りinitializeメソッドでそれをLabelに設定しています。また、loadFXMLメソッドでfxmlをロードし、fxmlのコントローラーを自分自身に指定しています。ここは今回のカスタムコントロール方式の肝です。

ページ2のコントローラーも同様なように作成します。名前をPage2Controller.classとします。(省略)

メインクラス

アプリのメインクラス(ここではSample.java)に画面遷移のためのロジックを追加します。

/**
     * Page1へ遷移する
     * @param labelText
     */
    public void sendPage1Controller(String labelText) {

        stage.setTitle("Page1");

        Page1Controller controller = new Page1Controller(labelText);
        this.replaceSceneContent(controller);
    }
    
    /**
     * Page1へ遷移する
     * @param labelText
     */
    public void sendPage2Controller(String labelText) {

        stage.setTitle("Page2");

        Page2Controller controller = new Page2Controller(labelText);
        this.replaceSceneContent(controller);
    }
    
    /**
     * シーンの変更
     * @param controller 
     */
    private void replaceSceneContent(Parent controller) {
        Scene scene = stage.getScene();
        if (scene == null) {
            scene = new Scene(controller);
            stage.setScene(scene);
        } else {
            stage.getScene().setRoot(controller);
        }
    }
    
    /**
     * Get Instance
     *
     * @return
     */
    public static Sample getInstance() {
        return instance;
    }

replaceSceneContentメソッドで渡されたコントローラーのインスタンスをシーンに設定しています。各画面のコントローラーはjavaFXのレイアウトの中のどれかを継承しているため、引数の型はすべてのレイアウトの親クラスのjavafx.scene.Parentにします。

そしてgetInstanceメソッドは他のクラスからメインクラスのインスタンスを取得するためのものです。

次にのメインクラスのstartメソッドを以下のように修正します。

    @Override
    public void start(Stage primaryStage) throws Exception {
        
        // インスタンス
        instance = this;
        
        // ステージの設定
        stage = primaryStage;
        stage.setWidth(800);
        stage.setHeight(600);
        
        // ページ1に遷移
        sendPage1Controller("Page1です。");
        
        // ステージの設定
        stage.show();
    }

遷移処理

画面1のボタンをクリックして画面2に遷移し、画面2のボタンをクリックして画面1に戻るためのボタンクリック処理を各画面のコントローラーに実装します。値渡しも相手のコントローラーのコンストラクタを使うため、非常にシンプルになります。

Page1Controller.class のボタンイベント

/**
     * ボタンクリックアクション
     */
    @FXML
    protected void handleButtonAction() {
        Sample.getInstance().sendPage2Controller("Page2です。");
    }

Page2Controller.class のボタンイベント

/**
     * ボタンクリックアクション
     */
    @FXML
    protected void handleButtonAction() {
        Sample.getInstance().sendPage1Controller("Page1です。");
    }

メインクラスとコントローラークラスの完全なソース

package javafx;

import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

/**
 *
 * @author takahara
 */
public class Sample extends Application {
    
    /**
     * Sample class instance
     */
    private static Sample instance;
    
    /**
     * ステージ
     */
    private Stage stage;
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        
        // インスタンス
        instance = this;
        
        // ステージの設定
        stage = primaryStage;
        stage.setWidth(800);
        stage.setHeight(600);
        
        // ページ1に遷移
        sendPage1Controller("Page1です。");
        
        // ステージの設定
        stage.show();
    }

    /**
     * The main() method is ignored in correctly deployed JavaFX application.
     * main() serves only as fallback in case the application can not be
     * launched through deployment artifacts, e.g., in IDEs with limited FX
     * support. NetBeans ignores main().
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }
    
    /**
     * Page1へ遷移する
     * @param labelText
     */
    public void sendPage1Controller(String labelText) {

        stage.setTitle("Page1");

        Page1Controller controller = new Page1Controller(labelText);
        this.replaceSceneContent(controller);
    }
    
    /**
     * Page1へ遷移する
     * @param labelText
     */
    public void sendPage2Controller(String labelText) {

        stage.setTitle("Page2");

        Page2Controller controller = new Page2Controller(labelText);
        this.replaceSceneContent(controller);
    }
    
    /**
     * シーンの変更
     * @param controller 
     */
    private void replaceSceneContent(Parent controller) {
        Scene scene = stage.getScene();
        if (scene == null) {
            scene = new Scene(controller);
            stage.setScene(scene);
        } else {
            stage.getScene().setRoot(controller);
        }
    }
    
    /**
     * Get Instance
     *
     * @return
     */
    public static Sample getInstance() {
        return instance;
    }
    
}

Page1Controller.class

package javafx;

import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;

/**
 *
 * @author takahara
 */
public class Page1Controller extends AnchorPane implements Initializable {
    
    /** ラベルに表示するテキスト */
    private final String labelText;
    
    /** ラベル */
    @FXML
    private Label label;
    
    /**
     * コンストラクタ
     * @param labelText 
     */
    public Page1Controller(String labelText) {
        this.labelText = labelText;
        
        loadFXML();
    }
    
    /**
     * FXMLのロード
     */
    private void loadFXML() {

        FXMLLoader fxmlLoader = new FXMLLoader(Sample.class.getResource("page1.fxml"));
        fxmlLoader.setRoot(this);
        
        // 自分自身をコントロールとして設定
        fxmlLoader.setController(this);
        
        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }
    
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        label.setText(labelText);
    }
    
    /**
     * ボタンクリックアクション
     */
    @FXML
    protected void handleButtonAction() {
        Sample.getInstance().sendPage2Controller("Page2です。");
    }
    
}

Page2Controller.class

package javafx;

import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;

/**
 *
 * @author takahara
 */
public class Page2Controller extends AnchorPane implements Initializable {
    
    /** ラベルに表示するテキスト */
    private final String labelText;
    
    /** ラベル */
    @FXML
    private Label label;
    
    /**
     * コンストラクタ
     * @param labelText 
     */
    public Page2Controller(String labelText) {
        this.labelText = labelText;
        
        loadFXML();
    }
    
    /**
     * FXMLのロード
     */
    private void loadFXML() {

        FXMLLoader fxmlLoader = new FXMLLoader(Sample.class.getResource("page2.fxml"));
        fxmlLoader.setRoot(this);
        
        // 自分自身をコントロールとして設定
        fxmlLoader.setController(this);
        
        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }
    
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        label.setText(labelText);
    }    
    
    /**
     * ボタンクリックアクション
     */
    @FXML
    protected void handleButtonAction() {
        Sample.getInstance().sendPage1Controller("Page1です。");
    }
}


  • 2JavaFXでの画面遷移(カスタムコントローラー方式)

<