プログラム系統備忘録ブログ

記事中のコードは自己責任の下でご自由にどうぞ。

TargetedTriggerActionの使用例

2015/09/22 xamlのサンプルをGridView(Header)RowPresenterを使用するものに変更、説明を追加。

System.Windows.Interactivity.TargetedTriggerAction をWPFで使用するメモです。
確認時のExpression Blend SDKのDLLのバージョン

  • System.Windows.Interactivity.dll: Runtime Version=v4.0.30319, Version=4.5.0.0
  • Microsoft.Expression.Interactions.dll: Runtime Version=v4.0.30319, Version=4.5.0.0

C#コードとxamlのサンプル

ScrollViewer with non scrolling area (fixed header) でvoteされている回答の内容を、TargetedTriggerActionで行うサンプルです。
なお、このサンプルではAssociatedObjectとTargetの両方を使用しています。これが一般的な使用方法なのかは分かりません。

C#コードでTargetedTriggerActionの派生クラスを定義します。今回の例では、AssociatedObject側のスクロール位置に合わせて、Target側のスクロール位置を変更しています。

using System.Windows.Controls;
using System.Windows.Interactivity;

namespace WpfTest
{
    // AssociatedObjectの型は、TypeConstraintAttributeで指定
    // 互換性のない型で使おうとすると、実行時のxamlのパース中にInalidOperationExceptionが発生した
    // Targetの型は、TargetedTriggerActionの型パラメーターで指定
    // 互換性のない型をTargetに指定すると、Invokeが呼ばれる時にInvalidOperationExceptionが発生した
    [TypeConstraint(typeof(ScrollViewer))]
    public class ReflectHorizontalScrollAction : TargetedTriggerAction<ScrollViewer>
    {
        protected override void Invoke(object parameter)
        {
            // TargetNameが未指定の場合は、TargetプロパティはAssociatedObjectを返す
            // TargetNameに存在しない要素名を渡すと、Targetプロパティはnullを返した(ドキュメントと異なる)
            if (this.Target == null ||
                this.Target == this.AssociatedObject)
            {
                return;
            }

            // AssociatedObjectはDependencyObject型なので、必要ならキャスト
            var source = (ScrollViewer)this.AssociatedObject;
            // Targetは型パラメーターで指定した型Tなので、キャストは不要
            this.Target.Width = source.ViewportWidth;
            this.Target.ScrollToHorizontalOffset(source.HorizontalOffset);
        }
    }
}

TriggerAction Class の場合は、型引数はAssociatedObjectの型を指定していました。TargetedTriggerActionの場合は、型引数はTargetの型を指定しており、AssociatedObjectの型制約は TypeConstraintAttribute Class で指定することに注意してください。
また、コメントに振っていますが、Targetプロパティがnullを返す場合があります。

If TargetName is not set or cannot be resolved, defaults to the AssociatedObject.

TargetedTriggerAction.Target Property

とあるので、TargetNameに存在しない要素名を渡した場合でもAssociatedObjectが返るべきですが、実際はnullが返ります。

xamlです。ScrollViewerを上下に2つ配置しています。
TargetedTriggerActionは TriggerAction なので、TriggerBase の子要素として記述する必要があります。今回は下のScrollViewerがスクロールするときに自作Actionを実行したいので、 EventTrigger の子要素にしています。
自作ActionのTargetNameに上のScrollViewerを指定しているのがポイントです。

<Window x:Class="WpfTest.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:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:WpfTest"
        Title="MainWindow"
        Width="500"
        Height="120">
    <Window.Resources>
        <GridViewColumnCollection x:Key="gvcc">
            <GridViewColumn Header="Year"
                            DisplayMemberBinding="{Binding Year}"
                            Width="200" />
            <GridViewColumn Header="Month"
                            DisplayMemberBinding="{Binding Month}"
                            Width="200" />
            <GridViewColumn Header="Day"
                            DisplayMemberBinding="{Binding Day}"
                            Width="200" />
        </GridViewColumnCollection>
        <x:Array x:Key="Dates"
                 Type="{x:Type sys:DateTime}">
            <sys:DateTime>0001/1/1</sys:DateTime>
            <sys:DateTime>2015/9/21</sys:DateTime>
            <sys:DateTime>2015/9/22</sys:DateTime>
            <sys:DateTime>9999/12/31</sys:DateTime>
        </x:Array>
        <DataTemplate x:Key="ItemTemplate">
            <GridViewRowPresenter Columns="{StaticResource gvcc}" />
        </DataTemplate>
    </Window.Resources>

    <DockPanel>
        <ScrollViewer x:Name="ScrollViewerHeader"
                      DockPanel.Dock="Top"
                      HorizontalScrollBarVisibility="Hidden"
                      VerticalScrollBarVisibility="Hidden"
                      HorizontalAlignment="Left">
            <GridViewHeaderRowPresenter Columns="{StaticResource gvcc}" />
        </ScrollViewer>
        <ScrollViewer HorizontalScrollBarVisibility="Auto"
                      VerticalScrollBarVisibility="Auto">
            <ListBox ItemsSource="{StaticResource Dates}"
                     ItemTemplate="{StaticResource ItemTemplate}" />

            <!--このScrollViewerが横スクロールした時に、Header部分のScrollViewrも連動してスクロールさせる-->
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="ScrollChanged">
                    <local:ReflectHorizontalScrollAction TargetName="ScrollViewerHeader" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </ScrollViewer>
    </DockPanel>
</Window>

下のScrollViewerの横スクロール位置を変更すると、上のScrollViewerも連動してスクロールすることを確認できます。