本文へスキップ
BecomeCoder

C# MVVMコース · 第7章 View ― WPFへ接続する · レッスン28

完了・削除 ― CanExecuteでボタンの活性/非活性を制御

ブラウザで完結

導入

一覧が空のときに「削除」ボタンが押せてしまうのはおかしな話です。第6章で学んだCanExecuteを使い、一覧に何かあるときだけ操作ボタンを有効にしてみましょう。

実務との違い(重要): 本来は一覧の行ごとに「この行を完了にする」「この行を削除する」ボタンを置き、CommandParameterで対象の行をCommandに渡します。今回使っている<mvvm-runner>は教材用のサブセットのためCommandParameterに対応していません。そこでこのレッスンでは、「先頭の1件を完了にする」「すべて削除する」という単純な操作で、CanExecuteによるボタンの活性/非活性制御そのものを体験します。行ごとの操作を本物のWPFで試したい場合はC# WPFコースのDataGrid回を参照してください。

説明

flowchart TB
    T["Tasks(一覧)"] -->|"Tasks.Any()"| CE{"canExecute"}
    CE -->|"true(1件以上)"| ON["ボタンが押せる"]
    CE -->|"false(0件)"| OFF["ボタンがグレーアウト"]
<Window Title="タスク一覧" Width="360" Height="360">
  <StackPanel Margin="16">
    <TextBlock Text="タスク一覧" FontWeight="Bold" FontSize="16" />
    <TextBox Text="{Binding NewTaskTitle, Mode=TwoWay}" Margin="0,10" />
    <Button Content="追加" Command="{Binding AddCommand}" Width="80" />
    <ListBox ItemsSource="{Binding Tasks}" DisplayMemberPath="Title" Height="120" Margin="0,10" />
    <StackPanel Orientation="Horizontal" Margin="0,8,0,0">
      <Button Content="先頭を完了" Command="{Binding CompleteCommand}" Width="100" Margin="0,0,8,0" />
      <Button Content="すべて削除" Command="{Binding DeleteAllCommand}" Width="100" />
    </StackPanel>
  </StackPanel>
</Window>
public record TaskDto(string Title, bool IsCompleted);

public class TaskListViewModel
{
    public string NewTaskTitle { get; set; } = "";
    public ObservableCollection<TaskDto> Tasks { get; set; } = new();

    public ICommand AddCommand => new RelayCommand(
        () => { Tasks.Add(new TaskDto(NewTaskTitle, false)); NewTaskTitle = ""; },
        () => !string.IsNullOrWhiteSpace(NewTaskTitle)
    );

    public ICommand CompleteCommand => new RelayCommand(
        () => { Tasks.RemoveAt(0); },
        () => Tasks.Any()
    );

    public ICommand DeleteAllCommand => new RelayCommand(
        () => { Tasks.Clear(); },
        () => Tasks.Any()
    );
}
  • CompleteCommandDeleteAllCommandはどちらもcanExecute() => Tasks.Any()を渡している。**一覧が空のときはfalse**になり、ボタンが自動的にグレーアウトする
  • Tasks.RemoveAt(0)は先頭の1件を取り除く、Tasks.Clear()はすべて取り除く。どちらもObservableCollectionなので実行のたびに一覧の表示も自動で更新される
  • TaskDtoIsCompletedはこのランナーの制約上、要素ごとに書き換える手段がないため、このレッスンでは「完了にする」を「一覧から取り除く」(=第5章GetActiveTasksが完了済みを除外するのと同じ考え方)として簡略化している

やってみよう

  1. 一覧が空の状態では「先頭を完了」「すべて削除」がどちらも押せないことを確認する
  2. いくつかタスクを追加する
  3. 「先頭を完了」を押すたびに、一番上の項目が消えることを確認する
  4. 「すべて削除」を押すと、一覧が空になり、両方のボタンが再びグレーアウトすることを確認する

確認ポイント

  • 一覧が空のとき、「先頭を完了」「すべて削除」の両方が押せないこと
  • 1件以上あるとき、両方が押せるようになること
  • 「先頭を完了」で先頭の1件だけが消え、「すべて削除」で全件消えること

まとめ

  • canExecuteは「今、実行してよい条件」を表す。Tasks.Any()のように一覧の状態を条件にできる
  • 条件を満たさない間、Viewは自動でボタンを非活性にする(IsEnabledを自分で書かない)
  • 実務では行ごとの対象指定(CommandParameter)が必要になる場面があるが、このランナーでは非対応。考え方(CanExecuteでの活性制御)自体は同じ

次回: RemainingCountのような計算プロパティを画面に表示します。

実際に動かしてみよう

上がXAML(View)、真ん中がViewModelのC#風コード、右がプレビューです。ViewModelのプロパティやRelayCommandを書き換えると、プレビュー側の一覧・入力・ボタンの有効/無効にその場で反映されます(本物のC#コンパイルではなく、単純なプロパティ・計算プロパティ・コマンドの実行/CanExecuteを再現した学習用エンジンです。複雑なLINQや独自クラスのメソッド呼び出しなどは対象外です)。本文のコードを書き換えて、Viewとの連動を確かめながら進めましょう。

MVVM (XAML + ViewModel) プレビュー

書いた XAML(View) と ViewModel(C#風) をその場で連携させて動かす学習用プレビューを読み込みます(本物のWPF/MVVMではなく、単純な自動プロパティ・計算プロパティ(=>)・ICommand(RelayCommandのexecute/CanExecute)・{Binding}の双方向バインディングを再現したものです。独自クラスのメソッド呼び出し・複雑なLINQ・非同期処理・DI・CommandParameterは実行されません)。
スクロールして表示された時点でも自動で読み込まれます。