WPF ViewModelでLOB開発 Vol 02 - ルーティング イベント

- - posted in wpf | Comments

最初に簡単にItemTemplatesについて紹介して、ルーティング イベントとビューモデルの紐付おまじないコードの解決を図りたいと思います。

たとえばVisualStudio2013をインストールすると、 C:\Users\“username”\Documents\Visual Studio 2013\Templates\ というフォルダができていると思います。

ここに以下の作業を実施します。

  • 【ItemTemplates】フォルダ - 【Visual C#】フォルダの中に新規フォルダ作成(uEN Laboratory)
  • このZIPファイルを先ほどのフォルダに入れる

そうすると、クラスの新規追加時に雛形を提供することができます。

wpf-02-01

ViewクラスとVeiwModelクラスがセットで追加されます。

wpf-02-02

名前空間などはテンプレートがサポートします。

wpf-02-03 wpf-02-04

前回はベースクラス名を変えたり、Xamlに名前空間をインポートしたりとかなり手間だったのですが、この機能を利用すると毎回同じ作業を実施しなくて済むようになります。 WPFでの開発生産性を上げる上ではとても有効なので、ぜひ利用してみてください。

カスタマイズするには、さっきのZIPファイルやMSDNを参考にすると簡単にできるのでチェックしてみてください。

ルーティング イベント

ボタン押下などのイベント処理を簡単に実装するためには、またおまじないコードが必要になります。
Model-View-ViewModel デザイン パターンを利用しない場合は、Xaml(ザムル)上でダブルクリックすると、Xaml.CSファイルのコードビハインドにクリックイベントが紐付けられます。

wpf-02-05 wpf-02-06

ただ、これでは画面をXamlで作成するという難易度だけ上がって、Windows Formとあまり変わりません。 そこで、このイベント処理もバインディングできればと思います。

上記の実装ですが、明示的にRoutedEventを利用してコードビハインド上で記述することもできます。

1
SampleButton.AddHandler(Button.ClickEvent, new RoutedEventHandler(SampleButton_Click));

Button.ClickEvent は Buttonクラスに静的に定義され、開発者が利用できるように提供されています。

この仕組みを利用することで、指定したオブジェクトが指定したRoutedEventを実行する際の処理をどこか別の場所(具体的にはViewModel)に定義することができるようになります。

Proxyパターンを利用したラッパー経由でViewModelのメソッドに割り付けるようなクラスを準備すれば十分ではないでしょうか。 前回、バインドをタイプセーフに行うためにラムダ式でプロパティ名を指定したように、このイベントの紐付けもラムダ式で行うようにしたいと思います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class RoutedEventBehavior
{
    public RoutedEvent RoutedEvent { get; set; }
    public object ViewModel { get; set; }
    public DependencyObject Element { get; set; }
    public LambdaExpression LambdaExpression { get; set; }
    protected Action Method { get; set; }
    protected Action<RoutedEventArgs> ArgsMethod { get; set; }
    public virtual void Ensure()
    {
        var uiElement = Element as UIElement;
        if (uiElement == null)
            return;

        if (Method == null && ArgsMethod == null)
        {
            var compile = LambdaExpression.Compile().DynamicInvoke(ViewModel);
            Method = compile as Action;
            ArgsMethod = compile as Action<RoutedEventArgs>;
        }
        uiElement.AddHandler(RoutedEvent, new RoutedEventHandler(OnEventInternal));
    }
    protected virtual void OnEventInternal(object sender, RoutedEventArgs e)
    {
        var currentCursor = Mouse.OverrideCursor;
        Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait;
        try
        {
            if (Method != null)
                Method.Invoke();

            if (ArgsMethod != null)
                ArgsMethod.Invoke(e);
        }
        catch (Exception)
        {
            //エラー時のアプリケーション ポリシー
            throw;
        }
        finally
        {
            Mouse.OverrideCursor = currentCursor;
        }
    }
}

サンプル程度の実装ですが、こうすることで、ボタン押下時のマウスカーソルの自動Wait化やエラー時のロギングなどを開発者が実装する必要がなくなります。 ログ出力には、TraceSourceクラスを利用すると便利なので、別の機会に紹介します。

また、開発者が直接このようなクラスを生成する手間は省きたいです。
たとえばExpression Builderパターンなんてプログラミング デザイン パターンを採用してみれば、開発がしやすく可読性があがるかもしれません。

画面上にテキストボックス、ボタンが配置されている場合のコーディング イメージとしてはこのような形でしょうか?

wpf-02-07

コントロールを指定して、依存関係プロパティないしルーティング イベントとViewModelのメソッドをタイプセーフに紐付けるビルダーを利用してバインドするという形です。

このコードを記述して実際に動かすとこのようになります。

wpf-02-08

テキストボックスに文字を入力し、ボタンをクリックするとViewModel上のSampleActionメソッドが呼び出され、バインドしているSampleTextプロパティから文字を取得してメッセージ表示しています。

ツールレベルならコレでも十分かもしれませんが、実際のLOB開発時には画面に表示する際に変換(IValueConverter)する、入力制限する、他にも様々な作業が発生するので、次回は少しこのバインディングについて掘り下げていきたいと思います。

余談ですが、ButtonクラスはCommandというプロパティを提供しています。
こちらを利用することでもボタンのクリックイベント処理を実装することができるのですが、この仕組みはLOBアプリのとあるボタンの押下処理とは意味合いが少し異なることを目的にしています。

LOBアプリでのイベント処理はルーティング イベントを利用するほうがシンプルです。 コマンドについては別の機会で紹介できればと思います。

今回のサンプルはコチラ


Comments