• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

WPF四 WPF 模板

武飞扬头像
阿阿阿安
帮助1

1 模板的概念

在WPF中,控件只是一个数据和算法行为的载体,是个抽象的概念。至于控件本身的外观和行为、控件数据的呈现方式都是靠 Template 来具体定义的,通过引入模板(Template)微软将数据和算法的“内容”与“形式”解耦了,我们可以轻松的创造、修改、调整控件长什么样、有什么行为、数据如何展示等。这就是为什么默认情况下 Button 或 TextBox 形状是矩形的,因为它是在其默认模板中定义(正是模板决定了TextBox是方方正正的输入框,可以输入数据)。深入到控件内部,每个控件本身是一棵 UI元素树,其内部都是由很多子元素节点挂载组成。WPF的模板主要包括 ControlTemplate(控件模板)和DataTemplate(数据模板)两种类型。

  • ControlTemplate:控件模板由控件的Template属性定义,用于决定控件的整体外观(控件UI元素树整体结构)与功能(触发器与事件)。由ControlTemplate生成的控件树其树根就是ControlTemplate的目标控件,此模板化控件的Template属性值就是这个ControlTemplate实例。
  • DataTemplate:数据模板由控件的xxxTemplate属性定义(比如ContentControl的ContentTemplate、ItemsControl的ItemTemplate等),用于决定控件数据的呈现方式。DataTemplate 可以包含 UI 元素和数据,也构成了一颗UI元素树。由 DataTemplate 生成的控件树其树根是一个 ContentPresenter 控件,ContentPresenter 控件只有ContentTemplate属性没有Template属性,用于通过Content来承载DataTemplate,在ControlTemplate中挂载。ContentPresenter 控件是 ControlTemplate 控件树上的一个结点,所以DataTemplate 控件树是 ControlTemplate 控件树的一棵子树。

学新通

2 模板的定义

2.1 数据模板 DataTemplate

DataTemplate常用的位置主要包括三处,分别如下。最重要的一点是为DataTemplate中的每个控件设置Binding,告诉DataTemplate中各个元素应该关注数据的哪个属性。

  • ContentControl的ContentTemplate属性:设置ContentControl控件内容Content数据的外观展示方式,使用较少。比如Button内的文字、UserControl的Content等
  • ItemsControl的ItemTemplate属性:获取或设置用来显示每个列表[数据项条目]的 DataTemplate,该属性可以视为与内部 xxxItem 的数据模板绑定(主要)
  • GridViewColumn的CellTemplate属性:定制GridViewColumn每个单元格数据的外观
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <!--定制“数据详情”的数据展示外观-->
    <DataTemplate x:Key="detailViewTemplate">
        <Border BorderBrush="Black" CornerRadius="6">
            <StackPanel Margin="5">
                <Border Width="380" Height="200" Background="{Binding Code}"/>
                <StackPanel Orientation="Horizontal" Margin="5,0">
                    <TextBlock Text="Color Name:" FontSize="20" FontWeight="Bold"/>
                    <TextBlock Text="{Binding Name}" FontSize="20" Margin="5,0"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="5,0">
                    <TextBlock Text="Color Code:" FontWeight="Bold"/>
                    <TextBlock Text="{Binding Code}" Margin="5,0"/>
                </StackPanel>
            </StackPanel>
        </Border>
    </DataTemplate>

    <!--定制“数据列表条目”的数据展示外观-->
    <DataTemplate x:Key="ListItemViewTemplate">
        <StackPanel Orientation="Horizontal" Margin="5,0">
            <Border Width="20" Height="20" Background="{Binding Code}"/>
            <StackPanel Margin="5,10">
                <TextBlock Text="{Binding Name}" FontSize="16" FontWeight="Bold"/>
                <TextBlock Text="{Binding Code}" FontSize="14"/>
            </StackPanel>
        </StackPanel>
    </DataTemplate>

