模板(Template):

每个控件都有自己的视觉外观,比如,我们一眼就能分清楚Button和CheckBox两个按钮。为什么?那是因为这两种按钮呈现出来的外观完全不一样。WPF为每一种控件都提供了一个默认的视觉外观,同时支持开发者去重写这个视觉外观,只需要将重写的视觉外观赋值到Template属性即可——这就是Template模板的由来。

LogicalTree逻辑树:

LogicalTreeHelper 类提供用于逻辑树遍历的 GetChildren、GetParent 和 FindLogicalNode 方法。

示例:后端代码

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

this.DataContext = new MainViewModel();

}

private void Button_Click(object sender, RoutedEventArgs e)

{

TreeViewItem item = new TreeViewItem() { Header = "逻辑树根" };

LogicalTree(item, this);

_TreeView.Items.Add(item);

}

private void LogicalTree(TreeViewItem item, object element)

{

if (!(element is DependencyObject)) return;

TreeViewItem treeViewItem = new TreeViewItem { Header = element.GetType().Name };

item.Items.Add(treeViewItem);

var elements = LogicalTreeHelper.GetChildren(element as DependencyObject);

foreach (object child in elements)

{

LogicalTree(treeViewItem, child);

}

}

}

前端代码:

<Window x:Class="HelloWorld.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:HelloWorld"

xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"

mc:Ignorable="d" FontSize="14"

Title="WPF中文网 - 模板 - www.wpfsoft.com" Height="350" Width="500">

<Grid>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="auto"/>

<ColumnDefinition/>

</Grid.ColumnDefinitions>

<Border Grid.Column="0" x:Name="_LeftBorder" Width="188" Background="LightCyan">

<Button Content="当前逻辑树" Click="Button_Click" HorizontalAlignment="Center" VerticalAlignment="Center"/>

</Border>

<Border Grid.Column="1" x:Name="_RightBorder" >

<TreeView x:Name="_TreeView" Margin="5"/>

</Border>

</Grid>

</Window>

VisualTree可视化树:

VisualTreeHelper 类提供 GetChild、GetParent和 GetChildrenCount方法。

示例:

前端代码

<Window x:Class="HelloWorld.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:HelloWorld"

xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"

mc:Ignorable="d" FontSize="14"

Title="WPF中文网 - 模板 - www.wpfsoft.com" Height="350" Width="500">

<Grid>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="auto"/>

<ColumnDefinition/>

</Grid.ColumnDefinitions>

<Border Grid.Column="0" x:Name="_LeftBorder" Width="188" Background="LightCyan">

<Button Content="当前可视化树" Click="Button_Click" HorizontalAlignment="Center" VerticalAlignment="Center"/>

</Border>

<Border Grid.Column="1" x:Name="_RightBorder" >

<TreeView x:Name="_TreeView" Margin="5"/>

</Border>

</Grid>

</Window>

我们没有修改XAML代码的结构,以便和逻辑树进行对比。

后端代码

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

this.DataContext = new MainViewModel();

}

private void Button_Click(object sender, RoutedEventArgs e)

{

TreeViewItem item = new TreeViewItem() { Header = "可视化树根" };

VisualTree(item, this)

_TreeView.Items.Add(item);

}

private void VisualTree(TreeViewItem item, object element)

{

if (!(element is DependencyObject)) return;

TreeViewItem treeViewItem = new TreeViewItem { Header = element.GetType().Name };

item.Items.Add(treeViewItem);

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element as DependencyObject); i++)

{

VisualTree(treeViewItem,VisualTreeHelper.GetChild(element as DependencyObject, i));

}

}

}

ControlTemplate控件模板:

了解一下模板与样式的区别。

比如一个人的肤色、臂长、身高、五官等,这个可以通过样式设定,所以大街上我们可以看到形形色色的人,有的是白种人,有的是黄种人,有的身高1米5,有的身高2米,有的五官好看,有的不好看。但是这些人都有皮肤、两只手、两只眼睛等等。这是因为他们的模板都是相同的。

咦?那有的人生下来就只有一只手,或者一只眼,这做何解释?这就是因为他的模板与大多数人不一样。

