WPFプログラミング備忘録

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

WPF

はじめに

この記事ではDataTemplateクラスを使ってButtonやListBoxやComboBoxの外観をカスタマイズできることを確認します。

今回はListBoxで説明を続けることにします。

ButtonとComboBoxは同様の方法で出来るので最後に軽く触れることにします。

プロジェクトのフォルダとファイルの構成については下図のとおりです。

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

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

カスタマイズしていないListBox

以下のとおり表示データとなるCatクラス(野良猫)を定義します。

Catクラスには種類を表すKindプロパティと年齢を表すAgeプロパティを持たせます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfDataTemplateSample
{
    //野良猫クラス
    class Cat
    {
        //猫の種類
        public string Kind { get; set; }
        //猫の年齢
        public int Age { get; set; }

        public override string ToString()
        {
            return string.Format("{0}は{1}歳です", Kind, Age);
        }
    }
}

CatインスタンスをListBoxコントロールに表示するコードは以下のとおりです。

<Window x:Class="WpfDataTemplateSample.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:WpfDataTemplateSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="400">
    <Grid>
        <ListBox ItemsSource="{Binding}" Width="200" Height="250" HorizontalContentAlignment="Stretch" />
    </Grid>
</Window>
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.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;
using System.Collections.ObjectModel;

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

            DataContext = new ObservableCollection<Cat>
            {
                new Cat{ Kind = "三毛猫", Age = 6},
                new Cat{ Kind = "黒猫", Age = 10},
                new Cat{ Kind = "茶トラ", Age = 8},
            };
        }
    }
}

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

実行結果を見ると素のListBoxは冴えない外観をしています。

例えばフォントの色や背景色、またテキストの配置などを変えたい場合はどうすればよいのでしょうか?

ListBoxをカスタマイズする

ListBoxのItemTemplateプロパティ

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

知っておくべきことはItemsControlクラスにはDataTemplate型のItemTemplateプロパティが定義されていることです。

カスタマイズ後のListBox

カスタマイズしたXAMLファイルは以下のとおりです。コードビハインドに変更はありません。

<Window x:Class="WpfDataTemplateSample.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:WpfDataTemplateSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="400">
    <Grid>
        <ListBox ItemsSource="{Binding}" Width="200" Height="300" HorizontalContentAlignment="Stretch">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Border BorderBrush="Gold" BorderThickness="2" Background="AntiqueWhite">
                        <StackPanel Margin="4">
                            <TextBlock Foreground="Green"
                                       FontSize="30"
                                       Text="{Binding Kind}"
                                       TextAlignment="Center"
                                       Padding="0, 20, 0, 0"/>
                            <TextBlock FontSize="14"
                                       Text="{Binding Age, StringFormat={}{0:N0}歳}"
                                       TextAlignment="Right"/>
                        </StackPanel>
                    </Border>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

ItemsSourceのBindingマークアップ拡張には何も指定しないでください。

念のために説明しておくと、バインディングソースとして使用できるElementName属性、RelativeSource属性、およびSource属性が全て指定されていない場合、その時点で参照可能なDataContextプロパティを探しにいきます。今回の場合はコードビハインドのコンストラクタでDataContextを設定しているので、これを参照しています。

DataContextプロパティの特徴については以下の記事で確認しています。

>> WPF DataContext プロパティの特徴について

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

だいぶ雰囲気が変わりました。

派生クラスとの混在(ポリモルフィズムへの対応)

さてここでCatクラスの派生クラスPetCatクラス(飼い猫)も1つのリストボックスに混在させたいとします。

そしてCatとPetCatでは異なる外観を指定したいとします。どうすればよいのでしょうか?

PetCatクラスの定義は以下のとおりです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfDataTemplateSample
{
    class PetCat : Cat
    {
        //飼い主の顔を表すイメージ
        public Uri Face { get; set; }
    }
}

PetCatクラスには飼い主の顔のイメージを追加しています。

CatとPetCatに異なる外観を持たせたい場合はDataTemplateクラスのDataTypeプロパティを使用します。

DataTypeプロパティを使えば、型の違いによってそれぞれ異なる外観を持つようにテンプレートを別々に定義することができるのです。

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

<Window x:Class="WpfDataTemplateSample.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:WpfDataTemplateSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="500" Width="450">
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:Cat}">
            <Border BorderBrush="Gold" BorderThickness="2" Background="AntiqueWhite">
                <StackPanel Margin="4">
                    <TextBlock Foreground="Green"
                                       FontSize="30"
                                       Text="{Binding Kind}"
                                       TextAlignment="Center"
                                       Padding="0, 20, 0, 0"/>
                    <TextBlock FontSize="14"
                                       Text="{Binding Age, StringFormat={}{0:N0}歳}"
                                       TextAlignment="Right"/>
                </StackPanel>
            </Border>
        </DataTemplate>

        <DataTemplate DataType="{x:Type local:PetCat}">
            <Border BorderBrush="Black" BorderThickness="2" Background="WhiteSmoke">
                <StackPanel Orientation="Horizontal" Margin="4">
                    <Image Source="{Binding Face}" Width="32" Height="32"/>
                    <TextBlock Foreground="DeepSkyBlue"
                                       FontSize="20"
                                       Text="{Binding Kind}"
                                       TextAlignment="Center"/>
                </StackPanel>
            </Border>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ListBox ItemsSource="{Binding}" Width="300" Height="400" HorizontalContentAlignment="Stretch"/>
    </Grid>
</Window>

DataTemplateの定義はリソースに移動しています。

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

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.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;
using System.Collections.ObjectModel;

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

            DataContext = new ObservableCollection<Cat>
            {
                new Cat{ Kind = "三毛猫", Age = 6 },
                new PetCat{ Kind = "マンチカン", Age = 2, Face = new Uri("Images/taro.png", UriKind.Relative) },
                new Cat{ Kind = "黒猫", Age = 10 },
                new PetCat{ Kind = "アメリカン・ショートヘア", Age = 4, Face = new Uri("Images/hanako.png", UriKind.Relative) },
                new Cat{ Kind = "茶トラ", Age = 8 },
            };
        }
    }
}

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

CatとPetCatでは異なる外観を持っていることがわかります。

ButtonとComboBoxのカスタマイズ

Buttonも似たような方法でカスタマイズできます。

ButtonクラスはContentControlクラスを継承しています。

知っておくべきことはContentControlクラスにはDataTemplate型のContentTemplateプロパティが定義されていることです。ListBoxのItemTemplateプロパティと同じ型なので同様にカスタマイズできます。

また、ComboBoxクラスはListBoxクラスと同様にItemsControlクラスを継承しているので ItemTemplateプロパティを使用すれば同じようにカスタマイズできます。

今回はDataTemplateクラスを使ってListBoxやButtonやComboBoxの外観をカスタマイズできることを確認しました。

以上です。

コメント