</ResourceDictionary>
<Window x:Class="WPF_Demo.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:WPF_Demo"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="600">

    <StackPanel Orientation="Horizontal" x:Name="stackPanel" Margin="5">
        <UserControl ContentTemplate="{StaticResource detailViewTemplate}" Content="{Binding SelectedItem,ElementName=listBoxColors}"/>
        <StackPanel Width="180">
            <ListBox x:Name="listBoxColors" Height="280" Margin="5,0" ItemTemplate="{StaticResource ListItemViewTemplate}"/>
            <Button Content="Add" Margin="5" x:Name="bntAdd" Click="bntAdd_Click"/>
        </StackPanel>
    </StackPanel>
</Window>
  • 对ContentControl来说(这里的UserControl):使用ContentTemplate相当于给控件的Content内容的展示提供了模板。而ContentPresenter又是DataTemplate在ControlTemplate中挂载展示的载体,是DataTemplate的根元素,设置Content内容绑定数据之后,模板会自动将控件的 Content 属性和ContentTemplate属性绑定到其ContentPresenter上(上下文),所以在ContentControl中我们直接使用Binding就可以获取上下文数据。
  • 对ItemsControl来说(这里的ListBox):使用ItemTemplate相当于给控件的每个数据条目内容数据的展示提供了模板。当ListBox的ItemSource被赋值时,会自动为每一个数据元素创建等量的条目容器 xxxItem(继承自ContentControl ),并自动使用Binding在每个数据条目容器与数据元素之间建立关联和绑定(将每个列表集合数据作为每条Item的DataContext)。
    • 以ListBoxItem为例,ListBoxItem继承自ContentControl,其Content属性承载了列表的每条数据条目,其ContentTemplate数据模板来自于ItemsControl的ItemTemplate,作为展示数据的模板。并在创建时自动绑定了ItemsSource的每条数据作为Item的上下文DataContext。
    • 同样,ItemsPresenter也是列表控件的整个DataTemplate在ControlTemplate中挂载展示的载体,是DataTemplate的根元素(一系列定义了DataTemplate 的 Item挂载到ItemsPresenter,然后ItemsPresenter在列表控件的ControlTemplate UI元素树中挂载展示)。
namespace WPF_Demo
{
    public partial class MainWindow : Window
    {
        ObservableCollection<ColorItem> colorList;
        public MainWindow()
        {
            InitializeComponent();
            //列表数据源
            colorList = new ObservableCollection<ColorItem>()
            {
                new ColorItem(){Name="浅绿色",Code="#33FF00"},
                new ColorItem(){Name="深绿色",Code="#006600"},
                new ColorItem(){Name="红色",Code="#FF0033"},
                new ColorItem(){Name="紫色",Code="#9900FF"}
            };
            //设置上下文
            this.listBoxColors.ItemsSource = colorList;
        }

        private void bntAdd_Click(object sender, RoutedEventArgs e)
        {
        	//动态添加数据
            colorList.Add(new ColorItem() { Name = "未知", Code = "#000000" });
        }
    }

    public class ColorItem
    {
        public string Name { get; set; }
        public string Code { get; set; }
    }
}

2.2 控件模板 ControlTemplate

ControlTemplate在DataTemplate之上,用于组织整个控件UI元素树的结构和行为。其内部通过Presenter来绑定挂载DataTemplate的数据内容Content。ControlTemplate(控件模板)不仅是用于来定义控件的外观、样式, 还可通过控件模板的触发器(ControlTemplate.Triggers)修改控件的行为、响应动画等。

(1) Button的ControlTemplate源码

学新通

  • TargetType : 获取或设置此 ControlTemplate 所针对的类型,默认为null。注意如果模板定义中包含 TargetType,则 ContentPresenter 属性不能为 null
  • ContentPresenter:挂载DataTemplate的控件,实现了数据内容的显示。等价于
  • TemplateBinding:ControlTemplate最终被应用到一个控件上,这个控件被称为模板目标控件,ControlTemplate 里的控件元素可以使用TemplateBinding将自己的属性单向关联/绑定到目标控件的属性值上。TemplateBinding 是绑定的优化形式,类似于使用 {Binding RelativeSource={RelativeSource TemplatedParent}} 构造的绑定。TemplateBinding 可用于将模板的各个部分绑定到控件的各个属性。

