nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

WPF DataTemplateの学習

はじめに

WPF DataTemplateの学習は、以下のMicrosoftのサンプル(DataTemplatingIntro)とドキュメントを合わせて確認するのがいいと思います。

github.com

まずは、DataTemplateを使用していないイメージ1までのコードです。

イメージ1

f:id:nprogram:20180725205213p:plain

コード

[MainWindow.xaml]

<Window x:Class="WpfApp2.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:WpfApp2"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.Resources>
        <local:Tasks x:Key="myTodoList"/>
    </Window.Resources>
    <StackPanel>
        <TextBlock Name="blah" FontSize="20" Text="My Task List:"/>
        <ListBox Width="400" Margin="10"
             ItemsSource="{Binding Source={StaticResource myTodoList}}"/>
    </StackPanel>
</Window>

[MainWindow.xaml.cs]

using System.Collections.ObjectModel;
using System.Windows;


namespace WpfApp2
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    public class Tasks : ObservableCollection<Task>
    {
        public Tasks()
        {
            Add(new Task("Groceries", "Pick up Groceries and Detergent", 2, TaskType.Home));
            Add(new Task("Laundry", "Do my Laundry", 2, TaskType.Home));
            Add(new Task("Email", "Email clients", 1, TaskType.Work));
            Add(new Task("Clean", "Clean my office", 3, TaskType.Work));
            Add(new Task("Dinner", "Get ready for family reunion", 1, TaskType.Home));
            Add(new Task("Proposals", "Review new budget proposals", 2, TaskType.Work));
        }
    }

    public class Task
    {
        public string TaskName { get; set; }
        public string Description { get; set; }
        public int Priority { get; set; }
        public TaskType TaskType { get; set; }

        public Task(string someTaskName, string someDescription, int somePriority, TaskType someTaskType)
        {
            this.TaskName = someTaskName;
            this.Description = someDescription;
            this.Priority = somePriority;
            this.TaskType = someTaskType;
        }
    }

    public enum TaskType
    {
        Home,
        Work
    }
}

DataTemplateを定義する

ここで、ListBoxに、DataTemplateを使用することで、ListBoxの各Taskクラスのプロパティの値をListBoxに表示できるようになりました。

イメージ2

f:id:nprogram:20180725210310p:plain

[MainWindow.xamlの一部]

        <ListBox Width="400" Margin="10"
             ItemsSource="{Binding Source={StaticResource myTodoList}}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <TextBlock Text="{Binding Path=TaskName}" />
                        <TextBlock Text="{Binding Path=Description}"/>
                        <TextBlock Text="{Binding Path=Priority}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

DataTemplateをWindows.Resourcesに登録

これだと、再利用性が低いので、再利用できるように、Windows.Resourcesに登録します。

[MainWindow.xamlの一部]

    <Window.Resources>
        <local:Tasks x:Key="myTodoList"/>

        <DataTemplate x:Key="myTaskTemplate">
            <StackPanel>
                <TextBlock Text="{Binding Path=TaskName}" />
                <TextBlock Text="{Binding Path=Description}"/>
                <TextBlock Text="{Binding Path=Priority}"/>
            </StackPanel>
        </DataTemplate>
        
    </Window.Resources>
    <StackPanel>
        <TextBlock Name="blah" FontSize="20" Text="My Task List:"/>
        <ListBox Width="400" Margin="10"
             ItemsSource="{Binding Source={StaticResource myTodoList}}"
             ItemTemplate="{StaticResource myTaskTemplate}"/>
    </StackPanel>

DataType プロパティを使用する

また、以下のように、DataTypeを使用しても、同じように表示可能です。

    <Window.Resources>
        <local:Tasks x:Key="myTodoList"/>

        <DataTemplate DataType="{x:Type local:Task}">
            <StackPanel>
                <TextBlock Text="{Binding Path=TaskName}" />
                <TextBlock Text="{Binding Path=Description}"/>
                <TextBlock Text="{Binding Path=Priority}"/>
            </StackPanel>
        </DataTemplate>

    </Window.Resources>
    <StackPanel>
        <TextBlock Name="blah" FontSize="20" Text="My Task List:"/>
        <ListBox Width="400" Margin="10"
             ItemsSource="{Binding Source={StaticResource myTodoList}}"/>
    </StackPanel>

