第6回 「コマンド」と「MVVMパターン」を理解する:連載:WPF入門(2/3 ページ)
ビューとモデルを疎結合するコマンドを解説。さらに、データ・バインディングとコマンドの仕組みを使ったWPFアプリのアーキテクチャ・パターンMVVMを紹介。
○データ・バインディングでコマンド処理
前回説明したように、WPFにはデータ・バインディングという仕組みがあり、ビューの外部から表示したいデータを与えることができる(外部から与えるデータは、後述する「ビューモデル(ViewModel)」というものになる場合が多い)。
この仕組みを用いることで、Figure 2に示すように、表示したいデータだけでなく、コマンドを外部から与えることも可能である。こうすることで、ビュー内にはデータも処理も書く必要がなくなり、一般にテストが難しくなりがちなビューを極力小さくすることができる。
この方法におけるコマンドの利用手順は以下のようになる。
- ICommandインターフェイスを実装したクラスを作る(=コマンドの実装)
- 外部から与えるデータ(=ビューモデル)により、実装したコマンドをプロパティとして公開する
- 公開したプロパティを、<Button>要素や<MenuItem>要素などのCommandプロパティにデータ・バインディングする
例えば、[OK]ボタンを押すとメッセージボックスを表示するだけの単純なウィンドウを作成するには、List 1のような書き方をする。
<Window x:Class="atmarkit06.CommandWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="コマンド(ビューモデル)" Height="80" Width="100">
<Grid>
<Button Content="OK" Command="{Binding OkCommand}" />
</Grid>
</Window>
using System;
using System.Windows;
using System.Windows.Input;
namespace atmarkit06
{
public partial class CommandWindow : Window
{
public CommandWindow()
{
InitializeComponent();
this.DataContext = new CommandWindowViewModel();
}
}
// 外部から与えるデータ(=ビューモデル)
public class CommandWindowViewModel
{
class OkCommandImpl : ICommand
{
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("コマンドが実行されました。");
}
}
public ICommand OkCommand { get; private set; }
public CommandWindowViewModel() {
this.OkCommand = new OkCommandImpl(); }
}
}
Class CommandWindow
Sub New()
InitializeComponent()
Me.DataContext = New CommandWindowViewModel()
End Sub
End Class
' 外部から与えるデータ(=ビューモデル)
Public Class CommandWindowViewModel
Class OkCommandImpl : Implements ICommand
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
Return True
End Function
Public Event CanExecuteChanged(ByVal sender As Object, ByVal e As EventArgs) Implements ICommand.CanExecuteChanged
Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
MessageBox.Show("コマンドが実行されました。")
End Sub
End Class
Private newOkCommand As ICommand
Public Property OkCommand As ICommand
Get
Return newOkCommand
End Get
Private Set(ByVal value As ICommand)
newOkCommand = value
End Set
End Property
Sub New()
Me.OkCommand = New OkCommandImpl()
End Sub
End Class
この書き方が、後述するMVVMパターンでのコマンド処理の基本となる。ICommandインターフェイスの実装については、本稿後半のMVVMパターンの項で再度説明を行う。
○ルーティング・コマンドを使ったコマンド処理
もう1つはルーティング・コマンド(routed command)を使う方法である。WPF標準で用意されたICommandインターフェイスの1実装として、RoutedCommandクラスというものがあり、このクラスを継承した派生クラスのオブジェクト(=これが、ルーティング・コマンドの実体)を用いる。ちなみに、WPFでは標準で、RoutedCommandクラスを継承したRoutedUICommandクラスが用意されている。
Figure 3に示すように、RoutedCommandクラスでは、Executeメソッドが呼び出された際にExecutedなどのルーティング・イベントを発生させる。このルーティング・イベントをツリー構造の上位のUI要素で拾って処理することになる。
RoutedCommandクラスは、以下のようなルーティング・イベントを発生させる(※トンネル・イベントでは、要素ツリーのルートからイベント発生源となる要素に向かって、要素ツリーを掘り進むようにイベント・ハンドラが呼び出される。トンネル・イベントの名前には原則として「Preview」というプレフィックスが付く。バブル・イベントでは、イベント発生源となる要素からルートに向かって、要素ツリーをたどりながら、浮かび上がるようにイベント・ハンドラが呼び出される)。
- PreveiwExecuted: Excuteメソッドが呼ばれたときに発生するトンネル・イベント。
- Executed: Excuteメソッドが呼ばれたときに発生するバブル・イベント。
- PreviewCanExecute: CanExcuteメソッドが呼ばれたときに発生するトンネル・イベント。
- CanExecute: CanExcuteメソッドが呼ばれたときに発生するバブル・イベント。
ルーティング・コマンドを使う方法でのコマンドの利用手順は以下のようになる。
- RoutedCommandクラスを継承してルーティング・コマンドを自作するか、あるいは、WPF標準で用意されたルーティング・コマンド(=RoutedUICommandクラス)をそのまま利用する
- 自作したルーティング・コマンドは(任意のクラスで)静的プロパティなどで公開しておく、一方、WPF標準のルーティング・コマンドは何もしなくてもよい(詳しくは後述するが、ApplicationCommandsクラスなどで静的プロパティとして公開されているため)
- ルーティング・コマンドの実体(=公開済みの静的プロパティの値)を、<Button>要素や<MenuItem>要素などのCommandプロパティに与える
- ルーティング・コマンド自作した場合、x:Staticマークアップ拡張*1などを用いて値に与える(本稿ではこの手法は割愛)
- 標準のルーティング・コマンドは、例えば「Command="ApplicationCommands.Copy"」*2などと書くだけで利用可能(詳細後述)
- コマンド処理を行う上位のUI要素でCommandBindingsプロパティを設定。その中の<CommandBinding>要素のCommandプロパティにルーティング・コマンドの実体(=公開済みの静的プロパティの値)を指定し、Executedプロパティなどにイベント・ハンドラを登録する(=コマンド・バインディング)
*1 x:Staticマークアップ拡張とは、マークアップ拡張構文の一種で、「<オブジェクト名 プロパティ名="{x:Static プレフィックス:静的な型/オブジェクトの名前.静的なメンバの名前}" .../>」のような記述により、何らかクラスの静的プロパティをXAMLコードで使用するためのもの。
*2 この例で指定しているApplicationCommands.Copy静的プロパティからはRoutedUICommandオブジェクトが得られる。詳細後述。
WPF標準のルーティング・コマンド(=いずれもRoutedUICommandオブジェクト)は、利用目的別にいくつかのクラスにまとめられ(=コマンド・ライブラリ)、各クラスの静的プロパティとして公開されている。具体的には以下のようなクラスがある(EditingCommandsクラスのみ、System.Windows.Documents名前空間。それ以外はいずれもSystem.Windows.Input名前空間)。
- ApplicationCommands: ファイルを開く、保存、ウィンドウを閉じるなど。
- NavigationCommands: ブラウザの戻る、ホームを開く、前項/次項を開くなど。
- ComponentCommands: リスト・アイテムやセルの移動、スクロール、フォーカスの移動など。
- EditingCommands: テキスト編集用のコマンド。
- MediaCommands: メディアの再生、停止、次のトラックへ移動など。
例えば、List 1と同様のもの(=[OK]ボタンを押すとメッセージボックスを表示するプログラム)を作成するには、List 2のような書き方をする。このコードでは、標準のルーティング・コマンドを利用している。
<Window x:Class="atmarkit06.RoutedCommandWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="コマンド(ルーティング・コマンド)"
Height="80" Width="100">
<Window.CommandBindings>
<CommandBinding Command="Properties"
Executed="CommandBinding_Executed" />
</Window.CommandBindings>
<Grid>
<Button Content="OK" Command="Properties" />
</Grid>
</Window>
using System.Windows;
using System.Windows.Input;
namespace atmarkit06
{
public partial class RoutedCommandWindow : Window
{
public RoutedCommandWindow()
{
InitializeComponent();
}
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("コマンドが実行されました。");
}
}
}
Class RoutedCommandWindow
Private Sub CommandBinding_Executed(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs)
MessageBox.Show("コマンドが実行されました。")
End Sub
End Class
ちなみにルーティング・コマンドを自作する場合は、
public static RoutedCommand MyRoutedCmd = new RoutedCommand(); // C#の場合
Public Shared MyRoutedCmd As New RoutedCommand() ' VBの場合
のような1行を、C#/VBコードの任意のpublicクラス内(例えば、上記のRoutedCommandWindowクラス内)に記述して、
XAMLコードのCommandプロパティに、x:Staticマークアップ拡張で、
{x:Static custom:RoutedCommandWindow.MyRoutedCmd}
のように記述すればよい。なお、この「cusutom」というXML名前空間に対応するxmlns:custom属性は、あらかじめXAMLコードのルートにある<Window>要素に追記しておく必要がある。例えば下記のようなコードになる。
xmlns:custom="clr-namespace:atmarkit06"
Commandプロパティに設定しているPropertiesという値は、正確にはApplicationCommandsクラス(System.Windows.Input名前空間)のProperties静的プロパティを指す。従って、本来なら「{x:Static ApplicationCommands.Properties}」と記述するべきだが、WPF標準のルーティング・コマンド群の場合、通常、Command属性への指定では「ApplicationCommands.」などを省略して「Command="Copy"」と簡潔に記述できる。
なお、このApplicationCommands.Properties静的プロパティより得られるルーティング・コマンドは、本来「プロパティ・ウィンドウを開く」という処理を表すものだが、説明の簡素化のために、ここではメッセージボックスの表示で代用している。
ルーティング・コマンドは、ユーザー・コントロールを自作する場合や、コントロール・テンプレートを利用する場合などに特に有効である。連載第4回でスクロールバーへのコントロール・テンプレートの適用の例を挙げたが、テンプレート側でルーティング・コマンドを使ってExecutedなどのルーティング・イベントを発生させ、コントロール側の<CommandBindings>要素でそれを拾ってイベント(=コマンド)処理してもらうというような使い方が可能だ。
●コマンド・ソース
データ・バインディングを使うにしろ、ルーティング・コマンドを使うにしろ、まずはコマンドの発生源(=コマンド・ソース)の設定が必要だ。
WPFでは、コマンド・ソースを実装するためのICommandSourceインターフェイス(System.Windows.Input名前空間)があり、コマンド・ソースとなるUI要素を作る場合には、このインターフェイスを実装する。ICommandSourceインターフェイスは以下のようなメンバを持っている。
- Commandプロパティ: 発生させたいコマンドを設定する。
- CommandParameterプロパティ: ICommandインターフェイスのExecuteメソッドやCanExecuteメソッドの引数として渡されるパラメータを設定する。
- CommandTargetプロパティ: コマンドの実行対象を設定する。例えば、Pasteコマンドが実行される際に、どのUI要素に対して貼り付け処理を行うかなどを指定する。ルーティング・イベントでのみ利用可能。
WPF標準で提供されているコマンド・ソースには、大きく分けてコントロールとインプット・バインディングの2種類ある。
○コントロール
<Button>要素や<MenuItem>要素など、一部のコントロールはICommandSourceインターフェイスを実装している。このため、ボタンをクリックした際などに、Commandプロパティで与えたコマンドが実行される(例:前述の「List 1: データ・バインディングを使ったコマンド処理」や「List 2: ルーティング・コマンドを用いたコマンド処理」で示した、<Button>要素のCommandプロパティを参照)。
ただし、残念ながら、標準では左ボタンのクリック・イベントを拾ってコマンド実行する以外のことができない。例えば、ダブルクリックや、中ボタンのクリックでコマンドを実行したい場合もあるかもしれないが、このような場合、後述するインプット・バインディングを用いることになる(※厳密には、ビヘイビアを使う方法もあるが、Expression Blend SDKが必要になるなど、入門の範囲を超えてしまうため、本連載では割愛する)。
○インプット・バインディング
コントロールのような、目に見えるコマンド・ソース以外に、キーボード・ショートカットや特殊なマウス・ジェスチャーなどの入力系のイベントを拾ってコマンド実行するためのインプット・バインディングという仕組みがある。
UIElementクラス(System.Windows名前空間)(=WPFのUI要素の共通基底クラス)にはInputBindingsというプロパティがあり、これに<KeyBinding>要素、もしくは、<MouseBinding>要素を与えることでインプット・バインディングを行う。
例えば、「[Alt]+[Shift]+[X]キーを押す」や「[Ctrl]キーを押しながらマウス・ホイールを回す」などの特殊な操作に対してコマンドを実行するには、List 3に示すようなXAMLコードを記述する。
<Window x:Class="atmarkit06.CommandSourceWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="インプット・バインディング" Height="300" Width="300">
<Window.InputBindings>
<KeyBinding Gesture="Alt+Shift+X"
Command="{Binding OkCommand}" />
<MouseBinding Gesture="Ctrl+WheelClick"
Command="{Binding OkCommand}" />
</Window.InputBindings>
<Grid>
</Grid>
</Window>
List 1と同様のビューモデルをデータ・バインディングすれば、「[Alt]+[Shift]+[X]キーを押す」「[Ctrl]キーを押しながらマウス・ホイールを回す」という操作を行った際にメッセージボックスが表示される。
<KeyBinding>要素のGestureプロパティには、1つ以上の修飾キーと1つのキーを定義するKeyGestureクラス(System.Windows.Input名前空間)の値を単一の文字列で設定する。文字列からのコンバータが働くので、List 3に示す「Alt+Shift+X」などのような略記が可能になっている。
同様に、<MouseBinding>要素のGestureプロパティには、(必要であれば1つ以上の修飾キーと)1つのマウス操作を定義するMouseGestureクラス(System.Windows.Input名前空間)の値を単一の文字列で、List 3に示す「Ctrl+WheelClick」などのように略記で設定する。
ちなみに、標準で用意されている(ApplicationCommandsクラスなどの静的プロパティとして公開されている)ルーティング・コマンドに対しては、最初からインプット・バインディングが設定されていて、何も記述しなくても、「Ctrl+S」でSaveコマンド、「Ctrl+C」でCopyコマンドなどというように、ルーティング・コマンドが実行される。
前回と今回を通したここまでの説明を踏まえて、次のページではMVVMパターンについて解説する。
Copyright© Digital Advantage Corp. All Rights Reserved.