WPFプログラミング備忘録

WPF DataTemplateSelectorクラスの使い方

WPF

はじめに

この記事ではListBoxコントロールとDataTemplateSelectorクラスを使って同じデータ型のインスタンスを条件の違いによって異なる外観にできることを確認します。

サンプルとなるアプリケーションでは特定のフォルダ直下のファイルとフォルダの一覧をListBoxに表示することにします。

プロジェクトのファイル構成は下図のとおりです。

開発環境は以下のとおりです。

オペレーティングシステムWindows10 x64
Visual StudioMicrosoft Visual Studio Community 2019 Version 16.11.2
.NET Framewrok4.7.2

カスタマイズしていないListBoxコントロール

まずは外観を定義せずにC:\Windowsフォルダ直下のファイル一覧を表示してみます。

XAMLファイルとコードビハインドは以下のとおりです。

<Window x:Class="WpfDataTemplateSelectorSample.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:WpfDataTemplateSelectorSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="540" x:Name="mainWindow">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <TextBlock Text="{Binding ElementName=mainWindow, Path=FullPath, 
                   StringFormat={}{0}フォルダ直下のファイル一覧}" 
                   TextAlignment="Center"
                   FontSize="20"
                   Background="LightSkyBlue"/>
        <ListBox Grid.Row="1"
                 HorizontalContentAlignment="Stretch"
                 ItemsSource="{Binding}"/>
    </Grid>
</Window>
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfDataTemplateSelectorSample
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public string FullPath { get; set; } = @"C:\Windows";

        public MainWindow()
        {
            InitializeComponent();

            var di = new DirectoryInfo(FullPath);

            DataContext = di.GetFileSystemInfos();
        }
    }
}

DirectoryInfo.GetFileSystemInfosメソッドはFileSystemInfoクラスの配列を返します。これをDataContextに設定しています。

実行結果は以下のとおりです。

一目見ただけではファイルとフォルダの区別が付きにくいですね。

例えばファイルとフォルダで外観を変えたい場合はどうすればよいのでしょうか?

ListBoxに表示されている項目は全て同じクラス(FileSystemInfo)のインスタンスだということがポイントとなります。

DataTemplateSelectorを使用する

DataTemplateSelectorクラスを使えば同一クラスのインスタンスでも条件の違いによって異なる外観を適用することができます。

方法はDataTemplateSelectorクラスの派生クラスを作成し、SelectTemplateメソッドをオーバーライドします。

このメソッドの中で条件によって異なるDataTemplateクラスのインスタンスを返せばよいのです。

DataTemplateクラスの使い方については以下の記事をご確認ください。

>> WPF DataTemplateクラスを使ってコントロールの外観をカスタマイズしよう

FileTemplateSelector.csファイルは以下のとおりです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.IO;

namespace WpfDataTemplateSelectorSample
{
    class FileTemplateSelector : DataTemplateSelector
    {
        public string FileTemplate { get; set; }
        public string DirectoryTemplate { get; set; }
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            var fileSystemInfo = (FileSystemInfo)item;

            string templateKey = (fileSystemInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory
                                ? DirectoryTemplate
                                : FileTemplate;

            return ((FrameworkElement)container).FindResource(templateKey) as DataTemplate;

        }
    }
}

FileTemplateSelector.csファイルではDataTemplateSelectorクラスを継承したFileTemplateSelectorクラスを作っています。

SelectTemplateメソッドではファイルかフォルダかによって異なるDataTemplateを返しています。

DataContextに設定したFileSystemInfoクラスの配列の要素すべてに1回ずつSelectTemplateメソッドが呼ばれることになります。

XAMLファイルは以下のとおりです。

<Window x:Class="WpfDataTemplateSelectorSample.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:WpfDataTemplateSelectorSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="540" x:Name="mainWindow">
    <Window.Resources>
        <DataTemplate x:Key="directoryTemplate">
            <Border Background="red" BorderBrush="AliceBlue"
                    BorderThickness="1" Padding="4">
                <TextBlock Text="{Binding Name}"
                           HorizontalAlignment="Left"
                           VerticalAlignment="Center"
                           FontSize="16" Foreground="White"/>
            </Border>
        </DataTemplate>
        <DataTemplate x:Key="fileTemplate">
            <Border Background="White" BorderBrush="DeepSkyBlue"
                    BorderThickness="1" Padding="4">
                <StackPanel Orientation="Horizontal" TextBlock.FontSize="14">
                    <TextBlock Text="{Binding Name}"/>
                    <TextBlock Text="{Binding CreationTime, StringFormat=' 作成日時: {0:yyyy年MM月dd日 H時mm分ss秒}'}"/>
                </StackPanel>
            </Border>
        </DataTemplate>
        <local:FileTemplateSelector x:Key="selector"
                                    FileTemplate="fileTemplate"
                                    DirectoryTemplate="directoryTemplate"/>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <TextBlock Text="{Binding ElementName=mainWindow, Path=FullPath, 
                   StringFormat={}{0}フォルダ直下のファイル一覧}" 
                   TextAlignment="Center"
                   FontSize="20"
                   Background="LightSkyBlue"/>
        <ListBox Grid.Row="1"
                 HorizontalContentAlignment="Stretch"
                 ItemsSource="{Binding}"
                 ItemTemplateSelector="{StaticResource selector}"/>
    </Grid>
</Window>

ここでのポイントはListBoxでItemTemplateSelectorプロパティにFileTemplateSelectorクラスのインスタンスを設定していることです。

少し補足しておくとItemTemplateSelectorはItemsControlクラスのプロパティで、データ型はDataTemplateSelectorです。

public System.Windows.Controls.DataTemplateSelector ItemTemplateSelector { get; set; }

ListBoxクラスはItemsControlクラスを継承しています。

ItemTemplateSelectorプロパティにFileTemplateSelectorクラスのインスタンスを設定することにより、各項目を表示する際に使用されるテンプレートを選択するためのカスタムロジックを提供しているのです。今回の場合はファイルかフォルダかによって異なるテンプレートを選択するというのがカスタムロジックということになります。

コードビハインドに変更はありません。

プログラムの実行結果は以下のとおりです。

赤色の背景色を持つ項目がフォルダでその他はファイルです。

今回はListBoxコントロールとDataTemplateSelectorクラスを使って同じデータ型のインスタンスを条件の違いによって異なる外観にできることを確認しました。

以上です。

コメント