1 テンプレートの概念
WPF では、コントロールはデータとアルゴリズム動作の単なるキャリアであり、抽象的な概念です。コントロール自体の外観や動作については、コントロール データの表現はテンプレートによって定義されますが、Microsoft はテンプレート (Template) を導入することで、データとアルゴリズムの「内容」と「形式」を切り離し、コントロールの外観、動作、データの表示方法などを簡単に作成、変更、調整できます。Button または TextBox の形状がデフォルトで長方形になるのはそのためです。これはデフォルトのテンプレートで定義されているためです (TextBox がデータを入力できる正方形の入力ボックスであるかどうかを決定するのはテンプレートです)。コントロールを詳しく見てみると、各コントロール自体は UI 要素ツリーであり、その内部は多くのサブ要素ノードで構成されています。WPF テンプレートには主に、ControlTemplate (コントロール テンプレート) と DataTemplate (データ テンプレート) の 2 種類があります。
- ControlTemplate: コントロール テンプレートは、コントロールの Template プロパティによって定義され、コントロールの全体的な外観 (コントロール UI 要素ツリーの全体構造) と機能 (トリガーとイベント) を決定するために使用されます。ControlTemplate によって生成されるコントロール ツリーのルートは ControlTemplate のターゲット コントロールであり、このテンプレート コントロールの Template プロパティ値は ControlTemplate インスタンスです。
- DataTemplate: データ テンプレートは、コントロールの xxxTemplate プロパティ (ContentControl の ContentTemplate、ItemsControl の ItemsTemplate など) によって定義され、コントロール データのプレゼンテーション モードを決定するために使用されます。DataTemplate には UI 要素とデータを含めることができ、UI 要素ツリーも構成します。DataTemplate によって生成されたコントロール ツリーのルートは ContentPresenter コントロールです。ContentPresenter コントロールには ContentTemplate プロパティのみがあり、DataTemplate を Content に渡して ControlTemplate にマウントするために使用される Template プロパティはありません。ContentPresenter コントロールは ControlTemplate コントロール ツリー上のノードであるため、DataTemplate コントロール ツリーは ControlTemplate コントロール ツリーのサブツリーです。
2 テンプレートの定義
2.1 データテンプレート DataTemplate
DataTemplate のよく使われる箇所は主に以下の 3 箇所です。最も重要な点は、DataTemplate 内の各コントロールに Binding を設定し、DataTemplate 内の各要素にデータのどの属性が関係するかを指示することです。
- ContentControl の ContentTemplate プロパティ: ほとんど使用されない ContentControl コントロール コンテンツの Content データの外観と表示モードを設定します。たとえば、ボタン内のテキスト、UserControl のコンテンツなどです。
- ItemsControl の ItemTemplate プロパティ: 各リスト [データ項目エントリ] の表示に使用される DataTemplate を取得または設定します。これは (主に) 内部 xxxItem のデータ テンプレートにバインドされていると見なされます。
- GridViewColumn の CellTemplate プロパティ: GridViewColumn の各セル データの外観をカスタマイズします。
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!--定制“数据详情”的数据展示外观-->
<DataTemplate x:Key="detailViewTemplate">
<Border BorderBrush="Black" CornerRadius="6">
<StackPanel Margin="5">
<Border Width="380" Height="200" Background="{Binding Code}"/>
<StackPanel Orientation="Horizontal" Margin="5,0">
<TextBlock Text="Color Name:" FontSize="20" FontWeight="Bold"/>
<TextBlock Text="{Binding Name}" FontSize="20" Margin="5,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5,0">
<TextBlock Text="Color Code:" FontWeight="Bold"/>
<TextBlock Text="{Binding Code}" Margin="5,0"/>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
<!--定制“数据列表条目”的数据展示外观-->
<DataTemplate x:Key="ListItemViewTemplate">
<StackPanel Orientation="Horizontal" Margin="5,0">
<Border Width="20" Height="20" Background="{Binding Code}"/>
<StackPanel Margin="5,10">
<TextBlock Text="{Binding Name}" FontSize="16" FontWeight="Bold"/>
<TextBlock Text="{Binding Code}" FontSize="14"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
<Window x:Class="WPF_Demo.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:WPF_Demo"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="600">
<StackPanel Orientation="Horizontal" x:Name="stackPanel" Margin="5">
<UserControl ContentTemplate="{StaticResource detailViewTemplate}" Content="{Binding SelectedItem,ElementName=listBoxColors}"/>
<StackPanel Width="180">
<ListBox x:Name="listBoxColors" Height="280" Margin="5,0" ItemTemplate="{StaticResource ListItemViewTemplate}"/>
<Button Content="Add" Margin="5" x:Name="bntAdd" Click="bntAdd_Click"/>
</StackPanel>
</StackPanel>
</Window>
- ContentControl (ここでは UserControl) の場合: ContentTemplate を使用することは、コントロールの Content コンテンツを表示するためのテンプレートを提供することと同じです。そして、ContentPresenter は、ControlTemplate にマウントされて表示される DataTemplate のキャリアです。DataTemplate のルート要素です。データをバインドするように Content コンテンツを設定した後、テンプレートは、コントロールの Content プロパティと ContentTemplate プロパティを自動的にバインドします (コンテキスト)
ContentPresenter
。そのため、ContentControl では、Binding を直接使用してコンテキスト データを取得できます。- ItemsControl (ここでは ListBox) の場合:ItemTemplate を使用することは、コントロール内の各データ項目のコンテンツ データを表示するためのテンプレートを提供することと同じです。ListBox の ItemSource が割り当てられると、各データ要素に対して同等の項目コンテナー xxxItem (ContentControl から継承) が自動的に作成され、自動的に Binding を使用して各データ項目コンテナーとデータ要素の間の関連付けとバインディングが確立されます (それぞれlist は各項目の DataContext としてデータを収集します)。
- ListBoxItem を例に挙げます。ListBoxItem は ContentControl から継承します。その Content プロパティは、リスト内の各データ項目を保持します。その ContentTemplate データ テンプレートは、データを表示するためのテンプレートとして ItemsControl の ItemsTemplate から取得されます。そして、ItemsSourceの各データは、Item作成時に自動的にItemのコンテキストDataContextとしてバインドされます。
- 同様に、ItemsPresenter は、ControlTemplate にマウントおよび表示されるリスト コントロールの DataTemplate 全体のキャリアでもあり、DataTemplate のルート要素です (DataTemplate を定義する一連の Items が ItemsPresenter にマウントされ、その後、 ItemsPresenter はリスト コントロールの ControlTemplate UI 要素ツリーにマウントされ、表示されます。
namespace WPF_Demo
{
public partial class MainWindow : Window
{
ObservableCollection<ColorItem> colorList;
public MainWindow()
{
InitializeComponent();
//列表数据源
colorList = new ObservableCollection<ColorItem>()
{
new ColorItem(){
Name="浅绿色",Code="#33FF00"},
new ColorItem(){
Name="深绿色",Code="#006600"},
new ColorItem(){
Name="红色",Code="#FF0033"},
new ColorItem(){
Name="紫色",Code="#9900FF"}
};
//设置上下文
this.listBoxColors.ItemsSource = colorList;
}
private void bntAdd_Click(object sender, RoutedEventArgs e)
{
//动态添加数据
colorList.Add(new ColorItem() {
Name = "未知", Code = "#000000" });
}
}
public class ColorItem
{
public string Name {
get; set; }
public string Code {
get; set; }
}
}
2.2 コントロール テンプレート ControlTemplate
ControlTemplate は DataTemplate の上にあり、コントロール UI 要素ツリー全体の構造と動作を整理するために使用されます。内部的には、Presenter は DataTemplate のデータ コンテンツをバインドしてマウントするために使用されます。ControlTemplate (コントロール テンプレート) は、コントロールの外観とスタイルを定義するために使用されるだけでなく、コントロール テンプレートのトリガー (ControlTemplate.Triggers) を通じてコントロールの動作と応答アニメーションを変更するためにも使用されます。
(1) ボタンの ControlTemplate ソースコード
- TargetType :このControlTemplateの対象となるタイプを取得または設定します。デフォルトは null です。TargetType がテンプレート定義に含まれている場合、ContentPresenter属性は使用できないことに注意してください
null
。- ContentPresenter: DataTemplate のコントロールを実装し、データ内容の表示を実現します。に相当
- TemplateBinding: ControlTemplate は最終的にコントロールに適用されます。このコントロールは、テンプレート ターゲット コントロールと呼ばれます。ControlTemplate のコントロール要素は、TemplateBinding を使用して、そのプロパティをターゲット コントロールのプロパティ値に一方向で関連付け/バインドできます。
TemplateBinding
は、 を使用して構築されたバインディングに似たバインディングの最適化された形式です{Binding RelativeSource={RelativeSource TemplatedParent}}
。TemplateBinding
テンプレートのさまざまな部分をコントロールのさまざまなプロパティにバインドするために使用できます。
(2) ListBoxのControlTemplateソースコード
ItemsControl を定義するには、主に 2 つの手順があります。
1. リスト レイアウトの最も外側のコンテナを収容するように ItemsPanel コンテナを設定します。 2. 子項目の DataTemplate を定義します。
ItemsPanel: ItemsPanelTemplate タイプ。テンプレートを取得または設定します。テンプレートはアイテム コントロール パネルのレイアウトを定義します。リスト レイアウトを保持する外側のコンテナです。パネル ファミリ要素のみを使用できます。ItemsControlのデフォルト値はStackPanelです
3 テンプレートの使用
ここでは、ItemsControl のカスタム テンプレートを例として、2 つの実用的な例を使用して、テンプレートの実際のアプリケーションを紹介します。
- IsItemsHost プロパティ: この例では、必須の重要なプロパティは IsItemsHost プロパティです。IsItemsHost プロパティは、生成された要素を項目のリストを処理する ListBox コントロールなどの ItemsControl のテンプレート内のどこに配置するかを示すために使用されます。StackPanel のこのプロパティを true に設定すると、ListBox に追加されたすべての項目が StackPanel に入ります。このプロパティはパネル タイプに対してのみ有効であることに注意してください。この方法で ControlTemplate でパネルを指定し、それを IsItemsHost としてマークすると、コントロールのユーザーは ControlTemplate を使用せずに ItemsPanel を置き換えることができないことに注意してください。したがって、パネルを置き換えるのに必ずテンプレートを使用する必要がある場合を除き、この方法は行わないでください。
- ItemsPresenter と ContentPresenter: あるいは、ItemsPresenter 要素を使用して項目の位置をマークし、ItemsPanel プロパティを設定して ItemsPanelTemplate を指定することもできます。ContentControl (Button など) のテンプレートを作成する場合、対応する要素は ContentPresenter です。同様に、この要素を ContentControl タイプの ControlTemplate に配置すると、コンテンツが表示される場所が示されます。
(1) 例1
<!-- 1.声明自定义ItemsControl控件 -->
<ItemsControl ItemsSource="{Binding Scans}">
<!-- 2.定义ItemsControl的整体样式模板ControlTemplate -->
<ItemsControl.Template>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<ScrollViewer HorizontalAlignment="Stretch" VerticalScrollBarVisibility="Hidden">
<!--IsItemsHost 属性用于指示在 ItemsControl(如处理项列表的 ListBox 控件)的模板中,生成的元素应放在什么位置。用于代替ItemsPresenter -->
<StackPanel IsItemsHost="True" Orientation="Vertical" />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<!-- 3.定义ItemsControl每个数据项的展示模板DataTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl x:Name="CardPresenter">
<RadioButton
Height="35"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="{Binding ScanName}"
FontSize="20"
Foreground="White"
GroupName="scanbtn">
<RadioButton.Style>
<Style BasedOn="{StaticResource ScanButtonStyle}" TargetType="RadioButton">
<Setter Property="Background" Value="{Binding Status, Converter={StaticResource bgCV}}" />
</Style>
</RadioButton.Style>
<!-- 声明RadioButton的交互触发器触发行为 -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding ScanClickCommand, Source={x:Static vm:EDESPageStatus.Instance}}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</RadioButton>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
(2) 例2
<!-- 1.声明自定义ItemsControl控件 -->
<ItemsControl
x:Name="itemsControl"
Padding="0,5,10,5"
DataContext="{x:Static vm:ExplorePageStatus.Instance}"
ItemsSource="{Binding Cards}">
<!-- 2.定义ItemsControl的整体样式模板ControlTemplate -->
<ItemsControl.Template>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<Border
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True">
<ScrollViewer VerticalScrollBarVisibility="Hidden">
<!-- 使用 ItemsPresenter 元素来标记数据项列表在整体样式中的的位置(挂载) -->
<ItemsPresenter HorizontalAlignment="Center" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</ItemsControl.Template>
<!-- 3.定义对数据项的布局进行控制的面板Panel(控制数据项的排布方式,作用在ItemsPresenter内数据项列表外) -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal">
<WrapPanel.Style>
<Style TargetType="WrapPanel">
<Setter Property="ItemWidth" Value="420" />
<Setter Property="ItemHeight" Value="326.67" />
</Style>
</WrapPanel.Style>
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- 4.定义ItemsControl每个数据项的展示模板DataTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--内部放置UI元素以及Binding数据(默认来自ItemsSource的每一项)-->
<Grid x:Name="OuterGrid">
<Grid x:Name="InnerGrid" DataContext="{x:Static vm:ExplorePageStatus.Instance}">
<Border
x:Name="ButtonBorder"
Margin="8,4.8"
Background="Transparent"
BorderBrush="{c:Binding 'IsChecked ? \'Yellow\' : \'Transparent\'',
Mode=OneWay}"
BorderThickness="3"
DataContext="{Binding DataContext, ElementName=OuterGrid}">
<Grid DataContext="{Binding DataContext, ElementName=OuterGrid}">
<Canvas Panel.ZIndex="1">
<Label
Canvas.Left="10"
Canvas.Top="12"
Width="40"
Height="19.2"
Padding="0"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Background="{c:Binding '(Phase == e:PhaseEnum.ED ? \'#FF6B6B\' : \'#44D7B6\')',
Mode=OneWay}"
BorderThickness="0"
Content="{c:Binding '(Phase == e:PhaseEnum.ED ? \'ED\' : \'ES\')',
Mode=OneWay}"
FontFamily="PingFangSC-Semibold"
FontSize="19px"
FontWeight="SemiBold"
Foreground="White" />
<Label
Canvas.Left="5"
Canvas.Bottom="10"
Width="30"
Height="20"
Padding="0"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Background="{c:Binding '(LVChecked ? \'#9ACD32\' : \'#C0C0C0\')',
Mode=OneWay}"
BorderThickness="0"
Content="LV"
FontFamily="PingFangSC-Semibold"
FontSize="16px"
FontWeight="SemiBold"
Foreground="White" />
<Label
Canvas.Left="40"
Canvas.Bottom="10"
Width="30"
Height="20"
Padding="0"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Background="{c:Binding '(LAChecked ? \'#9ACD32\' : \'#C0C0C0\')',
Mode=OneWay}"
BorderThickness="0"
Content="LA"
FontFamily="PingFangSC-Semibold"
FontSize="16px"
FontWeight="SemiBold"
Foreground="White" />
<Label
Canvas.Left="75"
Canvas.Bottom="10"
Width="30"
Height="20"
Padding="0"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Background="{c:Binding '(RVChecked ? \'#9ACD32\' : \'#C0C0C0\')',
Mode=OneWay}"
BorderThickness="0"
Content="RV"
FontFamily="PingFangSC-Semibold"
FontSize="16px"
FontWeight="SemiBold"
Foreground="White" />
<Label
Canvas.Left="110"
Canvas.Bottom="10"
Width="30"
Height="20"
Padding="0"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Background="{c:Binding '(RAChecked ? \'#9ACD32\' : \'#C0C0C0\')',
Mode=OneWay}"
BorderThickness="0"
Content="RA"
FontFamily="PingFangSC-Semibold"
FontSize="16px"
FontWeight="SemiBold"
Foreground="White" />
</Canvas>
<Image
Panel.ZIndex="0"
Source="{c:Binding '(Phase == e:PhaseEnum.ED ? EDImage : ESImage)',
Mode=OneWay}"
Stretch="Uniform" />
</Grid>
</Border>
</Grid>
<!--每个数据项的设备输入行为-->
<Grid.InputBindings>
<MouseBinding
Command="{Binding Source={x:Static vm:ExplorePageStatus.Instance}, Path=KeyFrameDetailsCommand}"
CommandParameter="{Binding ElementName=OuterGrid, Path=DataContext}"
MouseAction="LeftClick" />
</Grid.InputBindings>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
4. テンプレート機構の内部原理
(1) 原則: ItemsControl のデフォルト テンプレート内の ItemsPresenter はプレースホルダー (プレースホルダー) としてのみ機能します。その主な役割は、ItemsControl の ItemsPanelTemplate テンプレートを受け取り、ItemsControl がテンプレートを適用するときにこのテンプレートを適用することです。このとき、ItemsControl の Template テンプレート内の ItemsPresenter が ItemsControl の ItemsPanel テンプレートを適用すると、テンプレート内のPanel クラス コントロールの TemplateParent を ItemsControl に設定し、その IsItemsHost プロパティを true としてマークします。ItemsControl のもう 1 つの使用法は、ItemsPanel を無視して、そのテンプレートで「ItemsPanel」を直接指定することです。これは、前述の IsItemsHost を指定する方法です。このとき、ItemsPanel テンプレートの設定は直接無視されます。ただし、この時点でこのパネルの IsItemsHost を True に設定する必要があります。そうしないと、ItemsControl はリスト項目を表示するための適切な ItemsPanel を見つけることができません。
(2) まとめ: ItemsControl のテンプレート適用メカニズムを上から下に整理しましょう。つまり、ItemsControl がテンプレートを適用するとき、最初に Template テンプレート (ControlTemplate 型) を適用して、独自のビジュアル ツリー (Control) を生成します。クラス テンプレート メカニズム)、Template テンプレートの ItemsPresenter がその TemplateParent (つまり ItemsControl) の ItemsPanel テンプレート (ItemsPanelTemplate タイプ) を適用してビジュアル ツリーを生成し、そのビジュアル ツリーを ItemsPresenter ( ItemsPresenter は、この時点ではプレースホルダーの役割として機能します)。ItemsPanel テンプレートが適用されると、このパネルの TemplateParent はこの ItemsControl を指し、その IsItemsHost プロパティは true としてマークされます。ItemsControl の ItemContainerGeneror は、独自の ItemsInternal リストを走査して、各リスト項目 (項目) のコンテナー (ItemContainer) を生成し、ItemsControl の ItemTemplate テンプレートをこのコンテナーに「転送」します。これにより、このコンテナーはテンプレート (ItemTemplate ) を適用し、ビジュアルを生成できます。このItemTemplateによって対応するデータ項目(item)に対して定義されるツリー。