D 是数据模板

来源:互联网 发布:有关网络暴力的剧本 编辑:程序博客网 时间:2024/04/30 17:40

ItemsControl: 'D' is for DataTemplate

 

术语“rich content model”经常在WPF的圈子里出现。在这篇文章中,我们剖析了内容模型,特别是该模型在ItemsControl上的应用。

1.     WPF内容模型

在WPF中,根据逻辑孩子的类型和数量来分类不同类型的元素。我们把逻辑孩子看作控件的内容。WPF对不同类型的元素定义了一些不同的内容模型。

可以包含单个项的控件(单个类型为Object的逻辑孩子)被称为“内容控件”。这些控件从ContentControl继承。可以包含集合项的控件被称作items controls。这些控件从ItemsControl派生。有的控件还同时包含一个标题头和一个集合项,这种控件叫做“headered items controls”,同样还有包含单个标题头以及单个内容的控件,叫做“headered content controls”,为了完整起见,我需要说明还有其他类型的元素拥有自己的内容模型。例如:TextBlock可以包含一个Inline项的集合,它们是一些从Inline派生的文本元素,用来创建格式化文本。Decorator类用来创建包含单个UIElement类型的孩子的装饰元素。Panel类型用来布局多个UIElement孩子。

2.     ItemsControl的内容模型

那么ItemsControl的内容模型有什么特殊的呢?主要来讲,ItemsControl允许它的逻辑孩子可以是任何CLR对象。这令人印象深刻,传统上讲,Windows开发人员通过组合visual元素来创建UI。但是通过使用WPF rich内容模型,可以通过visual元素和数据项来共同构建一个逻辑UI了。

为了更好的理解这点,考虑下面的例子:

<Window x:Class="HomersListBox.Window1"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:src="clr-namespace:HomersListBox"

   Title="Homer's ListBox" Width="300" Height="400">

 <ListBox Width="200" Height="300">

   <src:Character First="Bart" Last="Simpson" Age="10"

       Gender="Male" Image="images/bart.png" />

   <src:Character First="Homer" Last="Simpson" Age="38"

       Gender="Male" Image="images/homer.png" />

   <src:Character First="Lisa" Last="Simpson" Age="8"

       Gender="Female" Image="images/lisa.png" />

   <src:Character First="Maggie" Last="Simpson" Age="0"

       Gender="Female" Image="images/maggie.png" />

   <src:Character First="Marge" Last="Simpson" Age="38"

       Gender="Female" Image="images/marge.png" />

 </ListBox>

</Window>

 

在这个例子中,逻辑树看上去如下:

Character对象是非常简单的CLR对象(每个属性不再介绍)。当你运行这个示例的时候,你会看到如下窗口:

可以看到,Character对象只是一个字符串,如果WPF不知道如何显示一个对象,那么它只是简单的调用ToString方法来获取一个字符串。

如果你用snoop或者mole在检视这个元素树,你看到WPF在visual tree中插入了一个TextBlock用来显示这个字符串。但是该TextBlock并不是逻辑树的一部分。Visual tree只包含visual元素,但是逻辑树可以包含可视元素和非可视元素。

这个自动创建TextBlock的行为是框架需要显示字符串时候的默认行为,你可以简单的重写ToString方法来改变现实的字符串。如下:

public override string ToString()

   {

       return _first + " " + _last;

   }

 

现在至少显示的数据有些意义了:

 

当然,你可能想要的更多。

3.     什么是模板?

在WPF中,一个模板就是一个可视元素树(跟有一些资源和触发器),被用来定义逻辑树中的一个成员的外观。当构建元素树的时候,框架查找项是否有对应的模板,如果有,模板被展开为实际的可视元素并被插入到相应的visual tree中。

有许多不同的模板,每一个都从FrameworkTemplate继承,最常用的模板就是ControlTemplate和DataTemplate。控件模板用来提供控件的可视化呈现,这种机制使用了WPF无外观控件模型。

数据模板类用来对数据项进行可视化展示。这也是我们用来为Character对象定义可视外观的模板。

定义DataTemplate

在大多数情况下,你将DataTemplate定义为资源。将我们的模板定义如下:

<Window.Resources>

   <DataTemplate x:Key="CharacterTemplate">

     <Grid>

       <Grid.ColumnDefinitions>

         <ColumnDefinition Width="100" />

         <ColumnDefinition Width="*" />

       </Grid.ColumnDefinitions>

       <Image Margin="5" Source="{Binding Image}" />

       <StackPanel Grid.Column="1" Margin="5">

         <TextBlock FontWeight="Bold" Text="{Binding First}" />

       </StackPanel>

     </Grid>

   </DataTemplate>

 </Window.Resources>

 

(模板的定义很简单,不再详述)

WPF会自动将展开的可视化树的根的DataContext属性设置为实际的数据项。一个在WPF论坛中经常被问到的问题是:我如何能得到在模板中定义的一个button所对应的数据项呢?答案非常明显:original source的DataContext属性就是当前数据项:

private void OnButtonClick(object sender, RoutedEventArgs e)

   {

       object item = (e.OriginalSource as FrameworkElement).DataContext;

       . . .

   }

 

