はじめに
前回やったトリガーはWPF標準なもので、良くも悪くも基本的なやつです。できることが少ないです。例えば、ボタンのマウスオーバー時にプロパティを変更したりしましたが、IsMouseOver
プロパティの変更を監視することで実現していました。
<Style TargetType="Button">
<Setter.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Blue"/>
</Trigger>
</Setter.Triggers>
</Style>
これ、IsMouseOver
プロパティがない場合はどうしましょう?実はEventTrigger
というものがあるんですが、アニメーション専門だったりでちょっとアレです。
インタラクショントリガーというのはトリガーの概念がより一般化されている感じです1。インタラクショントリガーの仕組みは単純で、何らかがトリガーを発火し、何らかがそのトリガーを受けます。そして何らかの応対、つまりアクションを行います。
トリガーの発火は、マウスイベントのようにViewが行うこともあれば、メッセージの様にViewModelが行う事もあります。
図ではインタラクショントリガーとトリガーが区別して書かれていますが、これはメッセージやイベントなどを受ける部分をトリガー、トリガーやアクションなどを総称した機構としてインタラクショントリガーと呼び分けているためです。が、以降は特に区別しません。まあ、受ける部分と行動する部分にわかれていますよ、ということです。
インタラクショントリガー
インタラクショントリガーはスタイルを使ったトリガーとは技術的に別物です。ので、例えば、あるコントロールに対するトリガーを設定することはスタイルを使うと可能ですが、インタラクショントリガーの場合は無理です。インタラクショントリガーは個々のコントロールに直接定義する必要があります。
ここでLivetプロジェクトを作ると自動生成されているMainWindow.xaml
を改めて見てみましょう。
<Window x:Class="LivetWPFApplication1.Views.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:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
xmlns:v="clr-namespace:LivetWPFApplication1.Views"
xmlns:vm="clr-namespace:LivetWPFApplication1.ViewModels"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContentRendered">
<l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="Initialize"/>
</i:EventTrigger>
<i:EventTrigger EventName="Closed">
<l:DataContextDisposeAction/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
</Grid>
</Window>
コメントは消しましたが、それ以外はLivetが自動生成したものまんまです。この<i:Interaction.Triggers>
内がインタラクショントリガーです。まんまですね。
定義されてるインタラクショントリガーは2つです。1つめはContentRendered
イベント時にl:LivetCallMethodAction
というアクションを実行するトリガー。2つめはClosed
イベント時にl:DataContextDisposeAction
アクションを実行するトリガー。
というわけで、i:EventTrigger
はViewのイベントを補足するインタラクショントリガーです。EventName
属性に補足したいイベント名を設定します。
また、このインタラクショントリガーはWindow
要素内に書かれているので、対象はWindow
コントロールです。
アクション
インタラクショントリガーに対応して実行されるものがアクションです。Livetでは色々とアクションが用意されています。l:LivetCallMethodAction
とかl:DataContextDisposeAction
ですね。Livetで定義されているアクションは<l:
と打つと補完されたものがずらーっと出てくるかと思います。
l:LivetCallMethodAction
は特に頻繁に使うアクションで、ViewModelの引数なしメソッドを呼んでくれます。<l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="Initialize"/>
と書かれていた場合、ViewModelのInitialize
メソッドが呼ばれます。あ、MethodTarget="{Binding}"
はあまり気にせずにこういうものだと思っておいてください。一応説明すると、{Binding}
と書くとその要素のDataContext
又は親要素のDataContext
を再帰的に調べて、見つかったものをBindingしてくれます。DataContext
というのは、平たく言えばViewModelのことです。まあ、そういうものです。
l:DataContextDisposeAction
はDataContext
、つまりViewModelのDispose
メソッドを呼びます。これはClosed
イベントで発火するように書かれているので、デフォルトではViewが閉じられるとViewModelもDisposeされます。が、ViewModelを複数のView間で使い回したい場合とかあると思います。その場合はl:DataContextDisposeAction
を削除してください。
ちなみにアクションはユーザー定義可能です。System.Windows.Interactivity.TriggerAction<T>
クラスを継承します。この時のTは、貼っつけたいViewを指定します。例えば、Window
クラスに特化したアクションを定義したい場合はTriggerAction<Window>
を継承します。特にどのコントロールにも依存しない場合はTriggerAction<FrameworkElement>
とかTriggerAction<UIElement>
とかを継承します。コントロールである必要すらない場合はTriggerAction<DependencyProperty>
を継承します。DependencyProperty
とか何?って話ですが、詳しく知りたい方はもっとしっかりWPFを勉強する必要があります。この記事はざっくりなんで説明しません。
TriggerAction<T>
のInvoke
メソッドで実際の処理を書きます。AssociatedObject
でT型のオブジェクトにアクセスできます。
実際どんな感じで書くのかという空気感を知っていただくために、Livetのl:DacaContextDisposeAction
のコードをそのまま貼っつけてみます。あ、Livetはオープンソースで、ライセンスはzlib/libpngです。
using System;
using System.Windows.Interactivity;
using System.Windows;
namespace Livet.Behaviors
{
/// <summary>
/// アタッチしたオブジェクトのDataContextがIDisposableである場合、Disposeします。
/// </summary>
public class DataContextDisposeAction : TriggerAction<FrameworkElement>
{
protected override void Invoke(object parameter)
{
var disposable = AssociatedObject.DataContext as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
}
}
だいぶとそのままな感じですね。こういう感じでよくわからない場合はLivet本体のコードや、Livetを採用していてオープンソースなプロジェクトのコードを参考にすると良いと思われます。Livetを採用しているプロジェクトで言うと、Krile StarryEyesあたりが参考になるかと思います。
ビヘイビア
ビヘイビアは常時発火されてるアクションみたいなものです。簡単に言うと、コードビハインドをカプセル化したものです。
コードビハインドとはコントロールの部分クラスに直接ロジックを書くことでした。で、コードビハインドのデメリットはXAMLで完結しなかったり、複数の振る舞いが1つのコードに同居している複雑性だったりでした。ビヘイビアを使うと振る舞いごとに分離でき、使用はXAMLで明に記述する必要があるのでコードビハインドよりも抽象性を保つことできます。
試しにLivetで提供されているl:WindowCloseCancelBehavior
を使ってみましょう。これはバツボタンを押してもウィンドウを終了させなくするようなビヘイビアです。
<i:Interaction.Behaviors>
<l:WindowCloseCancelBehavior CanClose="False"/>
</i:Interaction.Behaviors>
ビヘイビアは<i:Interaction.Behaviors>
内に記述します。あ、<i:Interaction.Behaviors>
は<Window>
直下に書きましょう。
どうでしょう。バツボタンを押しても終了しないウィンドウができたと思います。わあ、マルウェアみたい。
ビヘイビアも自作できます。System.Windows.Interactivity.Behavior<T>
を継承します。Window
に対するビヘイビアはBehavior<Window>
を継承します。AssociatedObject
でT型のオブジェクトにアクセス可能です。トリガーではないのでInvoke
メソッドはありません。OnAttached
メソッドあたりをオーバーライドし、AssociatedObject
に対してごにょごにょするのが一般的です。
WindowCloseCancelBehavior
クラスの定義はちょっと行数あるので、まあ、GitHub行って見てください。いろいろやってますが、OnAttached
内でAssociatedObject.Closing
イベントを補足してる部分がコア処理です。これもだいぶとそのままですね。
https://github.com/ugaya40/Livet/blob/ver1.0.5/.NET4.0/Livet(.NET4.0)/Behaviors/WindowCloseCancelBehavior.cs
まとめ
実はインタラクショントリガー、アクション、ビヘイビアはLivetの機能というわけではなく、かと言ってWPF標準の機能でもなく、Blend SDKにて提供されている機能です。Blendという超高級WPFデザイナ的なものがあって、そいつが提供している機能をまとめたSDKに同梱されており、LivetはBlend SDKに依存しています。
最初の記事でも書きましたが、LivetはともかくとしてBlend SDKなしのWPF開発はマゾいので最低限Blend SDKは使いましょう。特にインタラクショントリガーとアクションは便利です。
先述した通り、Livetで始めるWPF(ざっくり)入門 その3で触れたメッセージはこのインタラクショントリガーとアクションを使って実装されています。メッセージというのはViewModelから自身のViewに対してインタラクショントリガーで補足可能な信号を送るための機構だったわけですね。
さて、今までの知識を結集すれば、大抵のViewは話半分程度には読めるようになってるんじゃないでしょうか。まあ、もっとちゃんと読めたい場合は普通にWPFとXAMLの勉強をしましょう。
という訳で、Livetで始めるWPF(ざっくり)入門 その3で紹介した、Livetを使用したTwitterクライアントアプリを再掲します。今ならまあまあ読めるかもしれません。読めないかもしれません。まあ、当分はこのアプリのコードが理解できることを目標に勉強してみてはいかがでしょう。
https://github.com/kokudori/Modoki
なんか最終回っぽい締め方ですが、まだ続きます。次回はDispatcherについてです。UIはUIスレッド以外から触ったらダメよ云々というやつです。
-
インタラクショントリガーは元々Silverlightのために開発されました。というのも、Silverlightで提供されてるコントロールは
Trigger
プロパティがないのです。ということでインタラクショントリガーは前回のトリガーとは目的は同じですが仕組みが違います。まあ、細かいことは今はやめましょう。で、普通に便利なんでWPFにも逆輸入された感じです。 ↩