none
ウィンドウからコントロールテンプレート中の名前を付けた要素を参照したい。 RRS feed

  • 質問

  • App.xaml に名前を付けた要素を含むコントロールテンプレートを作成し、Window に適用しました。
    Window からマークアップを使ってコントロールテンプレート中の名前を付けた要素を参照する方法を知りたいと考えています。
    "Binding ElementName=" は動作しませんでした。
    この理由はコントロールテンプレートと Window で namescope が異なるからだと考えています。
    よろしくお願いします。

    (契約上の都合により普段使用している三輪の牛とは異なる ID で質問しております。)

    2017年7月5日 6:09

回答

  • 普通はSystem.Windows.TemplatePartAttributeを指定しておいて、OnApplyTemplate内でFrameworkTemplate.FindNameを使ってテンプレート内の名前付き要素を取り出すので、標準のXAML機能だけでは参照できないです。
    そもそもテンプレートは差し替えられるものなので、最初に適用されたテンプレートから名前付き要素を探しても、差し替えられたら参照できなくなります。

    いちおうテンプレート差し替えなしという前提であればコンバーターで取り出すことは可能ですが。

    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:app="clr-namespace:WpfApplication1"
            Title="MainWindow" Height="350" Width="525" >
        <Window.Resources>
            <ControlTemplate x:Key="WindowTemplateKey" TargetType="{x:Type Window}">
                <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                    <Grid>
                        <AdornerDecorator>
                            <DockPanel>
                                <StackPanel DockPanel.Dock="Top" Background="LightPink" Margin="5">
                                    <TextBlock Text="テンプレートの要素"/>
                                    <TextBlock x:Name="PART_TEXTBLOCK" MinHeight="30" />
                                </StackPanel>
                                <ContentPresenter/>
                            </DockPanel>
                        </AdornerDecorator>
                    </Grid>
                </Border>
            </ControlTemplate>
    
            <app:ElementFromTemplateConverter x:Key="etconv"/>
        </Window.Resources>
        <Window.Template>
            <StaticResource ResourceKey="WindowTemplateKey" />
        </Window.Template>
        <StackPanel>
            <TextBlock Text="OnApplyTemplateで取り出す場合"/>
            <TextBox Margin="5" Text="{Binding Path=PART_TEXTBLOCK.Text,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},UpdateSourceTrigger=PropertyChanged}"/>
    
    
            <TextBlock Text="Converterで掘り出す場合"/>
            <TextBox Margin="5" Text="{Binding Path=Tag.Text,RelativeSource={RelativeSource Mode=Self},UpdateSourceTrigger=PropertyChanged}">
                <TextBox.Tag>
                    <Binding Path="."  Converter="{StaticResource etconv}" 
                             ConverterParameter="PART_TEXTBLOCK"  >
                        <Binding.RelativeSource>
                            <RelativeSource Mode="FindAncestor" AncestorType="{x:Type Window}"  />
                        </Binding.RelativeSource>
                    </Binding>
                </TextBox.Tag>
            </TextBox>
    
        </StackPanel>
    </Window>
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    
    namespace WpfApplication1
    {
    
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
            public override void OnApplyTemplate()
            {
                base.OnApplyTemplate();
    
                if (this.Template != null)
                {
                    this.PART_TEXTBLOCK = this.Template.FindName("PART_TEXTBLOCK", this) as TextBlock;
                }
            }
    
            internal TextBlock PART_TEXTBLOCK
            {
                get { return (TextBlock)GetValue(PART_TEXTBLOCKProperty); }
                set { SetValue(PART_TEXTBLOCKProperty, value); }
            }
    
            internal static readonly DependencyProperty PART_TEXTBLOCKProperty =
                DependencyProperty.Register("PART_TEXTBLOCK", typeof(TextBlock), typeof(MainWindow)
                , new FrameworkPropertyMetadata(default(TextBlock)));
        }
    
    
    
        class ElementFromTemplateConverter : IValueConverter
        {
    
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                Control ctl = value as Control;
                ContentPresenter cp = value as ContentPresenter;
                ContentControl cc = value as ContentControl;
    
                if (ctl != null)
                {
                    return ctl.Template.FindName(parameter.ToString(), ctl);
                }
                else if (cp != null)
                {
                    return cp.ContentTemplate.FindName(parameter.ToString(), cp);
                }
                else if (cc != null)
                {
                    return cc.ContentTemplate.FindName(parameter.ToString(), cc);
                }
                return null;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotSupportedException();
            }
        }
    }

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

    2017年7月5日 9:35