我们再以WPF中的Button为例。默认情况下,Button按钮的内容只能显示文字,我们可以设置它的Content属性即可。也可以设置它的Width和Height,改变它的尺寸,但是,它始终是一个矩形的按钮。假如我们希望得到一个圆形的按钮、或者带图标的按钮,这个时候就需要去改变按钮的内部结构外观——ControlTemplate控件模板。

什么是ContentPresenter对象?

ContentPresenter继承于FrameworkElement,说明它也是一个控件。从命名上看,它叫内容主持者,本质上它只是一个占座的,为谁占座?为ContentControl内容控件占座。因为Button继承于ContentControl,所以Button也有Content属性,在ContentTemplate中的ContentPresenter可视为等于Content属性。

控件模板的几种设置方式:

1:将ControlTemplate定义在控件中

<Button Content="将ControlTemplate定义在在控件中"

Width="280" Height="40" Margin="10" Foreground="#747787">

<Button.Template>

<ControlTemplate TargetType="Button">

<Border Background="Transparent" CornerRadius="5" BorderThickness="1" BorderBrush="#C9CCD5">

<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>

</Border>

</ControlTemplate>

</Button.Template>

</Button>

2:将ControlTemplate定义在资源中

<Window.Resources>

<ControlTemplate x:Key="ButtonTemplate" TargetType="Button">

<Border Background="#C6D2FC" CornerRadius="5" BorderThickness="1" BorderBrush="#545BAD">

<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>

</Border>

</ControlTemplate>

</Window.Resources>

<Button Content="将ControlTemplate定义在资源中"

Template="{StaticResource ButtonTemplate}"

Width="280" Height="40" Margin="10" Foreground="#707CA5"/>

3:将ControlTemplate定义在Style样式中

<Button Content="将ControlTemplate定义在Style样式中"

Width="280" Height="40" Margin="10" Foreground="White">

<Button.Style>

<Style TargetType="Button">

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="Button">

<Border Background="#7AAB7D" CornerRadius="5">

<Grid>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="auto"/>

<ColumnDefinition/>

</Grid.ColumnDefinitions>

<TextBlock Grid.Column="0" Text="☻"

VerticalAlignment="Center"

Margin="3" FontSize="18"/>

<ContentPresenter Grid.Column="1"

HorizontalAlignment="Center"

VerticalAlignment="Center"/>

</Grid>

</Border>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

</Button.Style>

</Button>

ControlTemplate的触发器:

1:在控件中的ControlTemplate的触发器

我们之前在《样式》章节中学过几种触发器,在这里用例子演示一下ControlTemplate的触发器的用法。

<Button Content="将ControlTemplate定义在在控件中"

Width="280"

Height="40"

Margin="10"

Foreground="#747787">

<Button.Template>

<ControlTemplate TargetType="Button">

<Border x:Name="border"

Background="Transparent"

CornerRadius="5"

BorderThickness="1"

BorderBrush="#C9CCD5">

<ContentPresenter x:Name="contentPresenter"

HorizontalAlignment="Center"

VerticalAlignment="Center"/>

</Border>

<ControlTemplate.Triggers>

<Trigger Property="IsMouseOver" Value="True">

<Setter Property="Content" Value="MouseOver" TargetName="contentPresenter"/>

</Trigger>

<Trigger Property="IsMouseOver" Value="False">

<Setter Property="Content" Value="将ControlTemplate定义在在控件中" TargetName="contentPresenter"/>

</Trigger>

</ControlTemplate.Triggers>

</ControlTemplate>

</Button.Template>

</Button>

2:在Resources定义的ControlTemplate的触发器

<Window.Resources>

<ControlTemplate x:Key="ButtonTemplate" TargetType="Button">

<Border Background="#C6D2FC"

CornerRadius="5"

BorderThickness="1"

BorderBrush="#545BAD">

<ContentPresenter HorizontalAlignment="Center"

VerticalAlignment="Center"/>

</Border>

<ControlTemplate.Triggers>

<Trigger Property="IsMouseOver" Value="True">

<Setter Property="Width" Value="300"/>

</Trigger>

<Trigger Property="IsMouseOver" Value="False">

<Setter Property="Width" Value="280"/>

</Trigger>

</ControlTemplate.Triggers>

</ControlTemplate>

</Window.Resources>

<Button Content="将ControlTemplate定义在资源中"

Template="{StaticResource ButtonTemplate}"

