【WPF】ControlTemplateの中にViewModelのプロパティをバインドする


やりたいこと

ItemsControlの中身をカスタムするときに、下記のように書いて、Borderの太さをViewModelのプロパティで変化させようとしたが、うまくいかなかった。どうにかして、うまくバインドしたい。

うまくいかなかったのでまねしないでください.xaml
<ItemsControl ItemsSource="{Binding Pl}" Width="600" Height="600">
    <ItemsControl.Template>
        <ControlTemplate TargetType="ItemsControl">
            <!-- このBorderのThicknessに、ViewModelのプロパティMyThicknessをバインドする -->
            <Border BorderThickness="{Binding Path=MyThickness}">
                <ItemsPresenter/>
            </Border>
        </ControlTemplate>
    </ItemsControl.Template>
</ItemsControl>

結果

「Binding RelativeSource」を使うとうまくいった。
相対参照で一番上の親のWindowまでさかのぼり、そいつのDataContext(=ViewModel)のプロパティを見よ、ということをしないといけない様子。
(ControlTemplateとその外側は分断されている)

MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" >
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>

    <Grid Background="#33990000" Name="MainGrid">
        <Viewbox>
            <ItemsControl ItemsSource="{Binding Pl}" Width="600" Height="600">
                <ItemsControl.Template>
                    <ControlTemplate TargetType="ItemsControl">
                        <!-- このBorderのThicknessに、ViewModelのプロパティをバインドする -->
                        <Border BorderThickness="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}, Path=DataContext.MyThickness}">
                            <ItemsPresenter />
                        </Border>
                    </ControlTemplate>
                </ItemsControl.Template>

                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Grid />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </Viewbox>
    </Grid>
</Window>

ViewModelはこのような感じ。
(ちなみに、ここではItemsControlを使って、List(ObservableCollection)に入れたPolylineを複数重ねて表示するということをしようとしている。ただの実験)

ViewModel.cs
namespace WpfApp1
{
    class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        // ItemsControlのSourceに設定するコレクション
        public ObservableCollection<Polyline> Pl { get { return pl; } }
        public ObservableCollection<Polyline> pl = new ObservableCollection<Polyline>();

        // DataTemplateの中に直接バインドしたいプロパティ
        public Thickness MyThickness { get; } = new Thickness(20);

        public ViewModel()
        {
            // 画面に描くPolylineをつくる
            Polyline pl1 = new Polyline();
            pl1.Points.Add(new Point(10, 10));
            pl1.Points.Add(new Point(200, 150));
            pl1.Stroke = new SolidColorBrush(Colors.Red);
            pl1.StrokeThickness = 5;
            pl.Add(pl1);

            Polyline pl2 = new Polyline();
            pl2.Points.Add(new Point(15, 15));
            pl2.Points.Add(new Point(250, 600));
            pl2.Points.Add(new Point(600, 400));
            pl2.Stroke = new SolidColorBrush(Colors.Yellow);
            pl2.StrokeThickness = 5;
            pl.Add(pl2);
        }
    }
}

追記(19/04/04)

他にも書き方はいろいろあるっぽい。

MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="Auto" Width="Auto"
        Name="Root">
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>

    <Grid Background="#33990000" Name="MainGrid">
        <ItemsControl Width="Auto" Height="Auto">
            <ItemsControl.Template>
                <ControlTemplate TargetType="ItemsControl">
                    <!-- このBorderのThicknessに、ViewModelのプロパティをバインドする -->

                    <!-- OKな書き方①:自分の親をたどりGridまで行ったらそのDataContext(=VM)のプロパティを見る -->
                    <Border BorderBrush="Green"
                            BorderThickness="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.MyThickness}">

                    <!-- OKな書き方②:一番上の親(Window)のDataContext(VM)の中のプロパティにバインドする -->
                    <!--<Border BorderBrush="Green"
                        BorderThickness="{Binding Path=DataContext.MyThickness, ElementName=Root}">-->

                    <!-- OKな書き方③:DataContextにMyThicknessがあればそれをBinding、なければItemsControlの選択項目(自分に該当する項目)のMyThicknessをBinding -->
                    <!-- 今回の場合、DataContextにMyThicknessがあってItemsControlにないので、VMにバインドされてる -->
                    <!--<Border BorderBrush="Green"
                        BorderThickness="{Binding Path=MyThickness}">-->

                    <!-- NGな書き方:Root=Windowで、その中のプロパティにバインド、とすると、VMのプロパティではなくコードビハインドにあるプロパティ、になってしまう。-->
                    <!-- 逆に、コードビハインドに書いたプロパティにバインドするのであれば、これでOK -->
                    <!--<Border BorderBrush="Green"
                        BorderThickness="{Binding Path=MyThickness, ElementName=Root}">-->

                        <ItemsPresenter />
                    </Border>

                </ControlTemplate>
            </ItemsControl.Template>

            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <!-- ここに、Gridを指定する。-->
                    <Canvas />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Grid>
</Window>

参考

データバインディングの概要
https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/data/data-binding-overview

WPF の {Binding Path=/}
https://ufcpp.wordpress.com/2011/01/28/wpf-%E3%81%AE-binding-path/

stack overflow
https://stackoverflow.com/questions/33694091/bind-from-controltemplate-to-viewmodel

自分のページ
https://qiita.com/tera1707/items/73cda312b7cd9c4df40d

コード