パラメータの渡し方 JavaFX FXML

javafx parameters dependency-injection parameter-passing fxml


javafxでセカンダリウィンドウにパラメータを渡すには?対応するコントローラと通信する方法はありますか?

例:ユーザーが TableView から顧客を選択すると、新しいウィンドウが開き、顧客の情報が表示されます。

Stage newStage = new Stage();
try 
{
    AnchorPane page = (AnchorPane) FXMLLoader.load(HectorGestion.class.getResource(fxmlResource));
    Scene scene = new Scene(page);
    newStage.setScene(scene);
    newStage.setTitle(windowTitle);
    newStage.setResizable(isResizable);
    if(showRightAway) 
    {
        newStage.show();
    }
}

newStage が新しいウィンドウになります。問題は、コントローラーに顧客の情報を探す場所を(IDをパラメーターとして渡すことによって)指示する方法が見つからないことです。

何かアイデアは?




Answer 1 jewelsea


推奨されるアプローチ

この回答では、FXMLコントローラにパラメータを渡すための様々なメカニズムを列挙しています。

小規模なアプリケーションには、呼び出し元から直接コントローラにパラメータを渡すことを強くお勧めします。

大規模で複雑なアプリケーションの場合、アプリケーション内で依存性注入またはイベントバスメカニズムを使用するかどうかを調べることは価値があります。

呼び出し元からコントローラーに直接パラメーターを渡す

FXML ローダのインスタンスからコントローラを取得し、そのコントローラ上でメソッドを呼び出して必要なデータ値で初期化することで、カスタムデータをFXMLコントローラに渡すことができます。

以下のコードのようなもの。

public Stage showCustomerDialog(Customer customer) {
  FXMLLoader loader = new FXMLLoader(
    getClass().getResource(
      "customerDialog.fxml"
    )
  );

  Stage stage = new Stage(StageStyle.DECORATED);
  stage.setScene(
    new Scene(
      (Pane) loader.load()
    )
  );

  CustomerDialogController controller = 
    loader.<CustomerDialogController>getController();
  controller.initData(customer);

  stage.show();

  return stage;
}

...

class CustomerDialogController {
  @FXML private Label customerName;
  void initialize() {}
  void initData(Customer customer) {
    customerName.setText(customer.getName());
  }
}

新しいFXMLLoaderは、サンプルコードに示すように、つまり new FXMLLoader(location) のように構築されます。場所はURLであり、次の方法でFXMLリソースからそのようなURLを生成できます。

new FXMLLoader(getClass().getResource("sample.fxml"));

FXMLLoaderで静的ロード関数を使用しないように注意してください。そうしないと、ローダーインスタンスからコントローラーを取得できなくなります。

FXMLLoader インスタンス自身は、ドメインオブジェクトについて何も知りません。FXMLLoader のコンストラクタにアプリケーション固有のドメインオブジェクトを直接渡すことはありません。

  1. 指定された場所でfxmlマークアップに基づいてFXMLLoaderを構築します。
  2. FXMLLoader インスタンスからコントローラを取得します。
  3. 取得したコントローラのメソッドを呼び出して、ドメインオブジェクトへの参照をコントローラに提供します。

このブログ(別のライターによる)は、代替の類似した例を提供しています。

FXMLLoaderでのコントローラーの設定

CustomerDialogController dialogController = 
    new CustomerDialogController(param1, param2);

FXMLLoader loader = new FXMLLoader(
    getClass().getResource(
        "customerDialog.fxml"
    )
);
loader.setController(dialogController);

Pane mainPane = (Pane) loader.load();

コードで新しいコントローラーを作成し、必要なパラメーターを呼び出し元からコントローラーコンストラクターに渡すことができます。コントローラーを作成したら、 load() インスタンスメソッドを呼び出すに、FXMLLoaderインスタンスコントローラーを設定できます

ローダー(JavaFX 2.x)でコントローラーを設定するには、fxmlファイルで fx:controller 属性も定義できません。