Height="40" Margin="10" Foreground="#707CA5"/>

3:Style样式中的ConControlTemplate的触发器

<Button Content="将ControlTemplate定义在Style样式中"

Width="280" Height="40" Margin="10" >

<Button.Style>

<Style TargetType="Button">

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="Button">

<Border x:Name="border"

CornerRadius="5"

BorderThickness="1"

BorderBrush="#7AAB7D">

<Grid>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="auto"/>

<ColumnDefinition/>

</Grid.ColumnDefinitions>

<TextBlock Grid.Column="0" Text="☻"

VerticalAlignment="Center"

Margin="3" FontSize="18"/>

<ContentPresenter Grid.Column="1"

HorizontalAlignment="Center"

VerticalAlignment="Center"/>

</Grid>

</Border>

<ControlTemplate.Triggers>

<Trigger Property="IsMouseOver" Value="True">

<Setter Property="Foreground" Value="#7AAB7D"/>

<Setter Property="Background" Value="White" TargetName="border"/>

</Trigger>

<Trigger Property="IsMouseOver" Value="False">

<Setter Property="Foreground" Value="White"/>

<Setter Property="Background" Value="#7AAB7D" TargetName="border"/>

</Trigger>

</ControlTemplate.Triggers>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

</Button.Style>

</Button>

TemplateBinding模板绑定:(绑定控件自身依赖属性,下面有二者运用)

TemplateBinding和Binding在使用上类似,但是从它的定义上看,它的Property属性是要求传入一个DependencyProperty依赖属性。

<Style x:Key="CardButtonStyle" TargetType="Button">

<Setter Property="Background" Value="#E7EAF4"/>

<Setter Property="Foreground" Value="#20232E"/>

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="Button">

<Border x:Name="border"

Width="{TemplateBinding Width}"

Height="{TemplateBinding Height}"

Background="{TemplateBinding Background}"

BorderThickness="1"

BorderBrush="Gray">

<Border.ToolTip>

<ContentPresenter/>

</Border.ToolTip>

<Grid>

<Grid.RowDefinitions>

<RowDefinition/>

<RowDefinition/>

</Grid.RowDefinitions>

<StackPanel Grid.Row="0" Margin="20">

<TextBlock Text="{Binding Name}"

Foreground="{TemplateBinding Foreground}"

FontWeight="Bold" FontSize="20"/>

<Rectangle Height="5"/>

<TextBlock Text="{Binding Occupation}"

Foreground="{TemplateBinding Foreground}"

FontSize="16"/>

</StackPanel>

<StackPanel Grid.Row="1" Orientation="Horizontal">

<TextBlock Grid.Column="0" Text="☻"

VerticalAlignment="Center" Margin="20"

FontSize="50" Foreground="#E26441"/>

<StackPanel Margin="30 0 0 0" Width="150">

<TextBlock Text="COMPANY NAME"/>

<TextBlock Text="Age:">

<Run Text="{Binding Age}"/>

</TextBlock>

<TextBlock Text="Money:">

<Run Text="{Binding Money, StringFormat={}{0:C}}"/>

</TextBlock>

<TextBlock Text="Address:" TextWrapping="Wrap">

<Run Text="{Binding Address}"/>

</TextBlock>

</StackPanel>

</StackPanel>

</Grid>

</Border>

<ControlTemplate.Triggers>

<Trigger Property="IsMouseOver" Value="True">

<Setter Property="Background" Value="#7AAB7D" />

</Trigger>

</ControlTemplate.Triggers>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

DataTemplate数据模板:

什么是数据模板?其实就是数据的表现形式——数据外衣。

首先是MainViewModel,新建一个Person的集合作为ItemsControl控件的数据源。

public class MainViewModel : ObservableObject