DataTemplateにさらに要素を追加する

もっと、美しく表示させてみます。

イメージ3

f:id:nprogram:20180725225653p:plain

[MainWindow.xamlの一部]

    <Window.Resources>
        <local:Tasks x:Key="myTodoList"/>

        <DataTemplate x:Key="myTaskTemplate">
            <Border Name="border" BorderBrush="Aqua" BorderThickness="1"
          Padding="5" Margin="5">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" Text="Task Name:"/>
                    <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=TaskName}" />
                    <TextBlock Grid.Row="1" Grid.Column="0" Text="Description:"/>
                    <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Description}"/>
                    <TextBlock Grid.Row="2" Grid.Column="0" Text="Priority:"/>
                    <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Path=Priority}"/>
                </Grid>
            </Border>
        </DataTemplate>
    </Window.Resources>
    
    <StackPanel>
        <TextBlock Name="blah" FontSize="20" Text="My Task List:"/>
        <ListBox Width="400" Margin="10"
             ItemsSource="{Binding Source={StaticResource myTodoList}}"
             ItemTemplate="{StaticResource myTaskTemplate}"/>
    </StackPanel>

DataTriggerを使ってみる

イメージ4

f:id:nprogram:20180725230736p:plain

[MainWindow.xamlの一部]

        <DataTemplate x:Key="myTaskTemplate">
            <Border Name="border" BorderBrush="Aqua" BorderThickness="1"
          Padding="5" Margin="5">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" Text="Task Name:"/>
                    <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=TaskName}" />
                    <TextBlock Grid.Row="1" Grid.Column="0" Text="Description:"/>
                    <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Description}"/>
                    <TextBlock Grid.Row="2" Grid.Column="0" Text="Priority:"/>
                    <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Path=Priority}"/>
                </Grid>
            </Border>
            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding Path=TaskType}">
                    <DataTrigger.Value>
                        <local:TaskType>Home</local:TaskType>
                    </DataTrigger.Value>
                    <Setter TargetName="border" Property="BorderBrush" Value="Yellow"/>
                </DataTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </Window.Resources>

データオブジェクトのプロパティに基づくDataTemplateの選択

イメージ5

f:id:nprogram:20180726075325p:plain

コード (MainWindow.xaml.csに追加)

    public class TaskListDataTemplateSelector : DataTemplateSelector
    {
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            FrameworkElement element = container as FrameworkElement;

            if (element != null && item != null && item is Task)
            {
                Task taskitem = item as Task;

                if (taskitem.Priority == 1)
                    return
                        element.FindResource("importantTaskTemplate") as DataTemplate;
                else
                    return
                        element.FindResource("myTaskTemplate") as DataTemplate;
            }

            return null;
        }
    }

コード (MainWindow.xaml)に追加

    <Window.Resources>
        <local:Tasks x:Key="myTodoList"/>

        <local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>

        <DataTemplate x:Key="myTaskTemplate">
        //// 省略 ////
        </DataTemplate>
        
        <DataTemplate x:Key="importantTaskTemplate">
            <DataTemplate.Resources>
                <Style TargetType="TextBlock">
                    <Setter Property="FontSize" Value="20"/>
                </Style>
            </DataTemplate.Resources>
            <Border Name="border" BorderBrush="Red" BorderThickness="1"
          Padding="5" Margin="5">
                <DockPanel HorizontalAlignment="Center">
                    <TextBlock Text="{Binding Path=Description}" />
                    <TextBlock>!</TextBlock>
                </DockPanel>
            </Border>
        </DataTemplate>
    </Window.Resources>
    
    <StackPanel>
        <TextBlock Name="blah" FontSize="20" Text="My Task List:"/>
        <ListBox Width="400" Margin="10"
         ItemsSource="{Binding Source={StaticResource myTodoList}}"
         ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
         HorizontalContentAlignment="Stretch"/>
    </StackPanel>

トラブルシューティング

  • xamlの記載において、<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>で、名前空間に存在するにもかかわらず、TaskListDataTemplateSelectorが見つかりません。と指摘される場合があります。その場合は、Visual Studioを再起動すると、直る場合があります。