FXMLでの fx:controller 定義の制限により、私はコントローラーをFXMLLoaderに設定するのではなく、FXMLLoaderからコントローラーを取得することを個人的に好みます。

コントローラに外部静的メソッドからパラメータを取得させる

このメソッドの例は、Controller.javaファイル内のJavafx 2.0 How-to Application.getParameters()に対するSergeyの回答です。

依存性注入を使用する

FXMLLoader は、FXMLLoader 上にカスタムコントローラファクトリを設定することで、Guice や Spring、Java EE CDI のような依存性注入システムをサポートしています。これは、それぞれの依存性注入システムによって注入された依存値を持つコントローラのインスタンスを作成するために使用できるコールバックを提供します。

Springを使ったJavaFXアプリケーションとコントローラの依存性注入の例は、回答の中にあります。

本当に素敵でクリーンな依存性注入アプローチは、それを使用するサンプルのエアハックアプリケーションを備えたafterburner.fxフレームワークによって例示されます。afterburner.fx は、依存性注入を実行するためにJEE6 javax.injectに依存しています

イベントバスを使用する

元のFXML仕様の作成者および実装者であるGreg Brownは、FXMLのインスタンス化されたコントローラーと他のアプリケーションロジック間の通信に、Guava EventBusなどのイベントバスの使用を検討することを提案しています。

EventBusはアノテーションを備えたシンプルながらも強力なパブリッシュサブスクライブAPIで、POJOはお互いを参照することなく、JVM内のどこにいても通信を行うことができます。

フォローアップQ&A

最初のメソッドでは、なぜStageを返すのでしょうか?return stage;の直前に既に show();コマンドを与えているので、このメソッドも同様に void にすることができます。を返すことで、どのように利用を計画しているのでしょうか?

これは問題に対する機能的な解決策です。ステージは showCustomerDialog 関数から返されるので、後でメインウィンドウのボタンクリックに基づいてステージを非表示にするなど、何かを実行したい外部クラスがステージへの参照を格納できます。別のオブジェクト指向ソリューションでは、機能とステージ参照をCustomerDialogオブジェクト内にカプセル化したり、CustomerDialogでステージを拡張したりできます。FXML、コントローラー、およびモデルデータをカプセル化するカスタムダイアログへのオブジェクト指向インターフェースの完全な例は、この回答の範囲を超えていますが、作成を志向する人にとって価値のあるブログ投稿になるかもしれません。


StackOverflowユーザーが@dzimという名前で提供する追加情報

Spring Boot Dependency Injectionの例

それをどのように行うかという問題「The Spring Boot Way」について、添付のパーマリンクで私が答えたJavaFX 2についての議論がありました。このアプローチはまだ有効であり、2016年3月にSpring Boot v1.3.3.RELEASEでテストされています:https ://stackoverflow.com/a/36310391/1281217


時には、結果を発信者に伝えたい場合もありますが、その場合は関連する質問の回答を確認することができます。




Answer 2 Zephyr


私はこれが非常に古いポストであり、すでにいくつかの素晴らしい答えを持っていることを認識していますが、私はそのようなアプローチの1つを実証するためにシンプルなMCVEを作成し、新しいコーダーがすぐにアクションで概念を見るための方法を可能にしたいと思いました。

この例では、5つのファイルを使用します。

  1. Main.java-アプリケーションを起動し、最初のコントローラーを呼び出すためだけに使用されます。
  2. Controller1.java-最初のFXMLレイアウトのコントローラー。
  3. Controller2.java -2番目のFXMLレイアウトのコントローラー。
  4. Layout1.fxml-最初のシーンのFXMLレイアウト。
  5. Layout2.fxml -2番目のシーンのFXMLレイアウト。

すべてのファイルは、この投稿の下部に全体がリストされています。

目標:から渡す値を実証するために Controller1Controller2 およびその逆。

