忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

DelegateCommandパターン。

 忘れては思い出しまた忘れないうちにメモしておこうシリーズ第二弾です。
今回はWPFで使われるコマンドのある種の使い方についてメモしておこうと思います。
DelegateCommandパターンです。

WPFに限った話ではありませんが、ある操作をするとき、ボタンをクリックしたり右クリックしてからメニューをクリックしたりあるいはキーボードのショートカットを使ったり複数の方法があるのが一般的です。
それぞれに別のイベントハンドラを使うのは美しくありません。
それに操作が実行不可能なときボタンを無効状態にしたりしますが、それも煩雑でめんどくさいものです。
そこで全部まとめてしまえるのがコマンドです。
具体的にはテキストのカットやペースト、ファイルを開くといった動作などいろいろなコマンドがあります。
コマンドが実行不可能なときには、コマンドをデータバインディングしたUIは自動的に使えなくなります。

コマンドはユーザー定義が可能です。
ICommandから継承するのです。
しかし継承したクラスにハードコードすると融通がきかないので、デリゲートを引数にとって柔軟性を持たせることがあります。
それがDelegateCommandパターンです。

なんのことだか分かりにくいのでコードを書いておきます。
かなり短くしました。

using System.Windows.Input;

namespace WpfApplication1
{
    public class TestViewModel
    {
        public DelegateCommand SqrtCommand { get; private set; }
        public double Number { get; set; }

        public TestViewModel()
        {
            SqrtCommand = new DelegateCommand(
                () => { System.Windows.MessageBox.Show(System.Math.Sqrt(Number).ToString()); },
                () => Number >= 0
                );
        }
    }

    public class DelegateCommand : ICommand
    {
        System.Action execute;
        System.Func<bool> canExecute;

        public bool CanExecute(object parameter)
        {
            return canExecute();
        }

        public event System.EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameter)
        {
            execute();
        }

        public DelegateCommand(System.Action execute, System.Func<bool> canExecute)
        {
            this.execute = execute;
            this.canExecute = canExecute;
        }
    }
}

これがViewModelのクラスです。
√を計算します。
たとえばNumberが9だったら3を、4だったら2を表示するわけですね。

その下にDelegateCommandクラスがあります。
今回の主役です。
これはユーザー定義のコマンドで、コマンドが「実行可能かどうか」と「何を実行するか」を司るのです。
これを次にデータバインドでボタンに突っ込むわけです。

XAMLのコードはこうなります。

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:src="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="150" Width="225">
    <Window.Resources>
        <src:TestViewModel x:Key="viewModel"/>
    </Window.Resources>
    
    <StackPanel DataContext="{StaticResource viewModel}">
        <TextBox Text="{Binding Number}"/>
        <Button Content="sqrt" Command="{Binding SqrtCommand}"/>
    </StackPanel>
</Window>


スタックパネルで2つのコントロールを表示しています。
テキストボックスと、その下にボタンです。
テキストボックスには√する数字が入ります。
ボタンをクリックするとコマンドが実行されるようになっています。

実行するとこんな感じです。

wpfDelegateCommandSample.jpg

さてテキストボックスに数字の4を入れてみましょう。
そしてsqrtボタンをクリックしてください。
するとこうなります:

wpfDelegateCommandSampleSqrt4.jpg

√4 = 2

です。
予想どうりの結果ですね。

さて、今度はテキストボックスに-4を入れてみましょう。
そしてsqrtボタンをクリックしてください。
すると・・・

wpfDelegateCommandSampleSqrt-4.jpg

なんと!!
ボタンがかってに無効状態になりました!
どんなに頑張っても押せません。

コマンドオブジェクトのCanExecuteプロパティがfalseなので、それをボタンが読み取って無効状態になってくれたのです。

ここではコマンドを実行するUIはボタンひとつだけですが、例えばメニューで実行出来るようにしても同じことです。
メニューとボタン両方が無効状態になってくれます。
-4などという値になったときには。

ちなみにこのサンプルプログラム、あまりに単純化しすぎてボタンが無効状態から戻ってくれません。
この問題はクリック出来るUIの数の少なさに由来します。
しかし普通のアプリケーションレベルのUIでは、戻ってくれます。
sqrtボタンの下にもう一つ別のボタンを作り、それをクリックすると、テキストボックスの中身が0以上なら、無効化状態から戻ってくれます。













拍手[1回]

PR