(2) ListBox的ControlTemplate源码
学新通

定义 ItemsControl 主要分两个步骤:

  • 1.设置ItemsPanel容器, 用于容纳列表布局的最外层容器 2.定义子项的DataTemplate

  • ItemsPanel :ItemsPanelTemplate类型,获取或设置模板,该模板定义对项的布局进行控制的面板。是容纳列表布局的外层容器。只能使用Panel族元素。 ItemsControl 的默认值是一个 StackPanel

学新通

3 模板的使用

我们这里以 ItemsControl 的自定义模板为例,用两个实际例子介绍模板的实际应用。

  • IsItemsHost 属性:在此示例中,一个必需的重要属性是 IsItemsHost 属性。IsItemsHost 属性用于指示在 ItemsControl(如处理项列表的 ListBox 控件)的模板中,生成的元素应放在什么位置。如果将 StackPanel 的这一属性设置为 true,则添加到 ListBox 的所有项都将进入 StackPanel。请注意,此属性只对 Panel 类型有效。请注意,如果以这种方式在 ControlTemplate 中指定一个面板并将其标记为 IsItemsHost,控件的用户不使用 ControlTemplate 就无法替换 ItemsPanel。因此,除非您确信必须使用模板才能替换面板,否则不要采用这种方式。
  • ItemsPresenter 和 ContentPresenter:此外,您也可以使用 ItemsPresenter 元素来标记项的位置,然后通过设置 ItemsPanel 属性来指定 ItemsPanelTemplate。如果要创建 ContentControl(如 Button)的模板,则对应元素为 ContentPresenter。同样,将此元素放置到 ContentControl 类型的 ControlTemplate 中,可以指示内容应在什么位置显示。

(1)实例1

    <!-- 1.声明自定义ItemsControl控件 -->
	<ItemsControl ItemsSource="{Binding Scans}">
        <!-- 2.定义ItemsControl的整体样式模板ControlTemplate -->
        <ItemsControl.Template>
            <ControlTemplate TargetType="{x:Type ItemsControl}">
                <ScrollViewer HorizontalAlignment="Stretch" VerticalScrollBarVisibility="Hidden">
                    <!--IsItemsHost 属性用于指示在 ItemsControl(如处理项列表的 ListBox 控件)的模板中,生成的元素应放在什么位置。用于代替ItemsPresenter -->
                    <StackPanel IsItemsHost="True" Orientation="Vertical" />
                </ScrollViewer>
            </ControlTemplate>
        </ItemsControl.Template>
         <!-- 3.定义ItemsControl每个数据项的展示模板DataTemplate -->
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <ContentControl x:Name="CardPresenter">
                    <RadioButton
                        Height="35"
                        HorizontalContentAlignment="Center"
                        VerticalContentAlignment="Center"
                        Content="{Binding ScanName}"
                        FontSize="20"
                        Foreground="White"
                        GroupName="scanbtn">
                        <RadioButton.Style>
                            <Style BasedOn="{StaticResource ScanButtonStyle}" TargetType="RadioButton">
                                <Setter Property="Background" Value="{Binding Status, Converter={StaticResource bgCV}}" />
                            </Style>
                        </RadioButton.Style>
                        <!-- 声明RadioButton的交互触发器触发行为 -->
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="Click">
                                <i:InvokeCommandAction Command="{Binding ScanClickCommand, Source={x:Static vm:EDESPageStatus.Instance}}" PassEventArgsToCommand="True" />
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </RadioButton>
                </ContentControl>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

