読者です 読者をやめる 読者になる 読者になる

かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

UWPでBindingのAncestorTypeを指定したい

UWP

出来ません。

なので疑似的にやるBehaviorを作ってみました。以下のような感じです。

using Microsoft.Xaml.Interactivity;
using System;
using System.Reflection;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;

namespace App28
{
    public class AncestorBindingBehavior : DependencyObject, IBehavior
    {
        /// <summary>
        /// Binding対象のオブジェクト
        /// </summary>
        public DependencyObject AssociatedObject { get; set; }

        /// <summary>
        /// Binding
        /// </summary>
        public Binding Binding { get; set; }

        /// <summary>
        /// 親方向に遡って探したい型の名前
        /// </summary>
        public string AncestorType { get; set; }

        /// <summary>
        /// Binding対象のプロパティ名
        /// </summary>
        public string TargetPropertyName { get; set; }

        public void Attach(DependencyObject associatedObject)
        {
            this.AssociatedObject = associatedObject;
            // プロパティの設定が未完全の場合は何もしない
            if (this.Binding == null || string.IsNullOrWhiteSpace(this.TargetPropertyName) || string.IsNullOrWhiteSpace(this.AncestorType)) { return; }
            // VisualTree構築後に1度だけ実行する
            ((FrameworkElement)this.AssociatedObject).Loaded += this.AncestorBindingBehavior_Loaded;
        }

        private void AncestorBindingBehavior_Loaded(object sender, RoutedEventArgs e)
        {
            // イベント解除
            ((FrameworkElement)this.AssociatedObject).Loaded -= this.AncestorBindingBehavior_Loaded;

            // 親を遡ってソースとなるオブジェクトを探す
            var source = FindAncestorType(this.AssociatedObject, this.AncestorType);
            // バインドする添付プロパティを探す
            var targetProperty = GetDependencyProperty(this.AssociatedObject.GetType(), this.TargetPropertyName);
            // 失敗したら何もしない
            if (source == null || targetProperty == null) { return; }
            // Bindingのソースをセットしてプロパティにバインドする
            this.Binding.Source = source;
            ((FrameworkElement)this.AssociatedObject).SetBinding(targetProperty,
                this.Binding);
        }

        public void Detach()
        {
        }

        /// <summary>
        /// VisualTreeを親へ辿っていって指定した型名のものがあったら返す
        /// </summary>
        /// <param name="element"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        private static DependencyObject FindAncestorType(DependencyObject element, string type)
        {
            if (element.GetType().Name == type) { return element; }

            var parent = VisualTreeHelper.GetParent(element);
            if (parent == null) { return null; }
            return FindAncestorType(parent, type);
        }

        /// <summary>
        /// 継承関係を親へ辿っていきながら指定した添付プロパティが定義されてたらそれを返す
        /// </summary>
        /// <param name="type"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        private static DependencyProperty GetDependencyProperty(Type type, string propertyName)
        {
            var field = type
                .GetTypeInfo()
                .GetDeclaredField($"{propertyName}Property");
            if (field != null)
            {
                return (DependencyProperty) field.GetValue(null);
            }

            var property = type
                .GetTypeInfo()
                .GetDeclaredProperty($"{propertyName}Property");
            if (property != null)
            {
                return (DependencyProperty) property.GetValue(null);
            }

            var baseType = type.GetTypeInfo().BaseType;
            if (baseType == typeof(object)) { return null; }

            return GetDependencyProperty(baseType, propertyName);
        }
    }
}

使い方は簡単。バインド対象にぽとっとBehaviorを落として各種プロパティを設定するだけ。 ButtonのVisibilityと、ListViewItemのIsSelectedをバインドするには以下のように使います。

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App28"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
      x:Class="App28.MainPage"
      mc:Ignorable="d">
    <Page.Resources>
        <local:BooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter"
                                            IsInverse="True"/>
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ListView ItemsSource="{x:Bind Users}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <TextBlock Text="{Binding}" />
                        <Button Content="削除">
                            <Interactivity:Interaction.Behaviors>
                                <!-- ここ! -->
                                <local:AncestorBindingBehavior AncestorType="ListViewItem"
                                                               TargetPropertyName="Visibility"
                                                               Binding="{Binding IsSelected, Mode=TwoWay, Converter={StaticResource InverseBooleanToVisibilityConverter}}" />
                            </Interactivity:Interaction.Behaviors>
                        </Button>
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Page>