Hatena::ブログ(Diary)

Kazzzの日記 このページをアンテナに追加 RSSフィード

2011-10-13

[][][]自作ビヘイビアを作ろう その2

せっかくなのでよく使いそうなビヘイビアをもう一つ書いてみた。

NotifyOnValidationErrorBehavior クラス
    public class NotifyOnValidationErrorBehavior : Behavior<FrameworkElement>
    {
        private static readonly Brush _errorBrush = new SolidColorBrush(Colors.Red);
        private readonly EventHandler<ValidationErrorEventArgs> _errorHandler;
        private Brush _saveForgroundBrush;
        private Brush _saveBorderBrush;

        public NotifyOnValidationErrorBehavior()
        {
            _errorHandler = (s, e) =>
            {
                var control = e.OriginalSource as Control;

                if (control == null) return;

                if (e.Action == ValidationErrorEventAction.Added)
                {
                    _saveForgroundBrush = control.Foreground;
                    _saveBorderBrush = control.BorderBrush;
                    control.Foreground = _errorBrush;
                    control.BorderBrush = _errorBrush;
                    control.FontWeight = FontWeights.Bold;
                }
                else if (e.Action == ValidationErrorEventAction.Removed)
                {
                    control.Foreground = _saveForgroundBrush;
                    control.BorderBrush = _saveBorderBrush;
                    control.FontWeight = FontWeights.Normal;
                }
            };
        }
        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.BindingValidationError += _errorHandler;
        }
        protected override void OnDetaching()
        {
            AssociatedObject.BindingValidationError -= _errorHandler;
            base.OnDetaching();
        }
    }

前回同様ビヘイビアが関係オブジェクト接続された際にBindingValidationErrorイベントに用意したハンドラを挿しこんでやる訳だが※、やっている処理自体は単純で、

1.ValidationErrorEventArgsクラスのプロパティActionの値がValidationErrorEventAction.Addedであれば、なんらかのエラーが発生していると判断し、エラーが発生したオブジェクト(OriginalSource)がControlであれば、ボーダーと、テキストのブラシの色を赤くして、フォントのウェイトをボールドにする。

2.その逆にValidationErrorEventAction.Removed、つまりエラーが解消されたならばエラー発生時に変更されたブラシとフォントのウェイトを元に戻している。

前回と大きく違うのが型パラメタにTextBoxではなく、ControlでもなくFrameworkElementを指定していること。Silverlightベースプラットホームはイベントがバブルアップするため、全てのコントロールのイベントを上位のコンテナ・コントロールで捕捉することができるため、今回のようなビヘイビアは個々のコントロールに設定する必要が無く、制御下とするコントロールのルートになっているオブジェクト(大抵はGridやStackPanel)に一つだけ配置すれば良いのだ。

XAML

f:id:Kazzz:20111013174808p:image

実行結果

f:id:Kazzz:20111013174845p:image

この描画、どこかで見たことがあるだろうか。そう、Silverlightで標準的に実装されているエラー処理と似たものであるWindows Phone 7は例外を投じることでBindingValidationErrorイベントを発生させることができるものの、それをビューに反映する処理が無いので自ら用意する必要があるのだが、それを簡単に実現できないだろうかと考えてみた。

このビヘイビアを使用するにはコントロール(XAMLエレメント)がBindingValidationErrorイベントを投じることができることが条件だが、それに対応するにはデータバインドを記述するBindingプロパティの拡張マークアップでNotifyOnValidationError=trueを指定するだけで良い。

<TextBox 〜x:Name="textBox1" Text="{Binding Name, Mode=TwoWay, NotifyOnValidationError=True}" 〜>

今回もビヘイビアに少量のコードを書いただけで、あとはBlend4でポトペタするだけである。 簡単且つスマートだ。

※ビヘイビアを処理コンポーネントとして好きな位置に挿入していくのは、ASPECT-JのようなAOPに近い感覚がある。

2011-10-12

[][][]自作ビヘイビアを作ろう

Blend4でプログラミングできる部品として使うビヘイビア。組込みものを使うのは勿論良いのだが、作るのが非常に簡単なので自分の欲しいビヘイビアを思いついたらばんばん作って良いと思う。

以下、ビヘイビアを作る手順。

System.Windows.Interactivity.Behaviorクラス継承クラスを作る

Behaviorクラスは型情報を取るジェネリクスクラスなので、このビヘイビアが接続を前提とするオブジェクト(AssociatedObject)の型を指定して、それを拡張したクラスを用意する。
オーバーライドするメソッドは、ビヘイビアーが関係するオブジェクト(AssociatedObject)に接続された際に呼ばれるOnAttachedメソッドと切断された際に呼ばれるOnDetachingメソッドのたった二つだけである。 素晴らしい。

例としてTextBoxのテキストが変更されたならばバインドされているデータソース更新するビヘイビアを書いてみる。

UpdateSourceOnTextChangedBehaviorクラス
    public class UpdateSourceOnTextChangedBehavior : Behavior<TextBox>
    {
        private readonly TextChangedEventHandler _textChangedEventHandler = 
            (s, e) =>
            {
                var t = s as TextBox;
                // イベントハンドラ内でSourceの更新を行う 
                var binding = t.GetBindingExpression(TextBox.TextProperty);
                if (binding != null)
                {
                    binding.UpdateSource();
                }
            };

        protected override void OnAttached()
        {
            // 接続のタイミングでイベントを登録
            base.OnAttached();
            AssociatedObject.TextChanged += _textChangedEventHandler;
        }
        protected override void OnDetaching()
        {
            // 切り離されるタイミングでイベントの登録解除 
            AssociatedObject.TextChanged -= _textChangedEventHandler;
            base.OnDetaching();
        } 
    }

ビヘイビアクラスのクラス名にはサフィクス"Behavior"を付加するのが良いだろう。
コントロールを関係オブジェクトとして使うビヘイビアを作る場合、このようにビヘイビアのAttach/Detch時になんらかのイベントに接続/切断するケースが多いと思われる。

作ったビヘイビアをBlend4で使う

コンパイルテストの済んだビヘイビアはBlend4でプロジェクト>アセットを介して参照することが出来るので、すぐに使うことができる。
f:id:Kazzz:20111012194029p:image

この例ではtextBox1に作ったビヘイビアをアタッチしている。これで、以降textBox1に入力された文字列が変化した場合にバインドソース(恐らくはViewModelのプロパティ)が更新されるだろう。

また、この時のXAMLは以下のようになる。
f:id:Kazzz:20111012194030p:image
このXAMLを意識する必要は殆どないだろう。

このようにビヘイビアを作るのは従来の関係コントロールを開発するのに比べてずっと簡単だ。
また、ビヘイビアの良い所はそれを追加、削除した所で通常我々が見ているソースコードを一切書き換えないことである。GUI-ソースコード連動型のツールではこうはいかない。