none
ItemsControl の ItemTemplate内で、親(ItemsControl)のDataContextのプロパティとバインドしたい RRS feed

  • 質問

  • 度々の質問ですみません。

    ItemsControlの各Itemを定義しているItemTemplate内で、親のItemsControlのDataContextのプロパティとバインドしたいのですが、XAML上ではどのように記述すれば良いのでしょうか。

    ItemTemplateを定義したDataTemplateは、ResourceDictionaryとして別ファイルに記述しています。

    WPFなら、RelativeSource FindAncestor で親にたどれたのですが、WinRTでは同様のことはできますか?

    よろしくお願いいたします。

    2016年7月7日 13:52

回答

  • Behaviorを使った例をGitHubのリポジトリにあげてみました。

    https://github.com/runceel/AncestorTypeSample


    かずき Blog:http://d.hatena.ne.jp/okazuki/

    • 回答としてマーク NIM5 2016年7月9日 4:25
    2016年7月9日 0:37
  • 正直、Converterの中で何をしているのか理解できてませんが。そのままコピペして設定してみました。
    引数valueはTemplateParentであるコンテナのインスタンスです。(ListBoxならListBoxItem)
    引数parameterに見つけたい型を与えているので、その型に代入可能なVisualが見つかるまでVisualTreeHelperで親を順に遡ってさかのぼります。
    代入可能かどうかはIsAssignableFromで判定しています。
    <TextBlock >
    	<TextBlock.Text>
    		<Binding Path="Tag.DataContext.Name" RelativeSource="{RelativeSource Mode=Self}" />
    	</TextBlock.Text>
    </TextBlock>
    RelativeSourceではModeがSelfになっているのでBinding対象となっているTextBlock自身がソースとなります。
    PathはTagを見ていますから、バインド対象のTextBlockのTagプロパティを探すことになりますが、このコードだとTagに何も設定されていません。
    サンプルにあるコードのようにTagのバインドも含めてコピーするか、DataContextにバインドするか、ElementNameなどで見える範囲にバインドしてください
    <DataTemplate x:Key="temp" >
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding}" Margin="0,0,10,0"/>
    
            <TextBlock Foreground="Red" Margin="0,0,10,0"
                        x:Name="textBox1">
                <TextBlock.Tag>
                    <Binding RelativeSource="{RelativeSource Mode=TemplatedParent}" Mode="OneTime"
                            Converter="{StaticResource findAncestor}" 
                            ConverterParameter="{StaticResource itemsControlType}">
                    </Binding>
                </TextBlock.Tag>
                <TextBlock.Text>
                    <Binding Path="Tag.DataContext.Name" RelativeSource="{RelativeSource Mode=Self}" />
                </TextBlock.Text>
            </TextBlock>
                        
            <TextBlock Foreground="Green" Margin="0,0,10,0"
                        Text="{Binding Path=DataContext.Name}"
                        x:Name="textBox2">
                <TextBlock.DataContext>
                    <Binding RelativeSource="{RelativeSource Mode=TemplatedParent}" Mode="OneTime"
                            Converter="{StaticResource findAncestor}" 
                            ConverterParameter="{StaticResource itemsControlType}">
                    </Binding>
                </TextBlock.DataContext>
            </TextBlock>
    
            <TextBlock Foreground="Blue" Margin="0,0,10,0"
                        Text="{Binding Path=Tag.DataContext.Name,ElementName=textBox1}">
            </TextBlock>
        </StackPanel>
    </DataTemplate>

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク NIM5 2016年7月9日 15:15
    2016年7月9日 4:41

