最終更新日:2020/07/19 原本2018-02-05

[JAVAFX]TextFiledに入力された値の取得方法

checkベストアンサー

+2

ユーザーとの対話をするための小さな画面を一般にダイアログと呼びます。多分画面Aはダイアログとして設計できると思います。

ダイアログを実装するためには通常の画面(JavaFXではStage)と同様にもできます。質問者さんはおそらくそういうイメージをお持ちであろうと思います。しかしながら大抵のGUIライブラリーと同様JavaFXもダイアログを簡便に実装するための特化機能が用意されておりそちらを使う方がより簡単と思います。

JavaFXでのダイアログはjavafx.scene.control.Dialogというベースクラスおよびその派生として定義することが想定されていますが、本件のように文字列テキストの入力なら専用クラスjavafx.scene.control.TextInputDailogが用意されていますのでそれを使うのがよいと思います。

簡単な例を挙げてみますと以下のようになります。

TextInputDialog dialog = new TextInputDialog("") {
  {
    setTitle("filtering");
    setHeaderText("input filtering text");
  }
};
Optional<String> optInput = dialog.showAndWait();
if (!optInput.isPresent()) {
  // キャンセルが押された場合optInputは値を持たない
  System.out.println("cancelled");
} else {
  // OKが押された場合はoptInputは値を持ち、それはgetにより得られる。
  System.out.println("input text = " + optInput.get());
}

上記ではデフォルトから2点のみ動作を変えています。一つはダイアログタイトルで、もう一つはヘッダーテキストです。様々なカスタマイズをするためにはjavafx.control.Dialogの機能を調べるとやり方がわかってきますが、とりあえずはデフォルトの振る舞いがどのようになっているかを観察してみるとよいでしょう。

他のコントローラクラスにその入力された値を渡す方法が分からず悩んでいます。

ダイアログを用いる場合、ユーザーの入力結果はshowAndWaitメソッドの戻り値になるので、それを用いることになります。本件の場合、画面Bのコントローラーインスタンスへなんらかの方法で渡すのが自然と思います。渡し方は画面Bをどうやって生成・表示するかによって若干のバリエーションがありますが、FXMLとFXMLLoaderにより画面を生成する方法を用いるなら、以下のようにFXMLロード後にコントローラーインスタンスを取得しそこに定義したメソッドを通じて設定という流れが典型的な方法の一つだと思います。

FXMLLoader loader = new FXMLLoader(...);
loader.load();
ControllerB controllerB = loader.getController();
controllerB.setFilterText(optInput.get());

追記:
回答コメントから質問者さんの想定がダイアログではないようですので、各画面のStage, コントローラーを画面遷移の都度生成する前提に注意しつつ、画面遷移方法を考えてみました。以下の例は唯一の方法でもベストなものというわけではありません。やりかたには微妙なバリエーションがあるでしょう。つまり「ある画面から別の画面へ情報を引き渡す」実装例の一つです。

画面遷移メソッド(アプリケーションクラスに用意する例)

public class MyApp extends Application {
  private static MyApp app;

  public static MyApp getInstance() {
    return Objects.requireNonNull(app);
  }

  public MyApp() { app = this; }

  HashMap<Class<? extends Initializable>, String> fxmlMap = new HashMap<>();

  @Override
  public void start(Stage stage) {
    fxmlMap.put(AController.class, "A.fxml");
    fxmlMap.put(BController.class, "B.fxml");

    try {
      Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));
      stage.setScene(new Scene(root));
      stage.show();
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public <C extends Initializable> C showStage(Class<C> controllerClass) throws IOException {
    FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlMap.get(controllerClass)));
    Parent parent = loader.load();
    Stage stage = new Stage();
    stage.setScene(new Scene(parent));
    stage.show();
    return loader.getController();
  }
}


画面Aから画面Bへの遷移処理(検索ボタンのアクション)

public class AController implements Initializable {
  @FXML
  private TextField filterText;

  @Override
  public void initialize(URL location, ResourceBundle resources) {

  }

  @FXML
  private void onShowB(ActionEvent ev) throws IOException {
    Stage stage = (Stage)((Node)ev.getSource()).getScene().getWindow();
    stage.hide();
    BController bController = MyApp.getInstance().showStage(BController.class);
    bController.setFilterText(filterText.getText());
  }
}


画面Bにて検索条件を受け取るメソッド

public class BController implements Initializable {
  @FXML
  TableView<Model> tableView;

  @Override
  public void initialize(URL location, ResourceBundle resources) {
    ...
  }

  public void setFilterText(String text) {
    // 指定テキストによりモデルを生成する(DBアクセスしてモデルインスタンスのリストを生成)
    // 時間がかかる処理ならバックグラウンドスレッドで行うべきだが、ここでは簡単のため
    // アプリケーションスレッドでやると想定
    List<Model> modelList = ...;
    tableView.getItems().setAll(modelList);
  }
}


自分が考えた点は以下です。

  • 多数の画面があるなら遷移処理を共通化したい
    アプリケーションクラスでshowStageというメソッドを用意し各画面ではこれを使う想定としました。また本質問の目的にあるように画面間での情報の引き渡しを遷移元と遷移先のコントローラーインスタンス間で行えるよう、画面遷移メソッドは遷移先のコントローラーを返すようにしています。
  • ステージのshowとコントローラークラスへの情報のやりとり順序
    例を簡単にするため共通メソッド(showStage)は無条件に生成したstageをshowし、その後にstageに結び付いたコントローラーインスタンスに対して情報設定メソッドを呼び出すと想定しています。viewがいかなる状態であろうと(表示前であろうと表示後であろうと)コントローラーやモデルの状態を変更した際にビューが適切に追従するように作ることは難しくないのですが、モデルの生成に時間がかかるような場合、画面を表示後にしばらくして表示内容が変わるのか、表示内容を確定させてから画面を表示させるかで見栄えが変わると思います。そうした点は細かなチューニング対象だと思います。
  • staticの仕様は最小限にする(追記2)
    本例ではアプリケーションインスタンスをシングルトンとしてMyApp#app及びMyApp#getInstanceのみstaticにしています。こうしておくと(殆どの定義がインスタンスフィールド、インスタンスメソッドのため)アプリケーションインスタンスを複数生成するような設計に変更する場合でも変更の影響を最小限にできると思います。Javaに限らずstatic(=グローバル)はできるだけ避けるという考え方があると思いますのでそれに倣ったつもりです。そういう想定が必要ないならこうした点をいちいち気にせず適宜staticにしてもよいとは思います。
  • KSwordOfHaste

    KSwordOfHaste

    2018/02/06 18:22

    回答の最後の方に「staticをなるべく避ける」ことについて追記しました。

    キャンセル

  • shibayun

    shibayun

    2018/02/07 16:46

    様々な回答ありがとうございます。ここ最近調べながら頑張って理解しているところです。
    申し訳ないのですが、BControllerの 'List<Model> modelList = ...;' とはいったい何なのでしょうか?度々すみません。

    キャンセル

  • KSwordOfHaste

    KSwordOfHaste

    2018/02/07 17:05

    それはTableViewへ表示すべきモデル群(Modelのリスト)を求めると想定して書いたものです。
    本件ですと引数に渡された検索条件に従ってDBアクセスし、結果セットの各行をModelクラスのインスタンスへと変換しそれをListとして蓄積するイメージです。TableViewは「なんらかのモデルクラスのインスタンス」を各行として表示しますよね?DBアクセスした結果の行をモデルクラスのインスタンスと見做すのはそういう意味合いです。

    キャンセル