(2)实例2

	<!-- 1.声明自定义ItemsControl控件 -->
	<ItemsControl
        x:Name="itemsControl"
        Padding="0,5,10,5"
        DataContext="{x:Static vm:ExplorePageStatus.Instance}"
        ItemsSource="{Binding Cards}">
        <!-- 2.定义ItemsControl的整体样式模板ControlTemplate -->
        <ItemsControl.Template>
            <ControlTemplate TargetType="{x:Type ItemsControl}">
                <Border
                    Padding="{TemplateBinding Padding}"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    SnapsToDevicePixels="True">
                    <ScrollViewer VerticalScrollBarVisibility="Hidden">
                        <!-- 使用 ItemsPresenter 元素来标记数据项列表在整体样式中的的位置(挂载) -->
                        <ItemsPresenter HorizontalAlignment="Center" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                    </ScrollViewer>
                </Border>
            </ControlTemplate>
        </ItemsControl.Template>
        <!-- 3.定义对数据项的布局进行控制的面板Panel(控制数据项的排布方式,作用在ItemsPresenter内数据项列表外) -->
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel Orientation="Horizontal">
                    <WrapPanel.Style>
                        <Style TargetType="WrapPanel">
                            <Setter Property="ItemWidth" Value="420" />
                            <Setter Property="ItemHeight" Value="326.67" />
                        </Style>
                    </WrapPanel.Style>
                </WrapPanel>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <!-- 4.定义ItemsControl每个数据项的展示模板DataTemplate -->
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <!--内部放置UI元素以及Binding数据(默认来自ItemsSource的每一项)-->
                <Grid x:Name="OuterGrid">
                    <Grid x:Name="InnerGrid" DataContext="{x:Static vm:ExplorePageStatus.Instance}">
                        <Border
                            x:Name="ButtonBorder"
                            Margin="8,4.8"
                            Background="Transparent"
                            BorderBrush="{c:Binding 'IsChecked ? \'Yellow\' : \'Transparent\'',
                                                    Mode=OneWay}"
                            BorderThickness="3"
                            DataContext="{Binding DataContext, ElementName=OuterGrid}">
                            <Grid DataContext="{Binding DataContext, ElementName=OuterGrid}">
                                <Canvas Panel.ZIndex="1">
                                    <Label
                                        Canvas.Left="10"
                                        Canvas.Top="12"
                                        Width="40"
                                        Height="19.2"
                                        Padding="0"
                                        HorizontalContentAlignment="Center"
                                        VerticalContentAlignment="Center"
                                        Background="{c:Binding '(Phase == e:PhaseEnum.ED ? \'#FF6B6B\' : \'#44D7B6\')',
                                                               Mode=OneWay}"
                                        BorderThickness="0"
                                        Content="{c:Binding '(Phase == e:PhaseEnum.ED ? \'ED\' : \'ES\')',
                                                            Mode=OneWay}"
                                        FontFamily="PingFangSC-Semibold"
                                        FontSize="19px"
                                        FontWeight="SemiBold"
                                        Foreground="White" />
                                    <Label
                                        Canvas.Left="5"
                                        Canvas.Bottom="10"
                                        Width="30"
                                        Height="20"
                                        Padding="0"
                                        HorizontalContentAlignment="Center"
                                        VerticalContentAlignment="Center"
                                        Background="{c:Binding '(LVChecked ? \'#9ACD32\' : \'#C0C0C0\')',
                                                               Mode=OneWay}"
                                        BorderThickness="0"
                                        Content="LV"
                                        FontFamily="PingFangSC-Semibold"
                                        FontSize="16px"
                                        FontWeight="SemiBold"
                                        Foreground="White" />
                                    <Label
                                        Canvas.Left="40"
                                        Canvas.Bottom="10"
                                        Width="30"
                                        Height="20"
                                        Padding="0"
                                        HorizontalContentAlignment="Center"
                                        VerticalContentAlignment="Center"
                                        Background="{c:Binding '(LAChecked ? \'#9ACD32\' : \'#C0C0C0\')',
                                                               Mode=OneWay}"
                                        BorderThickness="0"
                                        Content="LA"
                                        FontFamily="PingFangSC-Semibold"
                                        FontSize="16px"
                                        FontWeight="SemiBold"
                                        Foreground="White" />
                                    <Label
                                        Canvas.Left="75"
                                        Canvas.Bottom="10"
                                        Width="30"
                                        Height="20"
                                        Padding="0"
                                        HorizontalContentAlignment="Center"
                                        VerticalContentAlignment="Center"
                                        Background="{c:Binding '(RVChecked ? \'#9ACD32\' : \'#C0C0C0\')',
                                                               Mode=OneWay}"
                                        BorderThickness="0"
                                        Content="RV"
                                        FontFamily="PingFangSC-Semibold"
                                        FontSize="16px"
                                        FontWeight="SemiBold"
                                        Foreground="White" />
                                    <Label
                                        Canvas.Left="110"
                                        Canvas.Bottom="10"
                                        Width="30"
                                        Height="20"
                                        Padding="0"
                                        HorizontalContentAlignment="Center"
                                        VerticalContentAlignment="Center"
                                        Background="{c:Binding '(RAChecked ? \'#9ACD32\' : \'#C0C0C0\')',
                                                               Mode=OneWay}"
                                        BorderThickness="0"
                                        Content="RA"
                                        FontFamily="PingFangSC-Semibold"
                                        FontSize="16px"
                                        FontWeight="SemiBold"
                                        Foreground="White" />
                                </Canvas>
                                <Image
                                    Panel.ZIndex="0"
                                    Source="{c:Binding '(Phase == e:PhaseEnum.ED ? EDImage : ESImage)',
                                                       Mode=OneWay}"
                                    Stretch="Uniform" />
                            </Grid>
                        </Border>
                    </Grid>
                    <!--每个数据项的设备输入行为-->
                    <Grid.InputBindings>
                        <MouseBinding
                            Command="{Binding Source={x:Static vm:ExplorePageStatus.Instance}, Path=KeyFrameDetailsCommand}"
                            CommandParameter="{Binding ElementName=OuterGrid, Path=DataContext}"
                            MouseAction="LeftClick" />
                    </Grid.InputBindings>

                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

