Windows Presentation Foundation (WPF) の重要な機能の一つであるコマンドについて学びます。
これまでは、例えばボタンをクリックしたときの処理をUI側のコード(C#コードビハインド)に直接書いていましたが、今回はそれを別クラスへ分離します。
以下の方に役立つ内容となっています。
- MVVMパターンの準備として、コマンドパターンを習得したい方
- ICommandの仕組みを理解したい方
ボタンのクリック処理を従来のClickイベントではなく、ICommandを用いて実装することで、役割ごとにクラスをきれいに分離することができます。
この記事では、カウンターアプリを通じて、ICommandインターフェイスの基本的な使い方とボタンの自動有効・無効制御を習得します。

前回までで学んだデータバインディングと今回のコマンドを組み合わせることで、MVVMパターンの基礎となる2本柱が完成します!
データバインディングについては以下の記事を参考にしてください。



演習のコード一式はGitHubに置いてあります。
講義:コマンドの仕組み
従来のイベント処理の問題点
従来は以下のように、UIのコードビハインドにイベント処理を直接書いていました。
UIコード(XAML)
...
<Button Content="+" Click="OnIncrement"/>
...
UIコード(C#コードビハインド)
private void OnIncrement(object sender, RoutedEventArgs e)
{
Count++;
}
これだと、「UIの表示」と「アプリのロジック(カウンターのインクリメント)」という異なる役割をもつ内容が、すべてUIクラスに書かれてしまっているという問題があります。
コマンドとは?
「コマンド」の仕組みは以下のように、「UIコード」と「データやロジックのコード」をきれいに分離し、コードを役割ごとにきれいに整理するための仕組みとなります。

このように整理することで、コードの可読性も上がりますし、「データやロジック」のコードを別の画面や機能で再利用しやすくなります。
前回までで学んだ「データバインディング」も、役割でクラスを分離するための仕組みなのかな?
その通りです!
データバインディングについて、双方向同期のメリットを中心に説明してきましたが、「UI」と「データ」のコードを分離する役割もあります。
データバインディングとコマンドによって以下のように「UI」と「データやロジック」の完全分離が実現します。以下のようなイメージです。

この役割によってクラスを分離する考え方は、MVVMパターンの基礎にもなっています。
まさに、データバインディングとコマンドがMVVMパターン実現の2本柱というわけですね。
ICommandインターフェイスとは?
WPFにおけるコマンドは、ICommand
インターフェイスによって実現されます。このインターフェイスは以下の3つのメンバを持ちます。
public interface ICommand
{
bool CanExecute(object? parameter);
void Execute(object? parameter);
event EventHandler? CanExecuteChanged;
}
それぞれ以下の役割があります。
- CanExecute: そのコマンドが現在実行可能かどうかを返す
(例:カウンターが0の時はマイナスボタンを無効にする) - Execute: 実際のコマンド処理を実行
(例:カウンターをインクリメント・デクリメント) - CanExecuteChanged: 実行可否の状態が変わったときに発生するイベント
Microsoft LearnのICommandのリファレンスも参考にしてください。
コマンドとボタンの自動制御
WPFのボタンは、バインドされたコマンドのCanExecute
メソッドの戻り値に応じて、自動的に有効・無効が切り替わります。
例えば、以下のようにデータクラスでICommandプロパティを公開します。
public class Counter : INotifyPropertyChanged
{
public int Count{
...
}
public ICommand DecrementCommand { get; }
public Counter()
{
// Count > 0の時のみ実行可能なコマンドを作成
// Executeとして「_ => Count--」を設定
// CanExecuteとして「_ => Count > 0」を設定
// (SimpleCommandはICommandを実装している。具体的な実装方法は演習で…)
DecrementCommand = new SimpleCommand(_ => Count--, _ => Count > 0);
}
}
そして、事前にDataContextでCounterインスタンスをUIへ紐づけたうえで、XAMLでこのコマンドをバインディングします。
<!-- DecrementCommandのCanExecuteがfalseを返すと、ボタンが自動的に無効化される -->
<Button Content="-" Command="{Binding DecrementCommand}"/>
この仕組みにより、「カウンターが0以下の時はマイナスボタンを押せなくする」といった制御を、UI側のコードを書くことなく実現できます。
演習:カウンターアプリでICommandを実装
作成するアプリの概要
以下の機能を持つカウンターアプリを作成します。
- カウンター値の表示(データバインディング)
- プラスボタン:カウンターをインクリメント
- マイナスボタン:カウンターをデクリメント(0以下で自動無効化)
以下の手順で実装します。
- 手順1:SimpleCommand.cs:
ICommand
インターフェイスの実装クラス - 手順2:Counter.cs: カウンターのデータとコマンドを管理するクラス
- 手順3:MainWindow.xaml、MainWindows.xaml.cs: UI部分
WPFの新規プロジェクトを作成し、そこへ「SimpleCommand.cs」、「Counter.cs」ファイルをそれぞれ追加しましょう。以下の構成になります。

ソリューションエクスプローラで「WpfApp1」上で右クリックメニューを開き、「追加>新しい項目」で「クラス」を選び追加します。
手順1:SimpleCommand.cs
SimpleCommand.csを以下のように作成しましょう。
(今回、もともとひな型にある不要な名前空間のusingについては削除しています)
これは、カウンターの加算(Increment)・減算(Decrement)のコマンドを作るための汎用的なコマンドクラスです。
using System.Windows.Input;
namespace WpfApp1
{
public class SimpleCommand : ICommand
{
private readonly Action<object?> _execute;
private readonly Predicate<object?>? _canExecute;
public SimpleCommand(Action<object?> execute, Predicate<object?>? canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object? parameter) => _canExecute?.Invoke(parameter) ?? true;
public void Execute(object? parameter) => _execute(parameter);
public event EventHandler? CanExecuteChanged;
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
このコードのポイントは以下です。
- コンストラクタで実行処理(
execute
)と実行可否判定(canExecute
)を受け取る CanExecute
は判定関数があれば実行し、なければ常にtrue
を返すRaiseCanExecuteChanged
メソッドでUI側に実行可否の再評価を要求
手順2:Counter.cs
Counter.csを以下のように作成しましょう。さきほどのSimpleCommandクラスを使って、Increment・Decrementコマンドを作ります。
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace WpfApp1
{
public class Counter : INotifyPropertyChanged
{
private int _count;
public int Count
{
get => _count;
set
{
if (_count != value)
{
_count = value;
OnPropertyChanged();
}
}
}
public ICommand IncrementCommand => _incrementCommand;
public ICommand DecrementCommand => _decrementCommand;
private readonly SimpleCommand _incrementCommand;
private readonly SimpleCommand _decrementCommand;
public Counter()
{
_incrementCommand = new SimpleCommand(_ => Count++);
_decrementCommand = new SimpleCommand(_ => Count--, _ => Count > 0);
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
if (propertyName == nameof(Count))
{
_decrementCommand.RaiseCanExecuteChanged();
}
}
}
}
このコードのポイントは以下です。
- 前回学んだ
INotifyPropertyChanged
を実装してデータバインディングに対応 IncrementCommand
: 常に実行可能なプラスコマンドDecrementCommand
: 「Count > 0」の時のみ実行可能なマイナスコマンドCount
プロパティ変更時にRaiseCanExecuteChanged()
を呼び出し、UI側へ「CanExecute結果が変わった可能性あるよ」と通知
RaiseCanExecuteChanged()
を呼び出すと、SimpleCommandクラスで実装した「CanExecuteChanged」を呼び出します。
public class SimpleCommand : ICommand
{
...
public event EventHandler? CanExecuteChanged;
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
DataContextとCounterを紐づけると、CanExecuteChangedはWPFのUIフレームワーク側で自動で購読します。なので、UIへCanExecuteの状態変更を通知できるわけです。
この仕組みは、INotifyPropertyChangedのPropertyChangedイベントと同じですね。
手順3:MainWindow.xaml(と、MainWindow.xaml.cs)
MainWindow.xamlを以下のように修正します。
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Margin="20" VerticalAlignment="Center">
<!-- カウンター表示 -->
<TextBlock Text="{Binding Count, StringFormat='Count: {0}'}"
FontSize="24"
HorizontalAlignment="Center"
Margin="0,0,0,20"/>
<!-- ボタン群 -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="-"
Command="{Binding DecrementCommand}"
Width="40" Height="30"/>
<Button Content="+"
Command="{Binding IncrementCommand}"
Width="40" Height="30"/>
</StackPanel>
<!-- 説明テキスト -->
<TextBlock Text="「-」ボタンは0以下で自動無効化されます"
HorizontalAlignment="Center"
Margin="0,15,0,0" />
</StackPanel>
</Window>
ポイントは以下です。
- 従来の
Click
イベントの代わりにCommand
プロパティを使用 Command="{Binding DecrementCommand}"
でコマンドをバインディング- ボタンの有効・無効制御はWPFが自動的に行う
最後に、もう1点だけUI側のコードビハインド(MainWindow.xaml.cs)でDataContextへCounterインスタンスを紐づけるコードを追記します。
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new Counter(); //ここだけ追加
}
}
}
アプリを実行
アプリをデバッグ実行して、以下の動作を確認してみましょう。

プラス・マイナスボタンで値が増減します。加えて、カウンターが0になったとき、マイナスボタンが自動的に無効化されます。
マイナスボタンが自動的にグレーアウトするのが面白い!これがコマンドの自動制御機能だね。
そうです!CanExecute
メソッドがfalse
を返すと、WPFが自動的にボタンを無効化してくれます。
今回実装した「SimpleCommandクラス」は汎用性が高いので、カウンターの加減操作以外にも、様々なコマンドを実装するときに共通して使える部品です。
補足:データバインディングやコマンドをより簡潔に書く方法
今回の記事では、ICommandを自分で実装して「SimpleCommandクラス」を用意しました。
学習目的としては、ICommandの仕組みを理解するのに最適ですが、実務として「毎回このようなボイラープレートコードを書くのは大変そうだな」と感じた方も多いと思います。
そのため、CommunityToolkit.Mvvm(旧Microsoft.Toolkit.Mvvm)やPrismといった外部ライブラリでは、あらかじめ汎用的なコマンドクラスが用意されています。
例として、CommunityToolkit.Mvvmの雰囲気を少しだけみてみましょう。
今回作成した SimpleCommand
相当のクラスは、RelayCommandとして用意されているので、それを使うだけでOKです。
public Counter()
{
...
IncrementCommand = new RelayCommand(() => Count++);
DecrementCommand = new RelayCommand(() => Count--, () => Count > 0);
...
}
あと実は、データバインディングについても、より簡潔に記述できます。例えば、プロパティについて以下のような簡潔な記述で行えます。
public partial class Counter : ObservableObject
{
[ObservableProperty]
private int count;
...
元々はget,setとかOnPropertyChangedの呼び出しとか、こちゃごちゃあったのが、すごくスッキリしてる!
CommunityToolkit.Mvvmなど使うとこのようにかなり簡潔に書けます。実務では必須といえます。
ただ、これらの裏側の仕組みを知ることも重要なため、本シリーズでは最初にこれらのライブラリを使わず、WPFの素の実装方法を紹介しています。
CommunityToolkit.Mvvmについては、MVVMパターンまで一通り学習した後に改めて紹介しますので、楽しみにしていてください。
まとめ
本記事では、WPFにおけるコマンド機能の実装について学びました。
コマンドを使ったアプリには以下の特徴と利点があります。
- 役割の分離:UIコードとアプリロジックを分離し、コードの可読性や再利用性が向上
- 有効化の自動制御:CanExecuteメソッドによってボタンの有効・無効が自動で切り替わり、UI状態の管理を簡易化
WPFではICommandインターフェイスを実装することで実現できます。
カウンターアプリの演習を通じて、SimpleCommandクラスの作成からボタンのコマンドバインディングまで、実際のアプリへコマンド機能を統合する方法を学びました。
また、データバインディング・コマンドのコードはCommunityToolkit.Mvvmなど、より簡潔な記述にする方法もあることを学びました。
データバインディングと今回のコマンドで、MVVMパターンの2本柱が揃いました!次回からいよいよMVVMパターンです。
引き続き、一緒にC# WPFアプリ開発を一緒に学んでいきましょう!