WPF 基础 - DataTemplate 和 ControlTemplate 的关系和应用

1. 关系

  • 凡是 Template,最后都得作用到 控件 上,这个控件就是 Template 的目标控件(也称模板化控件);
  • DataTemplate 一般是落实在一个 ContentPresenter 对象上,而 ContentPresenter 类只有 ContentTemplate 属性、没有 Template 属性,这可以说明它的专门用途是承载由 DataTemplate 生成的一组控件;
  • ControlTemplate 生成的控件树其树根是 ControlTemplate 的目标控件,此目标控件的 Template 属性值是这个 ControlTemplate 实例;
  • DataTemplate 生成的控件树其树根是一个 ContentPresenter 控件,此目标控件的 ContentTemplate 属性值是这个 DataTemplate 实例;
  • 因为 ContentPresenter 控件是 ContentTemplate 控件树上的一个结点,所以 DataTemplate 控件树是 ContentTemplate 控件树的一颗子树;
  • 如果由 Template 生成的控件使用了 TemplateBinding 获取属性值,则 TemplateBinding 的数据源就是应用了这个模板的目标控件;
  • 如果把数据对象赋值给 ContentPresenter 的 DataContext 属性,由 DataTemplate 生成的控件自然会找到这个数据对象并把它当做自己的数据源。

简而言之:

typeof(Control.Template) = ControlTemplate
typeof(Control.ContentTemplate) = DateTemplate ∈ ContentPresenter
ContentPresenter.DateContext => Controls(∈ Datemplate).DataContext

2. 应用

2.1 ControlTemplate 的应用

  1. 设置 key,由需要的控件调用;
  2. 不设置 key,统一运用到某个类型的控件。
<Style TargetType="{x:Type TextBox">
    <Setter Property="Template">
        <Setter.Value>
            <ContentPresenter TargetType="{x:Type TextBox}">
                ...
            </ContentPresenter>
        </Setter.Value>
    </Setter>
</Style>

<TextBox />
<TextBox />
<!--如果不需要这个样式,就另行设置 Style 的属性值-->
<TextBox Style="{x:Null}"/>

2.2 DataTemplate 的应用

同理于 ControlTemplate。

2.2.1 DataTemplate 实例

<DataTemplate DataType="{x:Type xx}">
    <Grid>
        ...
    </Grid>
</DataTemplate>
xmlns:sys="clr-namespace:System.Collections;assembly=mscorlib"
    
<DataTemplate DataType="{x:Type local:Unit}">
    <Grid>
        ...
    </Grid>
</DataTemplate>

<sys:ArrayList x:key="unitList">
    <local:Unit Year="2010" Price="100"/>
    <local:Unit Year="2011" Price="110"/>
    <local:Unit Year="2012" Price="120"/>
</sys:ArrayList>

<ListBox ItemsSource="{StaticResource unitList}" />

public Class Unit
{ 
    public string Year { get; set; }
    public string Price { get; set; }
}

虽然没有为 ListBox 指定 ItemTemplate,当 DataTemplate 会自动加载到所有 Unit 类型对象上。

2.2.2 把 XML 数据结点当作目标对象

DataTemplate 具有直接把 XML 数据结点当做目标对象的功能;

  • XML 数据中元素名作为 DataType
  • 可使用 XPath 访问元素的 子结点
  • 可使用 XPath 访问元素的 Attribute
  • XPath 可以在 XmlDataProvider 标签中或 Binding 中定义
2.2.2.1 只用 XML 的一层
<Window.Resources>
    <XmlDataProvider x:Key="ds" XPath="Units/Unit">
        <x:XData>
            <Units xmlns="">
                <Unit Year="2001" Price="110"/>
                <Unit Year="2002" Price="120"/>
                <Unit Year="2003" Price="130"/>
                <Unit Year="2004" Price="140"/>
            </Units>
        </x:XData>
    </XmlDataProvider>

    <DataTemplate DataType="Unit">
        <Grid>
            <StackPanel Orientation="Horizontal">
                <Grid>
                    <Rectangle Fill="LightSalmon" Width="{Binding XPath=@Price}"/>
                    <TextBlock Text="{Binding XPath=@Year}"/>
                </Grid>
                <TextBlock Text="{Binding XPath=@Price}"/>
            </StackPanel>
        </Grid>
    </DataTemplate>      
</Window.Resources>

<StackPanel>
    <ListBox ItemsSource="{Binding Source={StaticResource ds}}" />
    <ComboBox ItemsSource="{Binding Source={StaticResource ds}}" Margin="0 10 0 0" />
</StackPanel>
<Window.Resources>
    <XmlDataProvider x:Key="ds">
    ..
    
<ListBox ItemsSource="{Binding Source={StaticResource ds}, XPath=/Units/Unit}" />
2.2.2.2 支持 HeaderedItemsControl 的 DataTemplate:HierarchicalDataTemplate

