Edited at

Livetで始めるWPF(ざっくり)入門 その6

More than 3 years have passed since last update.


はじめに

前回やったトリガーはWPF標準なもので、良くも悪くも基本的なやつです。できることが少ないです。例えば、ボタンのマウスオーバー時にプロパティを変更したりしましたが、IsMouseOverプロパティの変更を監視することで実現していました。

<Style TargetType="Button">

<Setter.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Blue"/>
</Trigger>
</Setter.Triggers>
</Style>

これ、IsMouseOverプロパティがない場合はどうしましょう?実はEventTriggerというものがあるんですが、アニメーション専門だったりでちょっとアレです。

インタラクショントリガーというのはトリガーの概念がより一般化されている感じです1。インタラクショントリガーの仕組みは単純で、何らかがトリガーを発火し、何らかがそのトリガーを受けます。そして何らかの応対、つまりアクションを行います。

インタラクショントリガー.png

トリガーの発火は、マウスイベントのようにViewが行うこともあれば、メッセージの様にViewModelが行う事もあります。

図ではインタラクショントリガーとトリガーが区別して書かれていますが、これはメッセージやイベントなどを受ける部分をトリガー、トリガーやアクションなどを総称した機構としてインタラクショントリガーと呼び分けているためです。が、以降は特に区別しません。まあ、受ける部分と行動する部分にわかれていますよ、ということです。


インタラクショントリガー

インタラクショントリガーはスタイルを使ったトリガーとは技術的に別物です。ので、例えば、あるコントロールに対するトリガーを設定することはスタイルを使うと可能ですが、インタラクショントリガーの場合は無理です。インタラクショントリガーは個々のコントロールに直接定義する必要があります。

ここでLivetプロジェクトを作ると自動生成されているMainWindow.xamlを改めて見てみましょう。


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:DataContextDisposeActionDataContext、つまり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です。

https://github.com/ugaya40/Livet/blob/ver1.0.5/.NET4.0/Livet(.NET4.0)/Behaviors/DataContextDisposeAction.cs


DataContextDisposeAction.cs

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スレッド以外から触ったらダメよ云々というやつです。





  1. インタラクショントリガーは元々Silverlightのために開発されました。というのも、Silverlightで提供されてるコントロールはTriggerプロパティがないのです。ということでインタラクショントリガーは前回のトリガーとは目的は同じですが仕組みが違います。まあ、細かいことは今はやめましょう。で、普通に便利なんでWPFにも逆輸入された感じです。