本文へスキップ
BecomeCoder

Java MVVMコース · 第7章 View ― JavaFXへ接続する · レッスン26

Viewの役割とFXML ― Controllerを薄く保つ

ローカル実施

導入

TaskListViewModelは既に「一覧を持つ」「タイトルを入力できる」「追加を実行できる」という振る舞いを、画面なしでテストだけを頼りに完成させています。残っているのは、このViewModelを実際のJavaFXの画面(View)につなぐことだけです。MVVMのViewは、それ自体は驚くほど何もしません。

説明

flowchart LR
    U["ユーザー操作"] --> C["Controller<br/>(@FXMLで部品を受け取る)"]
    C <-->|"手で配線する"| VM["TaskListViewModel"]
    VM --> SVC["TaskService"]
    SVC --> REPO["TaskRepository"]

FXMLは画面の見た目(部品の配置)だけを宣言します。

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="app.view.MainViewController"
      spacing="8" prefWidth="420">
    <HBox spacing="8">
        <TextField fx:id="titleField" promptText="新しいタスク" HBox.hgrow="ALWAYS" />
        <Button fx:id="addButton" text="追加" />
    </HBox>
    <ListView fx:id="taskListView" prefHeight="240" />
    <Label fx:id="remainingLabel" text="残り 0 件" />
</VBox>

Controller側は、レッスン4で見た@FXMLで各部品を受け取ったあと、TaskListViewModelとの配線を自分で書きます。

package app.view;

import javafx.fxml.FXML;
import javafx.scene.control.*;
import app.viewmodel.TaskListViewModel;

public class MainViewController {
    @FXML private TextField titleField;
    @FXML private Button addButton;
    @FXML private ListView<Object> taskListView;
    @FXML private Label remainingLabel;

    private TaskListViewModel viewModel;

    public void setViewModel(TaskListViewModel viewModel) {
        this.viewModel = viewModel;
        // ViewModelの変更を購読し、変わるたびに画面を描き直す
        viewModel.addPropertyChangedListener(name -> refresh());
        refresh();
    }

    @FXML
    private void initialize() {
        // クリックをCommandの実行に変換するだけ。業務ロジックはここに書かない
        addButton.setOnAction(e -> {
            viewModel.setNewTitle(titleField.getText());
            viewModel.getAddCommand().execute();
        });
    }

    private void refresh() {
        taskListView.getItems().setAll(viewModel.getTasks());
        remainingLabel.setText("残り " + viewModel.getRemainingCount() + " 件");
        titleField.setText(viewModel.getNewTitle());
        addButton.setDisable(!viewModel.getAddCommand().canExecute());
    }
}
  • コントローラに書くのは「部品からViewModelへ値を渡す」「ViewModelの変更通知を受けて画面を描き直す」という配線だけ。タイトルの検証や一覧の追加といった業務ロジックは一切書かない
  • viewModel.addPropertyChangedListener(...)が、第6章ViewModelBaseが用意した通知の仕組みを実際に使う場面。ViewModelがraisePropertyChanged(...)を呼ぶたびに、Controllerのrefresh()が実行され、画面全体が最新の状態に描き直される
  • ここでの依存の組み立て方(setViewModel(...)にViewModelのインスタンスを渡す)は、次の章で学ぶComposition Rootの考え方そのもの。今は「起動時にViewModelを1つ作ってControllerへ渡す」という最小形で十分

まとめ

  • Viewの仕事は「表示する」ことと「ユーザー操作をViewModelへ伝える」ことだけ。業務ロジックは一切持たない
  • JavaFXにはWPFの{Binding}のような自動配線がないため、Controllerがinitialize()と変更通知の購読で明示的につなぐ
  • 次回からは、この配線が受け取ることになるViewModel側の振る舞いを、テストで1つずつ固めていく

次回: 一覧を表示するために、ViewModelのgetTasks()が何を返すべきかを確認します。