4.     对ItemsControl应用数据模板

现在我们定义了模板,如何把模板应用到ItemsControl有多种方法,最简单的方法就是显式设置,如下所示:

<ListBox Width="200" Height="300"

     ItemTemplate="{StaticResourceCharacterTemplate}">

   . . .

 </ListBox>

 

现在运行程序,可以看到模板被应用了:

 

使用指定类型的数据模板

以上示例显式设置了模板。使用这种方法,每个集合中的项都会有相同的模板。对于相同类型的对象,这没有问题。但是如果集合中包含不同类型的对象呢?如下所示:

<Page

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:sys="clr-namespace:System;assembly=mscorlib">

 <ItemsControl Width="100" Height="100">

   <sys:Int32>30</sys:Int32>

   <sys:DateTime>12/16/1970</sys:DateTime>

   <sys:Boolean>True</sys:Boolean>

   <sys:Boolean>False</sys:Boolean>

   <sys:String>Foo</sys:String>

 </ItemsControl>

</Page>

 

结果如下:

 

假定你想要为不同的类型显示不同的数据模板,WPF为你提供了类型指定的模板。

例如你决定为布尔类型使用checkbox,而不是字符串true或者false。为了定义这种模板,你需要指定数据模板的DataType属性,如下:

<Page

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:sys="clr-namespace:System;assembly=mscorlib">

 <Page.Resources>

   <DataTemplate DataType="{x:Type sys:Boolean}">

     <CheckBox IsChecked="{Binding Mode=OneWay}" />

   </DataTemplate>

 </Page.Resources>

 <ItemsControl Width="100" Height="100">

   <sys:Int32>30</sys:Int32>

   <sys:DateTime>12/16/1970</sys:DateTime>

   <sys:Boolean>True</sys:Boolean>

   <sys:Boolean>False</sys:Boolean>

   <sys:String>Foo</sys:String>

 </ItemsControl>

</Page>

 

现在ItemsControl显示如下:

我们并没有为ItemTemplate指定任何模板,但是框架为了显示Bool类型的值,它会在资源中查找匹配bool类型的模板,直到找到了包含Checkbox的模板。

5.     为指定CLR数据类型定义默认模板

在前面的例子中,我们使用资源key定义了一个模板。我们可以使用DataType声明来指定默认数据模板。如下:

<DataTemplate DataType="{x:Type src:Character}">

   <Grid>

     <Grid.ColumnDefinitions>

       <ColumnDefinition Width="100" />

       <ColumnDefinition Width="*" />

     </Grid.ColumnDefinitions>

     <Image Margin="5" Source="{Binding Image}" />

     <StackPanel Grid.Column="1" Margin="5">

       <TextBlock FontWeight="Bold" Text="{Binding First}" />

     </StackPanel>

   </Grid>

 </DataTemplate>

 

这就为在逻辑树中的所有的Character对象提供了一个默认展示。即使我们不为ItemsControl指定,也是如此。更者,如果我们在button中包含一个Character对象,同样,模板也会被应用:

<Button HorizontalAlignment="Center" VerticalAlignment="Center">

   <src:Character First="Maggie" Image="images/maggie.png" />

 </Button>

 

 

如果你很好奇,想为所有CLR对象指定一个默认模板,那么你做不到,因为WPF特别禁止DataType=”{x:Type sys:Object}”类型的模板。

6.     使用DataTemplateSelector

虽然类型特定的模板很有用,然而,这可能没有给你选择模板的灵活性。例如:你想为年龄低于21的人选择一个模板,剩余的选择另外一个模板。

为了更灵活的选择模板,你可以实现一个DataTemplateSelector。一个模板选择器是一个从DataTemplateSelector派生并重写了SelectTemplate方法并返回了你需要的模板的类。下面是一个非常简单的例子:

public class CharacterTemplateSelector : DataTemplateSelector

{

   private DataTemplate _childTemplate = null;

   public DataTemplate ChildTemplate

   {

       get { return _childTemplate; }

       set { _childTemplate = value; }

   }

 

   private DataTemplate _adultTemplate = null;

   public DataTemplate AdultTemplate

   {

       get { return _adultTemplate; }

       set { _adultTemplate = value; }

   }

 

   public override DataTemplate SelectTemplate(object item,

       DependencyObject container)

   {

       if (item is Character)

       {

            return (item as Character).Age >= 21

                ? _adultTemplate :_childTemplate;

       }

       return base.SelectTemplate(item, container);

   }

}

 

现在为了使用模板选择器,我们需要在资源中简单的声明一个实例,并设置恰当的设置属性:

<src:CharacterTemplateSelector x:Key="CharacterTemplateSelector"

     ChildTemplate="{StaticResourceCharacterTemplate}"

     AdultTemplate="{StaticResourceAdultCharacterTemplate}" />

 

然后为ItemsControl设置ItemTemplateSelector属性,如下:

<ListBox Width="200" Height="300"

     ItemTemplateSelector="{StaticResourceCharacterTemplateSelector}">

   . . .

 </ListBox>

 

原创粉丝点击