一般情况下,为集合控件定义了数据模板后,数据视图中的每一个子项都会套用相同的模板,即每个项都呈现相同的布局。然而在某些特殊情况下,可能需要让集合中的一个或多个数据项应用不同的模板。例如,一个呈现实时新闻的数据视图,开发者可能会做这样的安排:如果新闻内容是以文字为主的,则显示新闻标题和摘要如果新闻内容是以图片为主的,即图片新闻,则显示新闻标题和图片的缩略图。
要让数据项能够有选择地加载不同的数据模板,需要实现数据模板选择器,即从DataTemplateSelector类(位于Windows.UI.Xaml.Controls命名空间)派生出一个自定义类型(DataTempalteSelector类不能直接使用),然后将这个自定义类型的实例赋值给集合控件的ItemTemplateSelector属性即可。
在继承DataTemplateSelector类时,要重写基类的虚方法来返回合适的DataTemplate对象,DataTemplateSelector类有两个可以重载的虚方法:
//
// 摘要:
// 返回给定项和容器的特定 DataTemplate。
//
// 参数:
// item:
// 要为其返回模板的项。
//
// container:
// 模版项的父容器。
//
// 返回结果:
// 用于特定项目和/或容器的模板。
public DataTemplate SelectTemplate(object item, DependencyObject container);
//
// 摘要:
// 返回给定项的特定 DataTemplate。
//
// 参数:
// item:
// 要为其返回模板的项。
//
// 返回结果:
// 用于特定项目和/或容器的模板。
[Overload("SelectTemplateForItem")]
public DataTemplate SelectTemplate(object item);
在编写自定义的数据模板选择器时,应当重写第二种重载的方法,因为集合控件在分析数据模板时调用的是第二种重载方法。item参数引用的是数据视图中的项,即放入集合控件Items集合中的对象;container参数引用呈现数据项的容器控件,该容器一般是ContentControl的派生类,即将数据项通过Content属性来呈现。不同的集合控件所使用的项容器控件不同,例如,ListBox控件中的想容器为ListBoxItem控件,ListView控件中的项容器为ListViewItem控件,ComboBox控件中的项容器为ComboBoxItem控件。。。可以发现这些命名都是有规律的,基本格式为"<集合控件类名>Item"。
下面示例将制作一个显示聊天记录的数据视图,其中就用到数据模板选择器。
定义一个MessageEntity类,表示单条聊天记录的信息,代码如下:
class MessageEntity
{
///<summary>
///消息类型
///</summary>
public enum MsgType
{
///<summary>
///接收到的消息
///</summary>
From,
///<summary>
///发送的消息
///</summary>
To
}
///<summary>
///消息内容
///</summary>
public string Content { get; set; }
///<summary>
///消息类型
///</summary>
public MsgType MessageType { get; set; }
}
然后实现一个自定义的数据模板选择器:
sealed class CustomDataTemplateSelector : DataTemplateSelector
{
///<summary>
///显示收到的消息的模版
///</summary>
public DataTemplate MessageFromTemplate { get; set; }
///<summary>
///显示已发送消息的模版
///</summary>
public DataTemplate MessageToTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
MessageEntity msgent = item as MessageEntity;
if (msgent != null)
{
//判断消息类型,返回对应的模版
if (msgent.MessageType is MessageEntity.MsgType.From)
{
return MessageFromTemplate;
}
return MessageToTemplate;
}
return null;
}
}
CustomDataTempalteSelector类公开了两个属性,类型均为DataTemplate,这是方便在XAML文档中引用不同的数据模板资源。重写SelectTemplateCore方法,根据MessageEntity实例的MessageType属性来决定使用哪个模板。由于稍后会向集合控件中添加MessageEntity实例,因此传递给SelectTemplateCore方法的item参数的对象就是一个MessageEntity实例引用。
一般来说,收到的消息在屏幕上左对齐,用户头像在左边,消息正文在右边;而发出去的消息则在屏幕上右对齐,用户头像在右边,消息正文在左边。因此需要在XAML文档中分别声明这两种数据模板。
<Page.Resources>
<ResourceDictionary>
<DataTemplate x:Key="from">
<Grid HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Width="40" Height="40" VerticalAlignment="Top" Source="ms-appx:///Assets/meinv_01.jpg"/>
<Border Grid.Column="1" Margin="8,0" Padding="10" Background="DarkBlue">
<TextBlock FontSize="20" Text="{Binding Content}" TextWrapping="Wrap"/>
</Border>
</Grid>
</DataTemplate>
<DataTemplate x:Key="to">
<Grid HorizontalAlignment="Right">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Image Width="40" Grid.Column="1" Height="40" VerticalAlignment="Top" Source="ms-appx:///Assets/meinv_02.jpg"/>
<Border Margin="8,0" Padding="10" Background="Green">
<TextBlock FontSize="20" Text="{Binding Content}" TextWrapping="Wrap"/>
</Border>
</Grid>
</DataTemplate>
</ResourceDictionary>
</Page.Resources>
接着在用户界面中放置一个ListView控件,并让它的ItemTemplateSelector属性引用前面定义的CustomDataTemplateSelector实例。
<Grid>
<ListView x:Name="lvMsgs" Margin="30,15">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="0,20"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplateSelector>
<local:CustomDataTemplateSelector MessageFromTemplate="{StaticResource from}"
MessageToTemplate="{StaticResource to}"/>
</ListView.ItemTemplateSelector>
</ListView>
</Grid>
由于ListView控件中的子项容器为ListViewItem类,所以ItemContainerStyle属性所设置的样式的目标类型应该面向ListViewItem类,设置HorizontalContentAlignmente属性的值为Strech,目的是让每个数据项在用户界面进行横向填充,只有项目在界面填充所有的空间后,数据模板的Grid控件才有可能向左或向右对齐,如果使用默认对其方案,那么所有子项都会偏向左方,就无法满足布局需求了。
最后在页面类的构造方法中为ListView控件设置数据源。
public MainPage()
{
this.InitializeComponent();
lvMsgs.ItemsSource = new MessageEntity[]
{
new MessageEntity{MessageType = MessageEntity.MsgType.From,Content = "小明,你今天在家吗?"},
new MessageEntity{MessageType = MessageEntity.MsgType.To,Content = "在啊。"},
new MessageEntity{MessageType = MessageEntity.MsgType.From,Content = "待会儿帮你把东西送过去。"},
new MessageEntity{MessageType = MessageEntity.MsgType.To,Content = "好的,十分感谢。"}
};
}