スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: スポンサー広告

MVVMパターンで、イベントをどうにかしよう。

WPFにも当然イベント機能があり、更にルーテッドイベントなんてのもある。
このイベントにハンドラ(リスナ)メソッドを登録すると、イベント発生時にフックされ、登録したメソッドが呼び出される。
ただ、このメソッド、基本的にコードビハインド側に作られる(そこでしか動かない)。
あれ?じゃあ、MVVMパターンでイベントを処理したい時どうすんだ?という話になる。仕事においては、以下の2つで対応した。

1.コマンド(ICommand)と添付プロパティ。
2.Expression BlendのTriggerActionを使う。
1.コマンド(ICommand)と添付プロパティ。
LoadedやUnLoaded等、基本的なイベント発生時に、VM側が用意したコマンドを呼び出せるように添付プロパティを実装する。

1-1.添付プロパティを用意する。
Loadedイベント発生時に呼び出すコマンド用の添付プロパティを実装する。

using System.Windows;
using System.Windows.Input;

namespace TawamureDays {

/// <summary>
/// FrameworkElement用ビヘイビアクラス
/// </summary>
public static class FrameworkElementBehavior {

/// <summary>
/// Loadedイベント時に実行するコマンドオブジェクトを取得します。
/// </summary>
/// <param name="obj">対象オブジェクト(FrameworkElement)</param>
/// <returns>コマンドオブジェクト</returns>
public static ICommand GetOnLoadedCommand(DependencyObject obj) {
return (ICommand)obj.GetValue(OnLoadedCommandProperty);
}

/// <summary>
/// Loadedイベント時に実行するコマンドオブジェクトを設定します。
/// </summary>
/// <param name="obj">対象オブジェクト(FrameworkElement)</param>
/// <param name="value">コマンドオブジェクト</param>
public static void SetOnLoadedCommand(
DependencyObject obj, ICommand value) {
obj.SetValue(OnLoadedCommandProperty, value);
}

/// <summary>Loadedイベント時に実行するコマンドオブジェクト</summary>
public static readonly DependencyProperty OnLoadedCommandProperty =
DependencyProperty.RegisterAttached("OnLoadedCommand",
typeof(ICommand),
typeof(FrameworkElementBehavior),
new UIPropertyMetadata(null,
OnLoadedCommandPropertyChanged));
}
}

1-2.コマンドが設定された(このプロパティが変更された)事を通知してもらうためにメソッドを用意する。

/// <summary>
/// OnLoadedCommandプロパティ値変更イベントハンドラ
/// </summary>
/// <param name="dpObj">対象オブジェクト</param>
/// <param name="e">イベント引数</param>
private static void OnLoadedCommandPropertyChanged(
DependencyObject dpObj,
DependencyPropertyChangedEventArgs e) {
var frwElement = dpObj as FrameworkElement;

if (frwElement == null) {
//この添付プロパティは、FrameworkElementのサブクラスで
//使われる事を前提としています。
//そのため、FrameworkElementを継承していない
//クラスインスタンスは無視されます。
return;
}

if (e.NewValue != null) {
//e.NewValue→新しいICommandオブジェクトが設定された。
if (!frwElement.IsLoaded) {
//IsLoaded = false = まだLoadされていない。
//→Loadedイベントにハンドラメソッドを登録します。
frwElement.Loaded += new RoutedEventHandler(FrwElement_Loaded);
} else {
//IsLoaded = true = 既にLoadされている。
//→Loadedイベント用ハンドラメソッドを実行します。
FrameworkElementBehavior.FrwElement_Loaded(
frwElement,
new RoutedEventArgs {
Source = frwElement, Handled = false});
}
}

return;
}

コメントにも書いているけど、既にLoadされている時はメソッドを即実行する。
IsLoaded=trueになっている後でLoadedイベントに登録しても、実行されないから。
WPFにおいて、各コントロールのLoadedイベントの発生順序は保証されないらしい。テンプレートの適用や、Windowの起動モードでも変わったりする。
1-3.最後にLoadedイベント用のメソッドを実装する。

/// <summary>
/// Loadedイベントハンドラ用メソッド
/// </summary>
/// <param name="sender">イベント発生元</param>
/// <param name="e">イベント引数</param>
private static void FrwElement_Loaded(object sender, RoutedEventArgs e) {
var frwElement = sender as FrameworkElement;
//登録したメソッドは削除しないと、メモリリークの元になる。
frwElement.Loaded -= new RoutedEventHandler(FrwElement_Loaded);

var command = FrameworkElementBehavior.GetOnLoadedCommand(frwElement);

//実行可能であれば、コマンドを実行する。
if (command != null && command.CanExecute(e)) {
command.Execute(e);
}

return;
}

このメソッドで初めて、コマンドが実装される。
さて、使ってみよう…。あ、ICommandの実装クラス作ってなかった。Orz
とりあえず、使い方。

<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:nfc="http://nichia.jp/sys/foundation/component/gui"
xmlns:local="clr-namespace:TawamureDays"
Title="MainWindow" Height="213" Width="385">
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Left"
local:FrameworkElementBehavior.OnLoadedCommand="{Binding OnLoadedCommand}">
</StackPanel>
</Window>

ちゃんとバインドできれば、(StackPanel)のLoadイベント時にコマンドが呼ばれるはず。

2.Expression BlendのTriggerActionを使う。
1のやり方は非常に融通が利くんだけど、そのイベント毎に実装する必要がある。3,4個くらいならどうとでもなるんだけど、それ以上になると大変になるし、コード量も無駄に大きくなる。
そんな時には、Expression Blendのライブラリである、System.Windows.Intaractity.dllにある、TriggerActionクラスを使う。
別にオリジナルではなく、↓のHPに載っている。
Writing custom TriggerAction using Interaction to enhance MVVM
上の記事を参考に、指定のイベント発生時にセットしたコマンドを実行してもらう。
多くの実装を必要としない代わりに、XAMLへの記述がめんどくさいかな。
まあ、それもテンプレートに実装すれば、どうにかなる話かもしれない。試してないけど。
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0


この記事へのコメント

コメントの投稿

非公開コメント


サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。