模板(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">
<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" >
<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>