やりたいこと
ItemsControlの中身をカスタムするときに、下記のように書いて、Borderの太さをViewModelのプロパティで変化させようとしたが、うまくいかなかった。どうにかして、うまくバインドしたい。
<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とその外側は分断されている)
<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を複数重ねて表示するということをしようとしている。ただの実験)
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)
他にも書き方はいろいろあるっぽい。
<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
コード