KatsuYuzuのブログ

.NET の開発をメインとした日記です。

DependencyObjectから子要素をたどってBehavior、Actionをみつける

それで何が嬉しいかっていうとActionからサスペンドした時に、Actionを探して復帰することで、簡単に処理を継続できるとかとか。具体的にはFileOpenPicker用のアクションを作った時とか。

Windows ストアアプリは直接コマンドへ、Windows Phoneは復帰後にページからActionをみつけてコマンドへ。

子要素をたどる

まず、子要素をたどるにはVisualTreeHelperを使います。しかし、低級すぎて使いにくいので拡張メソッドを用意するといいです。

public static IEnumerable<DependencyObject> Children(this DependencyObject obj)
{
    if (obj == null) throw new ArgumentNullException("obj");

    var childrenCount = VisualTreeHelper.GetChildrenCount(obj);

    for (int i = 0; i < childrenCount; i++)
    {
        yield return VisualTreeHelper.GetChild(obj, i);
    }
}

public static IEnumerable<DependencyObject> Descendants(this DependencyObject obj)
{
    if (obj == null) throw new ArgumentNullException("obj");

    foreach (var child in obj.Children())
    {
        yield return child;

        foreach (var grandChild in child.Descendants())
        {
            yield return grandChild;
        }
    }
}

これでpage.Descendants()なんてやると子要素をごっそり取れます。

Behavior、Actionをみつける

Behavior、Actionは表示要素ではないので子要素の中に入っていません。ここではInteraction.GetBehaviorsを使います。

要素を引数にして関連づけられたBehaviorCollectionが取得できます。ここまでくれば何とかなる!こちらも拡張メソッドを定義します。

public static T FindAction<T>(this DependencyObject obj)
    where T: IAction
{
    return obj
        .Descendants()
        .SelectMany(Interaction.GetBehaviors)
        .SelectMany(x =>
        {
            var eventTrigger = x as EventTriggerBehavior;
            if (eventTrigger != null)
            {
                return eventTrigger.Actions;
            }

            var dataTrigger = x as DataTriggerBehavior;
            if (dataTrigger != null)
            {
                return dataTrigger.Actions;
            }

            return null;
        })
        .OfType<T>()
        .SingleOrDefault();
}

Behaviorをみつける場合はSelectMany(Interaction.GetBehaviors).OfType()でいいです。Actionをみつける場合はActionsをもつBehaviorから展開する必要があるので、ここではSDKに含まれているBehaviorを対象としました。