DataTemplateSelector使ってみた、実際便利
DataのリストをBindingするだけで、それぞれに合ったTemplateを適用して表示することができる
DataTypeの識別用列挙体を用意しておいて
enum Type { Text, Ellipse, Rect }
Dataクラス
class MyData
- MyType、識別用
- MyText、文字列
- MyLeft、左位置
あとはMyDataのリストを作成して、ItemsControlのItemsSourceプロパティにBinding
今回はItemsControlを使ったけど、ListBoxやStackPanelなどのItemsSourceプロパティを持つPanel系の要素なら使えると思う
結果

文字列のtextblock thumbはTextBlockに改変
図形の丸と四角はEllipseとRectangle
textボタンではそれぞれのDataのプロパティに変更を加えている、文字列には👍️を追加、丸はLeftに+10、四角はTopに+10

テストアプリのコード
2024WPF/20241228_DataTemplateSelector_de_Thumb at master · gogowaten/2024WPF

環境
- Windows 10 Home バージョン 22H2
- Visual Studio Community 2022 Version 17.12.3
- WPF
- C#
- .NET 8.0
Class1.cs
using System.Windows; namespace _20241228_DataTemplateSelector_de_Thumb { /// <summary> /// データタイプの識別用列挙体 /// </summary> public enum ThumbType { None = 0, Text, Ellipse, Rect } /// <summary> /// データ用 /// 殆どを依存関係プロパティにしているのは、値を更新したときにBinding先に通知するため /// </summary> public class MyData : DependencyObject { public ThumbType Type { get; } public MyData(ThumbType type) { this.Type = type; } #region 依存関係プロパティ public string MyText { get { return (string)GetValue(MyTextProperty); } set { SetValue(MyTextProperty, value); } } public static readonly DependencyProperty MyTextProperty = DependencyProperty.Register(nameof(MyText), typeof(string), typeof(MyData), new PropertyMetadata(string.Empty)); public double MyLeft { get { return (double)GetValue(MyLeftProperty); } set { SetValue(MyLeftProperty, value); } } public static readonly DependencyProperty MyLeftProperty = DependencyProperty.Register(nameof(MyLeft), typeof(double), typeof(MyData), new PropertyMetadata(0.0)); public double MyTop { get { return (double)GetValue(MyTopProperty); } set { SetValue(MyTopProperty, value); } } public static readonly DependencyProperty MyTopProperty = DependencyProperty.Register(nameof(MyTop), typeof(double), typeof(MyData), new PropertyMetadata(0.0)); public double MyVolume { get { return (double)GetValue(MyVolumeProperty); } set { SetValue(MyVolumeProperty, value); } } public static readonly DependencyProperty MyVolumeProperty = DependencyProperty.Register(nameof(MyVolume), typeof(double), typeof(MyData), new PropertyMetadata(30.0)); #endregion 依存関係プロパティ } }
値の格納用のクラスなのに、行数が多いのは依存関係プロパティを使っているからなんだけど、これは通知プロパティのほうが行数少なくて済んだかもしれない
Class2.cs
using System.Windows.Controls; using System.Windows; namespace _20241228_DataTemplateSelector_de_Thumb { public class MyDTSelector : DataTemplateSelector { public DataTemplate? DT1 { get; set; } public DataTemplate? DT2 { get; set; } public DataTemplate? DT3 { get; set; } /// <summary> /// 今回の場合だと、引数のitemにMyDataが入っているので、 /// データタイプを識別してそれぞれに合ったDataTemplateを返している /// それぞれのDataTemplateの設定はXAMLの方で行っている /// </summary> /// <param name="item"></param> /// <param name="container"></param> /// <returns></returns> public override DataTemplate? SelectTemplate(object item, DependencyObject container) { if (item is not MyData) { return base.SelectTemplate(item, container); } else if (item is MyData dd) { if (dd.Type == ThumbType.Text) { return DT1; } else if (dd.Type == ThumbType.Ellipse) { return DT2; } else if (dd.Type == ThumbType.Rect) { return DT3; } else { return base.SelectTemplate(item, container); } } return base.SelectTemplate(item, container); } } }
今回の要DataTemplateSelectorクラスを継承したMyDTSelectorクラス
SelectTemplateメソッドをOverride、そこでDataのTypeを識別して、それぞれに合ったDataTemplateを返している
返しているDataTemplate(DT1からDT3)は、このクラスだけで見ると空っぽに見えるので、こんなのを返して何になるんだろうと思ったら、このクラスを使う側(XAMLのほう)でDataTemplateを入れて使うみたいで、次がその使う側のMainWindow.xaml
MainWindow.xaml
<Window x:Class="_20241228_DataTemplateSelector_de_Thumb.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:_20241228_DataTemplateSelector_de_Thumb" mc:Ignorable="d" Title="MainWindow" Height="450" Width="500"> <Window.Resources> <Style x:Key="ts" TargetType="Thumb"> <EventSetter Event="DragDelta" Handler="Thumb_DragDelta"/> <Setter Property="Canvas.Left" Value="{Binding MyLeft, Mode=TwoWay}"/> <Setter Property="Canvas.Top" Value="{Binding MyTop, Mode=TwoWay}"/> </Style> <DataTemplate x:Key="ddText" DataType="local:MyData"> <Thumb Style="{StaticResource ts}"> <Thumb.Template> <ControlTemplate> <TextBlock Text="{Binding MyText}"/> </ControlTemplate> </Thumb.Template> </Thumb> </DataTemplate> <DataTemplate x:Key="ddEllipse" DataType="local:MyData"> <Thumb Style="{StaticResource ts}"> <Thumb.Template> <ControlTemplate> <Grid> <Ellipse Width="{Binding MyVolume}" Height="{Binding MyVolume}" Fill="Gold"/> <TextBlock Text="{Binding MyText}" VerticalAlignment="Center"/> </Grid> </ControlTemplate> </Thumb.Template> </Thumb> </DataTemplate> <DataTemplate x:Key="ddRect" DataType="local:MyData"> <Thumb Style="{StaticResource ts}"> <Thumb.Template> <ControlTemplate> <Grid> <Rectangle Width="{Binding MyVolume}" Height="{Binding MyVolume}" Fill="DodgerBlue"/> <TextBlock Text="{Binding MyText}" VerticalAlignment="Center"/> </Grid> </ControlTemplate> </Thumb.Template> </Thumb> </DataTemplate> </Window.Resources> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition Width="150"/> </Grid.ColumnDefinitions> <ItemsControl x:Name="ic" ItemsSource="{Binding}" FontSize="30"> <ItemsControl.ItemTemplateSelector> <local:MyDTSelector DT1="{StaticResource ddText}" DT2="{StaticResource ddEllipse}" DT3="{StaticResource ddRect}"/> </ItemsControl.ItemTemplateSelector> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemContainerStyle> <Style TargetType="ContentPresenter"> <Setter Property="Canvas.Left" Value="{Binding MyLeft}"/> <Setter Property="Canvas.Top" Value="{Binding MyTop}"/> </Style> </ItemsControl.ItemContainerStyle> </ItemsControl> <!--動作確認用Panel--> <StackPanel Grid.Column="1"> <Button Content="test" Click="Button_Click"/> <ListBox ItemsSource="{Binding}"> <ListBox.ItemTemplate> <DataTemplate DataType="{x:Type local:MyData}"> <StackPanel> <TextBlock Text="{Binding MyLeft, StringFormat=left {0:0.0}}"/> <TextBlock Text="{Binding MyTop, StringFormat=top {0:0.0}}"/> <Separator/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </StackPanel> </Grid> </Window>
Window.Resourceの中で各種DataTemplateを用意、Keyプロパティに名前を付けておく、18、28、41行目

12行目、StyleでThumb用のスタイルを用意しておいて、各DataTemplateのThumbのスタイルに適用している
ここで肝要なのが14、15行目のBindingのModeをTwoWay設定で、これを明記しておかないとドラッグ移動してくれなかった
DataTemplateSelectorを使っているところは

これで空っぽだと思っていたDT1からDT3に、DataTemplateが入ったことになるみたい、そういう使い方なのねえ
69行目からの5行でItemsPanelをCanvasに変更している、これで各ItemのCanvas.Leftを指定で左位置が変更できる
74行目からのItemContainerStyle
ここでもCanvas.LeftとかをBindingする必要があるんだけど、よくわかっていなくて
まず、TargetTypeをContentPresenterにする必要がわからん
Canvas.LeftとかのBindingも、ItemTemplateSelectorのほうのStyleで行っているから要らないと思うんだけどねえ、そのくせModeはTwoWayと明記する必要がないのもよくわからん
MainWindow.xaml.cs
using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; namespace _20241228_DataTemplateSelector_de_Thumb { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public List<MyData> MyDatas { get; set; } public MainWindow() { InitializeComponent(); MyDatas = [ new MyData(ThumbType.Text) { MyLeft = 30, MyTop = 10, MyText = "textblock thumb" }, new MyData(ThumbType.Ellipse) { MyLeft = 30, MyTop = 80, MyVolume = 100 }, new MyData(ThumbType.Rect) { MyLeft = 130, MyTop= 140, MyVolume = 100 }]; DataContext = MyDatas; } private void Thumb_DragDelta(object sender, DragDeltaEventArgs e) { if (sender is Thumb t) { Canvas.SetLeft(t, Canvas.GetLeft(t) + e.HorizontalChange); Canvas.SetTop(t, Canvas.GetTop(t) + e.VerticalChange); e.Handled = true; } } private void Button_Click(object sender, RoutedEventArgs e) { MyDatas[0].MyText += "👍️"; MyDatas[1].MyLeft += 10; MyDatas[2].MyTop += 10; } } }
MyData作成してBindingとドラッグ移動処理、ボタンクリックでの動作
どうでもいいけど絵文字の👍️は色違いの💩に見える
参照したところ
【WPF】DataTemplateSelectorクラスを使用して、動的にコントロールの種類を変更する方法【C#】 #Xaml - Qiita
qiita.com
DataTemplateSelectorでDataTemplateを切り替える – 山本隆の開発日誌
www.gesource.jp
[WPF] データに応じてリストの表示形式を切り替える #C# - Qiita
qiita.com
TemplateSelectorとDataTriggerを使用した一例 #C# - Qiita
qiita.com
WPFで列挙型の表示(DataTemplateSelector)|Memeplexes
memeplex.blog.shinobi.jp
感想
昔からこうしたくて色々試してようやくここまでできた(歓喜将軍)
XAMLは難しいけど、使えれば便利なんだなあ
いままで移動できる各種要素を表現するには、Thumbクラスを継承したそれぞれのクラスを作成していた、今回はクラスを作ること無く各種Templateだけを作成して、あとはDataを渡すだけでできた
まだ足りない
あとはグループ化(階層化)できるのかとか、細かい動作(フォーカス範囲指定、右クリックメニュー)を入れられるのかってとこ
これは違った
ItemContainerStyleSelector
これはStyleを適用する型が決まっているようで、ItemsControlならContentPresenter型、ListBoxならListBoxItem型とかで、Thumb用のStyleは指定できなかった
関連記事
3日後
WPF、この一ヶ月でのカスタムコントロールThumbのマウスドラッグ移動のまとめ - 午後わてんのブログ
gogowaten.hatenablog.com