HierarchicalDataTemplate 表示支持 System.Windows.Controls.HeaderedItemsControl 的 System.Windows.DataTemplate,例如 System.Windows.Controls.TreeViewItem 或 System.Windows.Controls.MenuItem。

<Window.Resources>
    <XmlDataProvider x:Key="ds" XPath="Data/Units">
        <x:XData>
            <Data xmlns="">
                <Units Name="单元组A">
                    <Unit Name="A 组 A 房">
                        <Student Name="AA1" Age="16"></Student>
                        <Student Name="AA2" Age="17"></Student>
                        <Student Name="AA3" Age="18"></Student>
                    </Unit>
                    <Unit Name="A 组 B 房">
                        <Student Name="AB1" Age="26"></Student>
                        <Student Name="AB2" Age="27"></Student>
                        <Student Name="AB3" Age="28"></Student>
                    </Unit>
                </Units>
                <Units Name="单元组B">
                    <Unit Name="B 组 A 房">
                        <Student Name="BA1" Age="16"></Student>
                        <Student Name="BA2" Age="17"></Student>
                        <Student Name="BA3" Age="18"></Student>
                    </Unit>
                    <Unit Name="B 组 B 房">
                        <Student Name="BB1" Age="26"></Student>
                        <Student Name="BB2" Age="27"></Student>
                        <Student Name="BB3" Age="28"></Student>
                    </Unit>
                </Units>
            </Data>
        </x:XData>
    </XmlDataProvider>
    <HierarchicalDataTemplate DataType="Units" ItemsSource="{Binding XPath=Unit}">
        <TextBlock Text="{Binding XPath=@Name}"/>
    </HierarchicalDataTemplate>

    <HierarchicalDataTemplate DataType="Unit" ItemsSource="{Binding XPath=Student}">
        <RadioButton Content="{Binding XPath=@Name}" GroupName="gn"/>
    </HierarchicalDataTemplate>

    <HierarchicalDataTemplate DataType="Student" ItemsSource="{Binding XPath=bu}">
        <CheckBox>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding XPath=@Name}" Margin="0 0 10 0"/>
                <TextBlock Text="{Binding XPath=@Age}"/> 
            </StackPanel>
        </CheckBox>
    </HierarchicalDataTemplate>
</Window.Resources>
<StackPanel>
    <ListBox ItemsSource="{Binding Source={StaticResource ds}}" />
    <TreeView ItemsSource="{Binding Source={StaticResource ds}}"></TreeView>
</StackPanel>

ListBox 只展示了两行 单元组A、单元组B;
TreeView 展示了树形结果,展示了 xml 的所有结点

<!--Data.xml-->
<?xml version="1.0" encoding="utf-8" ?>
<Data xmlns="">
    <Operation Name="文件" Gesture="F">
        <Operation Name="新建" Gesture="N">
            <Operation Name="项目" Gesture="Control + P"/>
            <Operation Name="网站" Gesture="Control + W"/>
            <Operation Name="文档" Gesture="Control + D"/>
        </Operation>
        <Operation Name="保存" Gesture="S"/>
        <Operation Name="打印" Gesture="P"/>
        <Operation Name="退出" Gesture="X"/>
    </Operation>
    <Operation Name="编辑" Gesture="E">
        <Operation Name="拷贝" Gesture="Control + C"/>
        <Operation Name="剪切" Gesture="Control + X"/>
        <Operation Name="粘贴" Gesture="Control + V"/>
    </Operation>
</Data>

...

<Window.Resources>
    <XmlDataProvider x:Key="ds" Source="Data.xml" XPath="Data/Operation"/>
        
    <HierarchicalDataTemplate DataType="Operation" ItemsSource="{Binding XPath=Operation}">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding XPath=@Name}" Margin="10,0"/>
            <TextBlock Text="{Binding XPath=@Gesture}"/>
        </StackPanel>
    </HierarchicalDataTemplate>
</Window.Resources>
<StackPanel MenuItem.Click="StackPanel_Click">
    <Menu ItemsSource="{Binding Source={StaticResource ds}}"/>
</StackPanel>

private void StackPanel_Click(object sender, RoutedEventArgs e)
{
    MenuItem mi = e.OriginalSource as MenuItem;

    System.Xml.XmlElement xe = mi.Header as System.Xml.XmlElement;

    string name = xe.Attributes["Name"].Value;
    string gesture = xe.Attributes["Gesture"].Value;

}

总结:
1)e.Source 是 Menu,e.OriginalSource 是 MenuItem;
2)HierarchicalDataTemplate 的作用目标不是 MenuItem 的内容,而是 MenuItem 的 Header;
3)可以监听 MenuItem 的单击事件,然后从路由参数的 OriginalSource 取出 MenuItem,取出 MenuItem 就能取出属性名为 Header 的值,从而取到某个特性的值;
4)搭配工厂模式,根据点击的 MenuItem 执行对应的操作。

posted @ 2021-02-26 22:12  鑫茂  阅读(524)  评论(0编辑  收藏  举报