{

private List<Person> persons = new List<Person>();

public List<Person> Persons

{

get { return persons; }

set { persons = value;RaisePropertyChanged(); }

}

private Person person;

public Person Person

{

get { return person; }

set { person = value; RaisePropertyChanged(); }

}

public MainViewModel()

{

person = new Person()

{

Name = "Michael Jackson",

Occupation = "Musicians",

Age = 25,

Money = 9999999,

Address = "深圳市光明区智慧招商城B4栋5楼"

};

var bill = new Person()

{

Name = "比尔·盖茨(Bill Gates)",

Occupation = "微软公司创始人",

Age = 61,

Money = 9999999,

Address = "美国华盛顿州西雅图"

};

var musk = new Person()

{

Name = "Elon Reeve Musk",

Occupation = "首席执行官",

Age = 50,

Money = 365214580,

Address = "出生于南非的行政首都比勒陀利亚"

};

var jeff = new Person()

{

Name = "杰夫·贝索斯(Jeff Bezos)",

Occupation = "董事会执行主席",

Age = 25,

Money = 85745845,

Address = "杰夫·贝索斯出生于美国新墨西哥州阿尔布奎克。"

};

persons.Add(person);

persons.Add(bill);

persons.Add(musk);

persons.Add(jeff);

}

}

然后在XAML前端代码中实例化一个ItemsControl控件,并将其包含在ScrollViewer之中。

<Grid>

<ScrollViewer>

<ItemsControl ItemsSource="{Binding Persons}">

<ItemsControl.ItemTemplate>

<DataTemplate>

<Border x:Name="border"

Width="280"

Height="200"

Margin="5"

BorderThickness="1"

BorderBrush="Gray">

<Grid>

<Grid.RowDefinitions>

<RowDefinition/>

<RowDefinition/>

</Grid.RowDefinitions>

<StackPanel Grid.Row="0" Margin="20">

<TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="20"/>

<Rectangle Height="5"/>

<TextBlock Text="{Binding Occupation}" FontSize="16"/>

</StackPanel>

<StackPanel Grid.Row="1" Orientation="Horizontal">

<TextBlock Grid.Column="0" Text="☻"

VerticalAlignment="Center" Margin="20"

FontSize="50" Foreground="#E26441"/>

<StackPanel Margin="30 0 0 0" Width="150">

<TextBlock Text="COMPANY NAME"/>

<TextBlock Text="Age:">

<Run Text="{Binding Age}"/>

</TextBlock>

<TextBlock Text="Money:">

<Run Text="{Binding Money, StringFormat={}{0:C}}"/>

</TextBlock>

<TextBlock Text="Address:" TextWrapping="Wrap">

<Run Text="{Binding Address}"/>

</TextBlock>

</StackPanel>

</StackPanel>

</Grid>

</Border>

<DataTemplate.Triggers>

<Trigger Property="IsMouseOver" Value="True">

<Setter Property="Background" Value="#7AAB7D" TargetName="border" />

</Trigger>

</DataTemplate.Triggers>

</DataTemplate>

</ItemsControl.ItemTemplate>

</ItemsControl>

</ScrollViewer>

</Grid>

ItemsPanelTemplate元素模板

ItemsPanelTemplate用于指定集合控件中元素与元素之间的布局的方式,所以,ItemsPanelTemplate其实就是一个布局面板,而我们在前面的章节中已经学习了WPF的面板控件,它们都继承于Panel基类,分别是Grid、UniformGrid、StackPanel、WrapPanel、DockPanel、Canvas等。而在使用ItemsPanelTemplate模板去设置某一个集合控件的元素布局面板时,默认使用StackPanel布局,或者WrapPanel。

例如在上一章节的ItemsControl控件中,我们有4个元素,它们都是垂直排列的。我们可以修改ItemsPanel属性,用以设置元素以瀑布流的方式排列显示。

<ItemsControl.ItemsPanel>

<ItemsPanelTemplate>

<WrapPanel/>

</ItemsPanelTemplate>

</ItemsControl.ItemsPanel>

ListBox的ItemContainerStyle:(ItemControl是ListBox的父类)

众所周知,ListBox继承于ItemsControl控件,那么,它就与ItemsControl一样,拥有了可以设置的数据模板。当然,它也可以拥有自己的控件模板(在Control基类中定义的Template)。这一节,我们只探讨一下ListBox如何使用数据模板。

因为在ListBox的父类ItemsControl中定义了一个ItemContainerStyle的样式,这个样式决定了ListBox控件中每个元素的容器外观。

<ListBox.ItemContainerStyle>

<Style TargetType="ListBoxItem">

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="ListBoxItem">

<Border Background="LightGoldenrodYellow"

Padding="15" Margin="5">

<ContentPresenter/>

</Border>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

</ListBox.ItemContainerStyle>