すべての返信

  • 別リソースに定義されてるとElementNameも使えなくて厄介ですね…。前に似た挙動をするBehaviorを作ってみました。

    http://blog.okazuki.jp/entry/2016/04/28/081737

    これでどうにかならないですかね?(今Win7しかなくて試せない)


    かずき Blog:http://d.hatena.ne.jp/okazuki/

    2016年7月8日 3:55
  • 別解としてConverterを使ってみる

    <Page
        x:Class="App1.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:App1"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
        mc:Ignorable="d">
    
        <Page.Resources>
            <ResourceDictionary>
                <local:FindAncestorConverter x:Key="findAncestor" />
    
                <!-- x:Typeの代わり -->
                <local:TypeProxy x:Key="itemsControlType">
                    <!--! UWPならインスタンスつくらずにTypeに変換可能 -->
                    <!-- <local:TypeProxy.Type>ItemsControl</local:TypeProxy.Type> -->
    
    
                    <!--! WinRT(8.1)はTypeに変換できないのでインスタンスからTypeを -->
                    <local:TypeProxy.TypeObject>
                        <ItemsControl />
                    </local:TypeProxy.TypeObject>
                </local:TypeProxy>
    
    
                <DataTemplate x:Key="temp" >
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding}" Margin="0,0,10,0"/>
                        <TextBlock Foreground="Blue">
                            <TextBlock.Tag>
                                <Binding RelativeSource="{RelativeSource Mode=TemplatedParent}" Mode="OneTime"
                                     Converter="{StaticResource findAncestor}" 
                                     ConverterParameter="{StaticResource itemsControlType}">
                                </Binding>
                            </TextBlock.Tag>
                            <TextBlock.Text>
                                <Binding Path="Tag.DataContext" RelativeSource="{RelativeSource Mode=Self}" />
                            </TextBlock.Text>
                        </TextBlock>
                    </StackPanel>
                </DataTemplate>
            </ResourceDictionary>
        </Page.Resources>
    
        <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <ListBox ItemsSource="123"
                     DataContext="ABC"
                     ItemTemplate="{StaticResource temp}">
            </ListBox>
        </Grid>
    </Page>
    using System;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Data;
    using Windows.UI.Xaml.Media;
    using System.Reflection;
    
    namespace App1
    {
        public sealed partial class MainPage : Page
        {
            public MainPage()
            {
                this.InitializeComponent();
            }
        }
    
        class TypeProxy
        {
            public System.Type Type { get; set; }
            public TypeInfo TypeInfo
            {
                get { return Type.GetTypeInfo(); }
            }
    
            public object TypeObject
            {
                set { Type = value.GetType(); }
            }
        }
    
        class FindAncestorConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, string language)
            {
                Type t;
                if (parameter is TypeProxy)
                {
                    t = ((TypeProxy)parameter).Type;
                }
                else if (parameter is Type)
                {
                    t = (Type)parameter;
                }
                else
                {
                    t = this.TargetType;
                }
                if (t != null)
                {
                    TypeInfo ti = t.GetTypeInfo();
                    DependencyObject d = (DependencyObject)value;
                    do
                    {
                        if (ti.IsAssignableFrom(d.GetType().GetTypeInfo()))
                        {
                            return d;
                        }
                        d = VisualTreeHelper.GetParent(d);
    
    
                    } while (d != null);
                }
                return null;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, string language)
            {
                throw new NotSupportedException();
            }
    
            public Type TargetType { get; set; }
        }
    }

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2016年7月8日 10:59
  • gekkaさん、返信ありがとうございます。

    正直、Converterの中で何をしているのか理解できてませんが。そのままコピペして設定してみました。

    Converterにブレークポイントを設定すると、do-while分の return d; のところで、ItemsControlが戻っていたので、そのあたりまでは動作したと思いますが、Bindingできていませんでした。

    下記の部分、例えば、ItemsControlのDataContext に Nameというプロパティを持つViewModelが設定してあった場合、

    <Binding Path="Tag.DataContext" RelativeSource="{RelativeSource Mode=Self}" />
     

    下記のような感じでNameプロパティにアクセスしようと思ったのですが、何も表示されませんでした。

    <TextBlock >
    	<TextBlock.Text>
    		<Binding Path="Tag.DataContext.Name" RelativeSource="{RelativeSource Mode=Self}" />
    	</TextBlock.Text>
    </TextBlock>

    ItemsControlが貼り付けてあるところで、NameプロパティをTextBlock.Textにバインドすると表示されるので、ViewModelには設定されてると思います。

    どこか記述方法は間違ってますか?




    • 編集済み NIM5 2016年7月8日 16:25
    2016年7月8日 16:18
  • Behaviorを使った例をGitHubのリポジトリにあげてみました。

    https://github.com/runceel/AncestorTypeSample


    かずき Blog:http://d.hatena.ne.jp/okazuki/

    • 回答としてマーク NIM5 2016年7月9日 4:25
    2016年7月9日 0:37
  • かずきさん、ご丁寧にありがとうございます。

    Behavior版もnugetからInteractionを持ってきて試してみたのですが、組み込み方が悪かったのかまだうまく値を取れてません。リポジトリのソースを改造して試してみます。

    2016年7月9日 4:25
  • 正直、Converterの中で何をしているのか理解できてませんが。そのままコピペして設定してみました。
    引数valueはTemplateParentであるコンテナのインスタンスです。(ListBoxならListBoxItem)
    引数parameterに見つけたい型を与えているので、その型に代入可能なVisualが見つかるまでVisualTreeHelperで親を順に遡ってさかのぼります。
    代入可能かどうかはIsAssignableFromで判定しています。
    <TextBlock >
    	<TextBlock.Text>
    		<Binding Path="Tag.DataContext.Name" RelativeSource="{RelativeSource Mode=Self}" />
    	</TextBlock.Text>
    </TextBlock>
    RelativeSourceではModeがSelfになっているのでBinding対象となっているTextBlock自身がソースとなります。
    PathはTagを見ていますから、バインド対象のTextBlockのTagプロパティを探すことになりますが、このコードだとTagに何も設定されていません。
    サンプルにあるコードのようにTagのバインドも含めてコピーするか、DataContextにバインドするか、ElementNameなどで見える範囲にバインドしてください
    <DataTemplate x:Key="temp" >
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding}" Margin="0,0,10,0"/>
    
            <TextBlock Foreground="Red" Margin="0,0,10,0"
                        x:Name="textBox1">
                <TextBlock.Tag>
                    <Binding RelativeSource="{RelativeSource Mode=TemplatedParent}" Mode="OneTime"
                            Converter="{StaticResource findAncestor}" 
                            ConverterParameter="{StaticResource itemsControlType}">
                    </Binding>
                </TextBlock.Tag>
                <TextBlock.Text>
                    <Binding Path="Tag.DataContext.Name" RelativeSource="{RelativeSource Mode=Self}" />
                </TextBlock.Text>
            </TextBlock>
                        
            <TextBlock Foreground="Green" Margin="0,0,10,0"
                        Text="{Binding Path=DataContext.Name}"
                        x:Name="textBox2">
                <TextBlock.DataContext>
                    <Binding RelativeSource="{RelativeSource Mode=TemplatedParent}" Mode="OneTime"
                            Converter="{StaticResource findAncestor}" 
                            ConverterParameter="{StaticResource itemsControlType}">
                    </Binding>
                </TextBlock.DataContext>
            </TextBlock>
    
            <TextBlock Foreground="Blue" Margin="0,0,10,0"
                        Text="{Binding Path=Tag.DataContext.Name,ElementName=textBox1}">
            </TextBlock>
        </StackPanel>
    </DataTemplate>

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク NIM5 2016年7月9日 15:15
    2016年7月9日 4:41
  • ありがとうございます。

    Tagプロパティ自体の仕様を勘違いしてました。試してみます

    2016年7月9日 15:16