すべての返信

  • 普通はSystem.Windows.TemplatePartAttributeを指定しておいて、OnApplyTemplate内でFrameworkTemplate.FindNameを使ってテンプレート内の名前付き要素を取り出すので、標準のXAML機能だけでは参照できないです。
    そもそもテンプレートは差し替えられるものなので、最初に適用されたテンプレートから名前付き要素を探しても、差し替えられたら参照できなくなります。

    いちおうテンプレート差し替えなしという前提であればコンバーターで取り出すことは可能ですが。

    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:app="clr-namespace:WpfApplication1"
            Title="MainWindow" Height="350" Width="525" >
        <Window.Resources>
            <ControlTemplate x:Key="WindowTemplateKey" TargetType="{x:Type Window}">
                <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                    <Grid>
                        <AdornerDecorator>
                            <DockPanel>
                                <StackPanel DockPanel.Dock="Top" Background="LightPink" Margin="5">
                                    <TextBlock Text="テンプレートの要素"/>
                                    <TextBlock x:Name="PART_TEXTBLOCK" MinHeight="30" />
                                </StackPanel>
                                <ContentPresenter/>
                            </DockPanel>
                        </AdornerDecorator>
                    </Grid>
                </Border>
            </ControlTemplate>
    
            <app:ElementFromTemplateConverter x:Key="etconv"/>
        </Window.Resources>
        <Window.Template>
            <StaticResource ResourceKey="WindowTemplateKey" />
        </Window.Template>
        <StackPanel>
            <TextBlock Text="OnApplyTemplateで取り出す場合"/>
            <TextBox Margin="5" Text="{Binding Path=PART_TEXTBLOCK.Text,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},UpdateSourceTrigger=PropertyChanged}"/>
    
    
            <TextBlock Text="Converterで掘り出す場合"/>
            <TextBox Margin="5" Text="{Binding Path=Tag.Text,RelativeSource={RelativeSource Mode=Self},UpdateSourceTrigger=PropertyChanged}">
                <TextBox.Tag>
                    <Binding Path="."  Converter="{StaticResource etconv}" 
                             ConverterParameter="PART_TEXTBLOCK"  >
                        <Binding.RelativeSource>
                            <RelativeSource Mode="FindAncestor" AncestorType="{x:Type Window}"  />
                        </Binding.RelativeSource>
                    </Binding>
                </TextBox.Tag>
            </TextBox>
    
        </StackPanel>
    </Window>
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    
    namespace WpfApplication1
    {
    
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
            public override void OnApplyTemplate()
            {
                base.OnApplyTemplate();
    
                if (this.Template != null)
                {
                    this.PART_TEXTBLOCK = this.Template.FindName("PART_TEXTBLOCK", this) as TextBlock;
                }
            }
    
            internal TextBlock PART_TEXTBLOCK
            {
                get { return (TextBlock)GetValue(PART_TEXTBLOCKProperty); }
                set { SetValue(PART_TEXTBLOCKProperty, value); }
            }
    
            internal static readonly DependencyProperty PART_TEXTBLOCKProperty =
                DependencyProperty.Register("PART_TEXTBLOCK", typeof(TextBlock), typeof(MainWindow)
                , new FrameworkPropertyMetadata(default(TextBlock)));
        }
    
    
    
        class ElementFromTemplateConverter : IValueConverter
        {
    
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                Control ctl = value as Control;
                ContentPresenter cp = value as ContentPresenter;
                ContentControl cc = value as ContentControl;
    
                if (ctl != null)
                {
                    return ctl.Template.FindName(parameter.ToString(), ctl);
                }
                else if (cp != null)
                {
                    return cp.ContentTemplate.FindName(parameter.ToString(), cp);
                }
                else if (cc != null)
                {
                    return cc.ContentTemplate.FindName(parameter.ToString(), cc);
                }
                return null;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotSupportedException();
            }
        }
    }

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

    2017年7月5日 9:35
  • gekka さん。ありがとうございます。
    検討に時間が掛かっています。
    まずは御礼申し上げます。
    2017年7月11日 0:33
  • OnApplyTemplate を使った方法は、言い換えるとテンプレートの利用側から参照される可能性のある要素をプロパティとして公開しておく方法と理解しました。この方法が良いように思いました。DRY のために基底クラスに記述することにします。
    テンプレートが差し替えられたときは null を返すようにしておきます。ありがとうございました。
    2017年7月11日 2:54