4.模板机制的内部原理

(1)原理: ItemsControl的默认Template里的ItemsPresenter只起一个占位符(placeholder)的作用,它的主要角色是接收ItemsControl的ItemsPanelTemplate模板,并在ItemsControl应用模板时应用这个模板。这时一个ItemsControl的Template模板里的ItemsPresenter在应用这个ItemsControl的ItemsPanel模板时,会将模板里面的Panel类控件的TemplateParent设定为这个ItemsControl,同时将其IsItemsHost属性标识为true。ItemsControl还有一种用法是忽略ItemsPanel,直接在其Template内指定一个"ItemsPanel",即上述指定IsItemsHost的方式。这时ItemsPanel模板的设置将被直接忽略。不过,这时一定要将这个Panel的IsItemsHost设定为True,否则ItemsControl将找不到一个合适的ItemsPanel来显示列表项。

(2)总结: 我们再按照从上至下的顺序从整体上梳理一下ItemsControl的模板应用机制即一个ItemsControl在应用模板时,首先会应用Template模板(ControlTemplate类型)生成自身的visual tree(Control类的模板机制),然后Template模板中的ItemsPresenter应用其TemplateParent(即这个ItemsControl)的ItemsPanel模板(ItemsPanelTemplate类型)生成一个visual tree,并把这个visual tree放置在这个ItemsPresenter的位置(ItemsPresenter这时起到占位符的作用)。在ItemsPanel模板被应用时,这个面板的TemplateParent会被指向这个ItemsControl,同时其IsItemsHost属性被标识为true。ItemsControl的ItemContainerGeneror在遍历自己的ItemsInternal列表并为每个列表项(item)生成一个container(ItemContainer),并将ItemsControl的ItemTemplate模板“转交”(forward)给这个container,这样这个container就可以应用模板(ItemTemplate),为与自己对应的数据项(item)生成一个由这个ItemTemplate定义的visual tree。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbgaih
系列文章
更多 icon
同类精品
更多 icon
继续加载