• 【wpf】wpf中的那些模板之深度解析


    举几个例子

    ItemsPanelTemplate(子项容器模板)

    1. <ListView.ItemsPanel>
    2. <ItemsPanelTemplate>
    3. <Canvas/>
    4. ItemsPanelTemplate>
    5. ListView.ItemsPanel>

    ListView的ItemsPanel的类型为ItemsPanelTemplate

    2  DataTemplate(数据模板)

    1. <ListView.ItemTemplate>
    2. <DataTemplate>
    3. <Grid Canvas.Left="{Binding Left}" Canvas.Top="{Binding Top}">
    4. <Grid.ColumnDefinitions>
    5. <ColumnDefinition/>
    6. <ColumnDefinition/>
    7. <ColumnDefinition/>
    8. Grid.ColumnDefinitions>
    9. <TextBlock Text="{Binding Name}"/>
    10. <TextBlock Text="{Binding Age}" Grid.Column="1"/>
    11. Grid>
    12. DataTemplate>
    13. ListView.ItemTemplate>

    ListView的ItemTemplate属性的类型是DataTemplate

    3  ControlTemplate(控件模板)

    而Button的Template属性的类型是ControlTemplate,其实任何控制的Template属性的类型都是

    ControlTemplate,如Button:

    1. <Button DataContext="{StaticResource datas}" Height="50">
    2. <Button.Template>
    3. <ControlTemplate TargetType="Button">
    4. <Grid DataContext="{Binding [0]}">
    5. <Grid.ColumnDefinitions>
    6. <ColumnDefinition/>
    7. <ColumnDefinition/>
    8. <ColumnDefinition/>
    9. Grid.ColumnDefinitions>
    10. <TextBlock Text="{Binding Name}"/>
    11. <TextBlock Text="{Binding Age}" Grid.Column="1"/>
    12. Grid>
    13. ControlTemplate>

    避开两个坑

    要理解模板,首要避开的一个坑就是,模板属性名称和模板类型名称。不区分这个东西,你会误以为有很多模板类型,直接吓蒙,劝退。

            每个控件的模板属性和模板类型,是实例与类的关系,这里一定注意!

    其次,要认识到,下面这种语法这并不是嵌套,而是属性的赋值,是一个实例化的过程:

    1. <ListView.ItemsPanel>
    2. <ItemsPanelTemplate>
    3. <Canvas/>
    4. ItemsPanelTemplate>
    5. ListView.ItemsPanel>

    ListView的ItemsPanel属性的类型为ItemsPanelTemplate,所以这里在里面写了一个ItemsPanelTemplate,这表示实例化了一个ItemsPanelTemplate对象并赋值给ListView的ItemsPanel属性

    逻辑树和视觉树

    谈论模板,避不开视觉树,首先看逻辑树,这个很简单,因为它很直观就是xaml中嵌套这些“业务逻辑”:

    1. <Window x:Class="Zhaoxi.WPFLession.Window1"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    6. xmlns:local="clr-namespace:Zhaoxi.WPFLession"
    7. mc:Ignorable="d"
    8. Title="Window1" Height="450" Width="800">
    9. <UniformGrid Rows="2" Columns="2">
    10. <Button>
    11. <TextBox>asdfasdfTextBox>
    12. Button>
    13. <TextBox Width="100" Height="30">123456TextBox>
    14. <TextBlock Width="100" Height="30" Background="AliceBlue">sdfadTextBlock>
    15. UniformGrid>
    16. Window>

    现在上面这段代码的逻辑树,就是:Window -》UniformGrid-》Button-》TextBox (并列的关系我就不画了)

    视觉树,就要看控件内部,比如Button这个控件,就是由更基础的元素构建的,只是被封装起来我们看不到细节而已,但是你能看到button中间有文字,背景是灰色的。

    接下来,我们可以借助编辑模板这个功能,观察一下,Button的内部世界(视觉树):

    选中Button,右键选择=》编辑模板

    1. <Style x:Key="ButtonStyle1" TargetType="{x:Type Button}">
    2. <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
    3. <Setter Property="Background" Value="{StaticResource Button.Static.Background}"/>
    4. <Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/>
    5. <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    6. <Setter Property="BorderThickness" Value="1"/>
    7. <Setter Property="HorizontalContentAlignment" Value="Center"/>
    8. <Setter Property="VerticalContentAlignment" Value="Center"/>
    9. <Setter Property="Padding" Value="1"/>
    10. <Setter Property="Template">
    11. <Setter.Value>
    12. <ControlTemplate TargetType="{x:Type Button}">
    13. <Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="true">
    14. <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
    15. Border>
    16. <ControlTemplate.Triggers>
    17. <Trigger Property="IsDefaulted" Value="true">
    18. <Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
    19. Trigger>
    20. <Trigger Property="IsMouseOver" Value="true">
    21. <Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/>
    22. <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/>
    23. Trigger>
    24. <Trigger Property="IsPressed" Value="true">
    25. <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/>
    26. <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/>
    27. Trigger>
    28. <Trigger Property="IsEnabled" Value="false">
    29. <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/>
    30. <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/>
    31. <Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/>
    32. Trigger>
    33. ControlTemplate.Triggers>
    34. ControlTemplate>
    35. Setter.Value>
    36. Setter>
    37. Style>

    这里通过Style设置了button的所有属性,但是这里我们重点关注的是Template这个属性!这里就是视觉树的呈现。那我们发现构成button的元素部分,异常的简单:

    1. <Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="true">
    2. <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
    3. Border>

    一个 border里面放了一个ContentPresenter(数据模板相关,后面讲解) 。而这些被包含在了一个叫做ControlTemplate的标签里。

    ControlTemplate

    ControlTemplate控件模板就是给修改视觉树提供了一个接口!接下来我想看看Listbox的控件模板,方法一样:

    1. <Style x:Key="ListBoxStyle1" TargetType="{x:Type ListBox}">
    2. <Setter Property="Background" Value="{StaticResource ListBox.Static.Background}"/>
    3. <Setter Property="BorderBrush" Value="{StaticResource ListBox.Static.Border}"/>
    4. <Setter Property="BorderThickness" Value="1"/>
    5. <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    6. <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
    7. <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
    8. <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
    9. <Setter Property="ScrollViewer.PanningMode" Value="Both"/>
    10. <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
    11. <Setter Property="VerticalContentAlignment" Value="Center"/>
    12. <Setter Property="Template">
    13. <Setter.Value>
    14. <ControlTemplate TargetType="{x:Type ListBox}">
    15. <Border x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="1" SnapsToDevicePixels="true">
    16. <ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}">
    17. <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
    18. ScrollViewer>
    19. Border>
    20. <ControlTemplate.Triggers>
    21. <Trigger Property="IsEnabled" Value="false">
    22. <Setter Property="Background" TargetName="Bd" Value="{StaticResource ListBox.Disabled.Background}"/>
    23. <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource ListBox.Disabled.Border}"/>
    24. Trigger>
    25. <MultiTrigger>
    26. <MultiTrigger.Conditions>
    27. <Condition Property="IsGrouping" Value="true"/>
    28. <Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/>
    29. MultiTrigger.Conditions>
    30. <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
    31. MultiTrigger>
    32. ControlTemplate.Triggers>
    33. ControlTemplate>
    34. Setter.Value>
    35. Setter>
    36. Style>

    元素构成部分也不多:

    1. <Border x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="1" SnapsToDevicePixels="true">
    2. <ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}">
    3. <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
    4. ScrollViewer>
    5. Border>

    最后我想看看TextBlock的ControlTemplate,然而你会发现TextBlock根本没有ControlTemplate,因为他根本就不算是一个控件,他直接继承自FrameworkElement,它只是一个元素。(包括Border和ItemPresenter以及ContentPresenter都是继承FrameworkElement并没有直接继承自Control)

    那么这里,我总结一下:

    逻辑树中每个控件的内部其实包含了视觉树。视觉树也是有基本的元素和控件构成!因为视觉树是被封装起来的,所以微软为程序员提供了修改控件内部(视觉树)的机会,及控件模板。修改控件模板,可以轻易改变控件的外表!举个例子:

    这两个都是checkbox,我们不需要重写控件,只需要通过控件模板修改一下逻辑树以及Trigger就能实现。(Trigger下一篇再说)

    注意:这里指定TargetType是很重要的,不然IsChecked这个属性无法通过编译

    1. <CheckBox Width="100" Height="50" >
    2. <CheckBox.Template>
    3. <ControlTemplate TargetType="CheckBox">
    4. <Border Background="AliceBlue">
    5. <Canvas>
    6. <Rectangle x:Name="rt" Fill="Orange" Width="50" Height="50"/>
    7. Canvas>
    8. Border>
    9. <ControlTemplate.Triggers>
    10. <Trigger Property="IsChecked" Value="true">
    11. <Setter TargetName="rt" Property="Canvas.Left" Value="50"/>
    12. Trigger>
    13. ControlTemplate.Triggers>
    14. ControlTemplate>
    15. CheckBox.Template>
    16. CheckBox>

    DataTemplate

    接下来是数据模板,他也要看这个课视觉树,前面的视觉树中,我们发现了一个不认识的东西,但是视觉树里基本都有他:ItemPresenter以及ContentPresenter(以Presenter结尾的东西)

    这些以Presenter结尾的元素,统称为“内容占位”。为啥wpf中控件之间可以任意嵌套?奥秘就在这里,如果你给button内部嵌套个啥的,这个东西都会扔给ContentPresenter,ContentPresenter会将其包裹起来。而Presenter结尾的元素也就是数据模板作用的对象

    举个例子:

    1. <Window x:Class="Zhaoxi.WPFLession.Window1"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    6. xmlns:local="clr-namespace:Zhaoxi.WPFLession"
    7. mc:Ignorable="d"
    8. Title="Window1" Height="450" Width="800">
    9. <Window.Resources>
    10. <x:Array Type="local:Person" x:Key="datas">
    11. <local:Person Name="啊啊啊" Age="20" Gender="1"/>
    12. <local:Person Name="呃呃呃" Age="21" Gender="2"/>
    13. <local:Person Name="哦哦哦" Age="21" Gender="2"/>
    14. <local:Person Name="呵呵呵" Age="21" Gender="2"/>
    15. x:Array>
    16. Window.Resources>
    17. <UniformGrid Rows="2" Columns="2">
    18. <ListBox ItemsSource="{StaticResource datas}">
    19. <ListBox.ItemTemplate>
    20. <DataTemplate>
    21. <DockPanel LastChildFill="False">
    22. <TextBlock DockPanel.Dock="Right" Text="{Binding Name}"/>
    23. <CheckBox/>
    24. DockPanel>
    25. DataTemplate>
    26. ListBox.ItemTemplate>
    27. ListBox>
    28. UniformGrid>
    29. Window>

     效果如下:

     

     用snoop观察一下我们这个程序的内部结构:

     你发现了三个Presenter结尾的元素,

    一个ScrollContentPresenter是属于ListBox的视觉树种的ScrollView的,ScrollContentPresenter就包含了ItemsPresenter,就包含了一系列的ListBoxItem,而ListBoxItem就包含了我们在数据模板中定义的DataTemplate的内容:

     小结,数据模板,其实也能改变视觉树,只不过他并不是重新修改控件的整个视觉树,而是向Presenter结尾的元素中添加内容。而这些内容需要绑定的数据数据才会被实例化。

    再来一个例子:

    1. <ItemsControl ItemsSource="{StaticResource datas}" AlternationCount="2">
    2. <ItemsControl.ItemTemplate>
    3. <DataTemplate>
    4. <Grid Background="Transparent" Name="root">
    5. <Grid.ColumnDefinitions>
    6. <ColumnDefinition/>
    7. <ColumnDefinition/>
    8. <ColumnDefinition/>
    9. Grid.ColumnDefinitions>
    10. <TextBlock Text="{Binding Name}"/>
    11. <TextBlock Text="{Binding Age}" Grid.Column="1"/>
    12. Grid>
    13. <DataTemplate.Triggers>
    14. <Trigger Property="ItemsControl.AlternationIndex" Value="1">
    15. <Setter Property="Background" Value="Orange" TargetName="root"/>
    16. Trigger>
    17. DataTemplate.Triggers>
    18. DataTemplate>
    19. ItemsControl.ItemTemplate>
    20. ItemsControl>

    这里注意,这种触发器是设置Background属性时要指定TargetName。

     ItemsPanelTemplate,这个今天累了,后续补充吧。

    总结:

    1  我们需要避开两个坑

    2  理解什么是视觉树

    3  数据模板和控件模板都是微软提供的接口,用于修改视觉树。

  • 相关阅读:
    HTML5期末大作业:基于 html css js仿腾讯课堂首页
    JVM_逃逸分析
    信息学奥赛一本通:1003:对齐输出
    Codeforces Round #828 (Div. 3) A-F
    二、T100固定资产之固定资产数据建立篇
    【Web】Ctfshow Nodejs刷题记录
    leetcode53 -- 最大数组和
    Unity Lighting 面板的参数设置用途详细总结
    机器学习-线性回归的各种操作代码
    云原生k8s的金箍棒
  • 原文地址:https://blog.csdn.net/songhuangong123/article/details/126071796