プログラムの流れ:

  • 最初のシーンには、 TextFieldButton 、および Label が含まれています。ときに Button クリックされると、第二のウィンドウがロードされ、中に入力したテキストを含む、表示された TextField
  • 2番目のシーンには、 TextFieldButtonLabel もあります。 Label に入力されたテキストが表示されます TextField の最初のシーンでは。
  • 2番目のシーンの TextField にテキストを入力してその Button をクリックすると、最初のシーンの Label が更新され、入力したテキストが表示されます。

これは非常にシンプルなデモンストレーションであり、確かに何らかの改善のために立つことができますが、コンセプトを非常に明確にする必要があります。

コード自体にも、何がどうなっているのか、どのようになっているのかを詳細にコメントしています。

コード

Main.java:

import javafx.application.Application;
import javafx.stage.Stage;

public class Main extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        // Create the first controller, which loads Layout1.fxml within its own constructor
        Controller1 controller1 = new Controller1();

        // Show the new stage
        controller1.showStage();

    }
}

Controller1.java:

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

import java.io.IOException;

public class Controller1 {

    // Holds this controller's Stage
    private final Stage thisStage;

    // Define the nodes from the Layout1.fxml file. This allows them to be referenced within the controller
    @FXML
    private TextField txtToSecondController;
    @FXML
    private Button btnOpenLayout2;
    @FXML
    private Label lblFromController2;

    public Controller1() {

        // Create the new stage
        thisStage = new Stage();

        // Load the FXML file
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout1.fxml"));

            // Set this class as the controller
            loader.setController(this);

            // Load the scene
            thisStage.setScene(new Scene(loader.load()));

            // Setup the window/stage
            thisStage.setTitle("Passing Controllers Example - Layout1");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Show the stage that was loaded in the constructor
     */
    public void showStage() {
        thisStage.showAndWait();
    }

    /**
     * The initialize() method allows you set setup your scene, adding actions, configuring nodes, etc.
     */
    @FXML
    private void initialize() {

        // Add an action for the "Open Layout2" button
        btnOpenLayout2.setOnAction(event -> openLayout2());
    }

    /**
     * Performs the action of loading and showing Layout2
     */
    private void openLayout2() {

        // Create the second controller, which loads its own FXML file. We pass a reference to this controller
        // using the keyword [this]; that allows the second controller to access the methods contained in here.
        Controller2 controller2 = new Controller2(this);

        // Show the new stage/window
        controller2.showStage();

    }

    /**
     * Returns the text entered into txtToSecondController. This allows other controllers/classes to view that data.
     */
    public String getEnteredText() {
        return txtToSecondController.getText();
    }

    /**
     * Allows other controllers to set the text of this layout's Label
     */
    public void setTextFromController2(String text) {
        lblFromController2.setText(text);
    }
}

Controller2.java:

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

import java.io.IOException;

public class Controller2 {

    // Holds this controller's Stage
    private Stage thisStage;

    // Will hold a reference to the first controller, allowing us to access the methods found there.
    private final Controller1 controller1;

    // Add references to the controls in Layout2.fxml
    @FXML
    private Label lblFromController1;
    @FXML
    private TextField txtToFirstController;
    @FXML
    private Button btnSetLayout1Text;

