WPF 模板总结2
一、数据模板
继承了ItemConrol的控件对象(如ListView、ListBox、DataGrid、TabControl等等),都可以使用数据模板DataTemplate。
数据模板的作用在于决定每个Item中的数据的展示形式。
普通控件通过Template属性来定义模板,而子项容器控件则通过ItemTemplate属性来定义子项模板。
先创建作为数据源对象的类
public class Person { public string Name { get; set; } public int Age { get; set; } public int Gender { get; set; } public double Left { get; set; } public double Top { get; set; } }
在xaml中定义数据集合,然后在子项容器控件中通过ItemTemplate
属性定义子项数据模板。
<Window ...... xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WPFStudy" ......> <Window.Resources> <!--数据集合--> <x:Array Type="local:Person" x:Key="persons"> <local:Person Name="Hello" Age="20" Gender="1" Left="10" Top="50"/> <local:Person Name="Schuyler" Age="21" Gender="2"/> <local:Person Name="Cai" Age="22" Gender="1" Left="100" Top="250"/> </x:Array> </Window.Resources> <Grid> <ItemsControl ItemsSource="{StaticResource persons}"> <ItemsControl.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Name}"/> <TextBlock Text="{Binding Age}" Grid.Column="1"/> <TextBlock Text="{Binding Gender}" Grid.Column="2"/> <TextBlock Text="{Binding Left}" Grid.Column="3"/> <TextBlock Text="{Binding Top}" Grid.Column="4"/> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Grid> </Window>
二、子项容器模板
数据模板决定了数据的展示形式,而子项容器模板则决定了数据模板的展示形式。
子项容器模板可以通过子项容器控件的ItemsPanel
进行设置。
<Window.Resources> <!--数据集合--> <x:Array Type="local:Person" x:Key="persons"> <local:Person Name="Hello" Age="20" Gender="1" Left="10" Top="50"/> <local:Person Name="Schuyler" Age="21" Gender="2"/> <local:Person Name="Cai" Age="22" Gender="1" Left="100" Top="250"/> </x:Array> </Window.Resources> <Grid> <ItemsControl ItemsSource="{StaticResource persons}"> <!--这里是子项容器模板--> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <!--这里是数据模板--> <ItemsControl.ItemTemplate> <DataTemplate> <Grid Canvas.Top="{Binding Top}" Canvas.Left="{Binding Left}"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Name}"/> <TextBlock Text="{Binding Age}" Grid.Column="1"/> <TextBlock Text="{Binding Gender}" Grid.Column="2"/> <TextBlock Text="{Binding Left}" Grid.Column="3"/> <TextBlock Text="{Binding Top}" Grid.Column="4"/> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Grid>
数据模板决定了每个Item中的数据怎么展示出来,而子项容器模板则决定了每个Item怎么展示出来。
如上述代码所示,会将ItemsControl
的每个Item按序放入到Canvas
中,由于Canvas
的每个子元素都以左上角为原始坐标,所以堆在左上角了。
需要注意的是,在上述代码中数据模板的Grid设置了Canvas.Top和Canvas.Left属性值,但却没有效果,这是因为这两个属性只针对Canvas的直接子元素起作用。
然而ItemControl的数据模板中的容器对象(这里是Grid)并不是直接放置到子项容器模板的容器对象(这里是Canvas)中的,而是先将Grid放入到ContentPresenter元素后,
再将ContentPresenter元素放置到Canvas中。
因此正确的做法应该是在ItemsControl的ItemContainerStyle属性中对ContentPresenter进行样式设置。
<Grid> <ItemsControl ItemsSource="{StaticResource persons}"> <!--应该在这里编写子项容器样式--> <ItemsControl.ItemContainerStyle> <Style TargetType="ContentPresenter"> <Setter Property="Canvas.Top" Value="{Binding Top}"/> <Setter Property="Canvas.Left" Value="{Binding Left}"/> </Style> </ItemsControl.ItemContainerStyle> ...... </ItemsControl> </Grid>
完整的渲染过程(ItemsControl中的ItemsPresenter与ContentPresenter)
为了便于理解,这里简单描述一下ItemsControl的渲染过程,顺便缕一缕ItemsPresenter与ContentPresenter在ItemsControl控件中的作用
ItemsControl在渲染时,使用一个名为 ItemsPresenter 的控件来展示各个子项,主要用来控制如何布局(使用ItemsPanelTemplate中设定的容器)和显示这些子项,但它不是用于承载单个子项内容的控件。
ItemsControl在渲染时,会使用ContentPresenter控件来展示单个子项内容,并且ContentPresenter会放置子项容器中(使用ItemsPanelTemplate中设定的容器)
注意,并不是所有控件都是将子项放入ContentPresenter元素中的,例如ListView就是将子项放入ListViewItem中再放入子项容器模板的容器对象中的,ComboBox则是放入到ComboBoxItem中然后再放入到容器对象中的,在使用时可以配合snoop软件进行查看。
三、层级数据模板
继承了ItemsControl的控件,除了可以使用上面说的DataTemplate数据模板外,还可以使用继承DataTemplate的层级数据模板HeaderedItemsControl,该模板对象一般用于TreeView和Menu等具有层级关系的子项容器控件中。TreeView和Menu控件都是通过ItemTemplate属性来定义层级数据模板。
public class Menu { public string Header { get; set; } public int Index { get; set; } public ObservableCollection<Menu> Children { get; set; } }
public class MainWindowViewModel { public ObservableCollection<Menu> Menus { get; set; } public MainWindowViewModel() { Menus = new ObservableCollection<Menu> { new Menu { Header = "系统配置", Index = 0, Children = new ObservableCollection<Menu>{ new Menu{Header="用户设置", Index=0}, new Menu{Header="权限设置", Index=1} } }, new Menu { Header = "样式配置", Index=1, Children = new ObservableCollection<Menu>{ new Menu{Header="主题", Index=0}, new Menu{Header="图片", Index=1} } } }; } }
<Window ......> <Window.DataContext> <local:MainWindowViewModel/> </Window.DataContext> <Grid HorizontalAlignment="Center" VerticalAlignment="Center"> <TreeView ItemsSource="{Binding Menus}"> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Children}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Index}"/> <TextBlock Text="{Binding Header}" Grid.Column="1"/> </Grid> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Grid> </Window>
四、资源中的数据模板与容器模板
上面的例子中是将数据模板与子项容器模板直接使用在控件中,通过子项容器控件(继承了ItemControl的控件)的ItemTemplate和ItemsPanel属性来进行设置的(内联的用法),样式则是通过子项容器控件的ItemContainerStyle属性来设置。而数据模板、容器模板、子项容器样式的内容与控件模板的内容一样,都可以放置在<Window.Resources>作为资源来使用。
因此,上面的例子可以写成下面的形式:
<Window.Resources> <!--数据集合--> <x:Array Type="local:Person" x:Key="persons"> <local:Person Name="Hello" Age="20" Gender="1" Left="10" Top="50"/> <local:Person Name="Schuyler" Age="21" Gender="2"/> <local:Person Name="Cai" Age="22" Gender="1" Left="100" Top="250"/> </x:Array> <!--数据模板--> <DataTemplate x:Key="dateTemplate"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Name}"/> <TextBlock Text="{Binding Age}" Grid.Column="1"/> <TextBlock Text="{Binding Gender}" Grid.Column="2"/> <TextBlock Text="{Binding Left}" Grid.Column="3"/> <TextBlock Text="{Binding Top}" Grid.Column="4"/> </Grid> </DataTemplate> <!--子项容器模板--> <ItemsPanelTemplate x:Key="itemsPanelTemplate"> <Canvas/> </ItemsPanelTemplate> <!--子项容器样式--> <Style x:Key="itemContainerStyle" TargetType="ContentPresenter"> <Setter Property="Canvas.Top" Value="{Binding Top}"/> <Setter Property="Canvas.Left" Value="{Binding Left}"/> </Style> </Window.Resources> <Grid> <ItemsControl ItemsSource="{StaticResource persons}" ItemTemplate="{StaticResource dateTemplate}" ItemContainerStyle="{StaticResource itemContainerStyle}" ItemsPanel="{StaticResource itemsPanelTemplate}"> </ItemsControl> </Grid>
模板选择器
考虑这么一个场景,子项容器控件中,子项源为Person的数组,每个Person对象中都有年龄Age属性,现在需要在大于20岁时,使用模板A;小于等于20岁时使用模板B。这样的需求显然只靠xaml代码是难以实现的,此时可以配合C#代码,定义一个模板选择器类型,根据不同的条件返回不同的模板对象。
使用模板选择器具体有如下几个步骤
1、定义模板资源
<Window.Resources> <!--数据集合--> <x:Array Type="local:Person" x:Key="persons"> <local:Person Name="Hello" Age="20" Gender="1" Left="10" Top="50"/> <local:Person Name="Schuyler" Age="21" Gender="2"/> <local:Person Name="Cai" Age="22" Gender="1" Left="100" Top="250"/> </x:Array> <DataTemplate x:Key="dataTemplateA"> <Grid> <TextBlock Text="{Binding Name}"/> </Grid> </DataTemplate> <DataTemplate x:Key="dataTemplateB"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Name}"/> <TextBlock Text="{Binding Age}" Grid.Column="1"/> </Grid> </DataTemplate> </Window.Resources>
2、创建模板选择器类型
可以通过查看ItemControl控件的ItemTemplateSelector属性的定义代码,得知其类型为DataTemplateSelector,因此可以创建一个MyDataTemplateSelector类型,继承DataTemplateSelector类,并重写其SelectTemplate函数。
DataTemplate SelectTemplate(object item, DependencyObject container):此函数会接收两个参数,item参数为子项的数据对象,本例中为Person对象;container参数则为子项所在的父类容器对象。
public class MyDataTemplateSelector: DataTemplateSelector { //模板A,可以在xaml中赋值 public DataTemplate TemplateA { get; set; } //模板B,可以在xaml中赋值 public DataTemplate TemplateB { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { var person = item as Person; if (person != null && person.Age > 20) { return TemplateA; } return TemplateB; } }
3、在控件中使用模板选择器
<Window ......> ...... <Grid> <ItemsControl ItemsSource="{StaticResource persons}"> <ItemsControl.ItemTemplateSelector> <local:MyDataTemplateSelector TemplateA="{StaticResource dataTemplateA}" TemplateB="{StaticResource dataTemplateB}" /> </ItemsControl.ItemTemplateSelector> </ItemsControl> </Grid> </Window>
子项容器控件中除了数据模板选择器ItemTemplateSelector外,还有子项容器模板选择器ItemContainerStyleSelector。
其他控件也有自己的模板选择器,比如继承了ContenteControl的控件(例如Button)都会有ContentTemplateSelector模板选择器。
来源:https://blog.csdn.net/jjailsa/article/details/135345658