    public Controller2(Controller1 controller1) {
        // We received the first controller, now let's make it usable throughout this controller.
        this.controller1 = controller1;

        // Create the new stage
        thisStage = new Stage();

        // Load the FXML file
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout2.fxml"));

            // Set this class as the controller
            loader.setController(this);

            // Load the scene
            thisStage.setScene(new Scene(loader.load()));

            // Setup the window/stage
            thisStage.setTitle("Passing Controllers Example - Layout2");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Show the stage that was loaded in the constructor
     */
    public void showStage() {
        thisStage.showAndWait();
    }

    @FXML
    private void initialize() {

        // Set the label to whatever the text entered on Layout1 is
        lblFromController1.setText(controller1.getEnteredText());

        // Set the action for the button
        btnSetLayout1Text.setOnAction(event -> setTextOnLayout1());
    }

    /**
     * Calls the "setTextFromController2()" method on the first controller to update its Label
     */
    private void setTextOnLayout1() {
        controller1.setTextFromController2(txtToFirstController.getText());
    }

}

Layout1.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
    <VBox alignment="CENTER" spacing="10.0">
        <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
        </padding>
        <Label style="-fx-font-weight: bold;" text="This is Layout1!"/>
        <HBox alignment="CENTER_LEFT" spacing="10.0">
            <Label text="Enter Text:"/>
            <TextField fx:id="txtToSecondController"/>
            <Button fx:id="btnOpenLayout2" mnemonicParsing="false" text="Open Layout2"/>
        </HBox>
        <VBox alignment="CENTER">
            <Label text="Text From Controller2:"/>
            <Label fx:id="lblFromController2" text="Nothing Yet!"/>
        </VBox>
    </VBox>
</AnchorPane>

Layout2.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
    <VBox alignment="CENTER" spacing="10.0">
        <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
        </padding>
        <Label style="-fx-font-weight: bold;" text="Welcome to Layout 2!"/>
        <VBox alignment="CENTER">
            <Label text="Text From Controller1:"/>
            <Label fx:id="lblFromController1" text="Nothing Yet!"/>
        </VBox>
        <HBox alignment="CENTER_LEFT" spacing="10.0">
            <Label text="Enter Text:"/>
            <TextField fx:id="txtToFirstController"/>
            <Button fx:id="btnSetLayout1Text" mnemonicParsing="false" text="Set Text on Layout1"/>
        </HBox>
    </VBox>
</AnchorPane>



Answer 3 Alexander Kirov


javafx.scene.Nodeクラスには、setUserData(Object)とObject getUserData()のペアのメソッドがあります。

これは、あなたの情報をノードに追加するために使用することができます。

そのため、page.setUserData(info)を呼び出すことができます。

また、情報が設定されているかどうかをコントローラがチェックすることができます。また、必要に応じて、ObjectPropertyを使ってバックフォワードでデータを転送することもできます。

ここでドキュメントを確認してください:http : //docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html「最初のバージョンでは、handleButtonAction()は@FXMLでタグ付けされていますコントローラのドキュメントで定義されたマークアップが呼び出せるようにします。2番目の例では、ローダーが値を設定できるようにボタンフィールドに注釈が付けられています。initialize()メソッドにも同様に注釈が付けられています。

そこで、ノードにコントローラを関連付けて、ノードにユーザデータを設定する必要があります。




Answer 4 user1503636


ここに、名前空間を介してfxmlドキュメントにパラメータを渡す例があります。

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/null" xmlns:fx="http://javafx.com/fxml/1">
    <BorderPane>
        <center>
            <Label text="$labelText"/>
        </center>
    </BorderPane>
</VBox>

名前空間変数 labelText の値 External Text を定義します。

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

import java.io.IOException;

public class NamespaceParameterExampleApplication extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws IOException {
        final FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("namespace-parameter-example.fxml"));

        fxmlLoader.getNamespace()
                  .put("labelText", "External Text");

        final Parent root = fxmlLoader.load();

        primaryStage.setTitle("Namespace Parameter Example");
        primaryStage.setScene(new Scene(root, 400, 400));
        primaryStage.show();
    }
}



Answer 5 diego matos - keke


この作品..

渡した値を最初に印刷するときは、nullになることを覚えておいてください。ウィンドウがロードされた後でも、他のコンポーネント用にコーディングしたいすべてのものと同じように使用できます。

ファーストコントローラー

try {
    Stage st = new Stage();
    FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/inty360/free/form/MainOnline.fxml"));

    Parent sceneMain = loader.load();

    MainOnlineController controller = loader.<MainOnlineController>getController();
    controller.initVariable(99L);

    Scene scene = new Scene(sceneMain);
    st.setScene(scene);
    st.setMaximized(true);
    st.setTitle("My App");
    st.show();
} catch (IOException ex) {
    Logger.getLogger(LoginController.class.getName()).log(Level.SEVERE, null, ex);
}

別のコントローラ

public void initVariable(Long id_usuario){
    this.id_usuario = id_usuario;
    label_usuario_nombre.setText(id_usuario.toString());
}