• 关于ControlTemplate 1



    有关Control Template的基本


    通过前面的介绍,我们已经知道WPF支持用Style的Setters修改控件的属性值,以改变控件的外观。我们知道,WPF的任何控件都有视觉树和逻辑树。但是Style有它自己的局限性:它只能修改控件已有树型结构的属性,不能修改控件的树型层次结构本身。而在实际运用中,我们常常需要对控件进行更高级的自定义。此时,可以需要使用 ControlTemplate才能实现。

    在WPF中,ControlTemplate用来定义控件的外观。我们可以为控件定义新的ControlTemplate来实现控件结构和外观的修改。

     1 <Style TargetType="Button">
     2   <Setter Property="OverridesDefaultStyle" Value="True"/>
     3   <Setter Property="Template">
     4     <Setter.Value>
     5       <ControlTemplate TargetType="Button">
     6         <Grid>
     7           <Ellipse Fill="{TemplateBinding Background}"/>
     8           <ContentPresenter HorizontalAlignment="Center"  //该button的内容部分,继承于调用这个模板的父
     9                             VerticalAlignment="Center"/>
    10         </Grid>
    11       </ControlTemplate>
    12     </Setter.Value>
    13   </Setter>
    14 </Style>

    从例子代码我们可以看出,ControlTemplate含有模板的语义。也就是说它影响的应该是多个控件。而这个功能恰好可以利用Style实现。所以,在理解了Style之后,这样的代码应该不会感到陌生。首先把OverridesDefaultStyle设置为True,表示这个控件不使用当前Themes的任何属性。然后用Setters修改控件的Template属性。我们定义了一个新的ControlTemplate来设置新的值。

    同样地,ControlTemplate也使用TargetType属性,其意义与Style的TargetType一样。它的x:Key属性也是如此。然后,由一个Grid来表示控件的视觉内容。其中的TemplateBinding与Binding类似,表示当前Ellipse的显示颜色与Button 的Background属性保持同步。TemplateBinding可以理解为Binding在模板中的特例。而另一个 ContentPresenter与WPF的基本控件类型有关,一种是ContentControl,一个是ItemControl。在上面的例子中定义是基于ContentControl的Button。所以使用ContentPresenter来表示内容的显示。

    WPF中每个预定义的控件都有一个默认的模板,因此,在我们学习自定义模板(也就是自定义控件)之前,可以先熟悉了解WPF的默认模板。为了便于查看模板的树形结构层次,我们可以将模板输出为XML文件格式,这样能有助于理解。

    XmlWriterSettings settings = new XmlWriterSettings();
    settings.Indent = true;
    settings.IndentChars = new string(' ', 4);
    settings.NewLineOnAttributes = true;
    StringBuilder strbuild = new StringBuilder();
    XmlWriter xmlwrite = XmlWriter.Create(strbuild, settings);
    XamlWriter.Save(ctrl.Template, xmlwrite);

    这里的ctrl是一个实例化的Control类。并且Control需要已经显示在屏幕上,否则Control.Template可能为NULL。


    有关Trigger, MultiTrigger,DataTrigger, MultiDataTrigger


    前面关于ControlTempalte的Post当中,只说明了如何定义的外观。如果对于很复杂的自定义控件,通常我们还需要在 ControlTemplate使用Resource。很显然,Resource的目的是便于实现元素的重用。另外,我们的自定义模板通常是在XAML中完成的,因为用代码实现是非常烦琐的。对于小的应用程序,ControlTemplate一般直接定义在XAML的根元素。对于大的应用程序,通常应该定义在专门的资源XAML文件中,根元素是ResourceDictionary。

    不管定义在什么地方,除了前面用Style定义外观,以及用Resource实现元素重用外,ControlTemplate包括一个 Trigger元素,它描述在控件属性发生变化时控件的外观如何变化。比如自定义Button时需要考虑鼠标在Button上移动时控件的外观。 Trigger元素也是可选的,比如文本标签元素,它一般不包括Trigger。

    ControlTemplate中使用资源很简单,与其他元素中的资源一样:

     1 <ControlTemplate x:Key="templateThermometer" TargetType="{x:Type ProgressBar}">
     2     <ControlTemplate.Resources>  
     3         <RadialGradientBrush x:Key="brushBowl"
     4                              GradientOrigin="0.3 0.3">
     5             <GradientStop Offset="0" Color="Pink" />
     6             <GradientStop Offset="1" Color="Red" />                       
     7         </RadialGradientBrush>
     8    </ControlTemplate.Resources>
     9    <!-- 忽略其他相关内容-->
    10 </ControlTemplate>

    接下来是Trigger的使用。利用Trigger对象,我们可以接收到属性变化或者事件发生,并据此做出适当的响应。Trigger本身也是支持多种类型的,下面是一个属性Trigger的例子:

    1 <Style TargetType="ListBoxItem">
    2   <Setter Property="Opacity" Value="0.5" />
    3   <Style.Triggers>
    4     <Trigger Property="IsSelected" Value="True">
    5         <Setter Property="Opacity" Value="1.0" />
    6         <!--其他的Setters->
    7     </Triggers>
    8   </Style.Triggers>
    9 </Style>

    这段代码设置ListBoxItem的Opacity属性的默认值为0.5。但是,在IsSelected属性为True 时,ListBoxItem的Opacity属性值为1。从上面的代码还可以看出,在满足一个条件后,可以触发多个行为(定义多个Setters)。同样 地,上面的Triggers也是一个集合,也可以添加多个Trigger。

    注意上面的多个Trigger是相互独立的,不会互相影响。另一种情况是需要满足多个条件时才触发某种行为。为此,WPF提供了MultiTrigger以满足这种需求。比如:

    <Style TargetType="{x:Type Button}">
      <Style.Triggers>
        <MultiTrigger>
          <MultiTrigger.Conditions>
            <Condition Property="IsMouseOver" Value="True" />
            <Condition Property="Content" Value="{x:Null}" />
          </MultiTrigger.Conditions>
          <Setter Property="Background" Value="Yellow" />
        </MultiTrigger>
      </Style.Triggers>
    </Style>

    这就表示只有IsMouseOver为True、Content为NULL的时候才将Background设置为Yellow。

    以上的Trigger都是基于元素属性的。对于鼠标移动等事件的处理,WPF有专门的EventTrigger。但因EventTrigger多数时候是和Storyboard配合使用的。因此,我将在后面介绍动画的时候详细说明EventTrigger。

    另一方面,现在所讨论的Trigger都是基于属性的值或者事件的WPF还支持另一种Trigger:DataTrigger。显然,这种数据 Trigger用于数据发生变化时,也就是说触发条件的属性是绑定数据的。类似地,数据Trigger也支持多个条件:MultiDataTrigger。他们的基于用法和前面的Trigger类似。


    有关Trigger, MultiTrigger,DataTrigger, MultiDataTrigger


    在实际应用中,ControlTemplate是一个非常重要的功能。它帮助我们快速实现很Cool的自定义控件。下面我以Windows Vista SDK中的例子ControlTemplateExamples为基础,简单地分析ControlTemplate的使用。这个例子工程非常丰富,几乎包含了所有的标准控件。所以,在实现自定义控件时,可以先参考这样进行适当的学习研究。

    首先是App.xaml文件,这里它把Application.StartupUri属性设置为Window1.xaml。然后把工程目录Resource下所有的控件xaml文件都merge为了应用程序范围内的资源。

    <Application.Resources>
        <ResourceDictionary>
          <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Resources\Shared.xaml" />
            <!-- 这里省略多个资源位置 -->
            <ResourceDictionary Source="Resources\NavigationWindow.xaml" />
          </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>

    这样的用法很有借鉴意义。在WPF中实现Skin框架也随之变得非常简单。值需要动态使用不同的XAML文件即可。然后是Window1.xaml 文件。它里面几乎把所有的控件都显示了一遍。没有什么多说的。重点看Resource目录下的自定义控件文件。这里的控件太多,不可能每个都说说。我只挑 选其中的Button.xaml为例:

     1 <ResourceDictionary
     2   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     3   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
     4 
     5   <ResourceDictionary.MergedDictionaries>
     6     <ResourceDictionary Source="Shared.xaml"/>
     7   </ResourceDictionary.MergedDictionaries>
     8 
     9   <!-- Focus Visual -->
    10 
    11   <Style x:Key="ButtonFocusVisual">
    12     <Setter Property="Control.Template">
    13       <Setter.Value>
    14         <ControlTemplate>
    15           <Border>
    16             <Rectangle Margin="5" StrokeThickness="3"
    17             Stroke="#60000000" StrokeDashArray="4 2"/>
    18           </Border>
    19         </ControlTemplate>
    20       </Setter.Value>
    21     </Setter>
    22   </Style>
    23   <!--...............-->
    24 </ResourceDictionary>

    因为这个XAML文件作为资源使用,所以其根元素是ResourceDictionary,而不再是Window/Application等等。同时,资源文件也可以相互的嵌套,比如上面的包含的Shared.xaml文件。然后定义了一个Style,注意这里的目标类型为 Control.Template,也就是针对所有的控件模板有效,所以Style添加了一个x:Key属性。这样就阻止Style适用于当前的所有控件。我们必须显式的引用这个Style。相关内容,可以参考我前面的Style文章

    另一个需要说明的是<ControlTemplate>的子元素,可以是任何的VisualTree。比如这里的Border,也可以是Grid等等。好了,现在定义了一个名为ButtonFocusVisual的模板,下面只需要引用它即可。

     1 <Style TargetType="Button">
     2     <!--.............-->
     3     <Setter Property="FocusVisualStyle" Value="{StaticResource ButtonFocusVisual}"/>
     4     <!--.............-->
     5     <Setter Property="Template">
     6       <Setter.Value>
     7         <ControlTemplate TargetType="Button">
     8 
     9           <Border x:Name="Border" ......./>
    10 
    11           <ControlTemplate.Triggers>
    12             <Trigger Property="IsKeyboardFocused" Value="true">
    13               <Setter TargetName="Border" Property="BorderBrush" Value="{StaticResource DefaultedBorderBrush}" />
    14             </Trigger>           
    15           </ControlTemplate.Triggers>
    16 
    17         </ControlTemplate>
    18       </Setter.Value>
    19     </Setter>
    20 </Style>
    这是真正影响控件外观的代码。因为在定义Style的时候没有指定具体的x:Key,所以将影响所有的Button。如你所见,在 FocusVisualStyle这个属性(类型是Style)上我们用资源方式引用了前面定义的命名Style:ButtonFocusVisual。 接下来是定义Template,并为其子元素Border定义了一个名称。然后就是ControlTemplate的触发器。在 IsKeyboardFocused属性满足条件的情况下,我们把Border(注意这个Border不是类型,而是具体的某个对象的 BorderBrush修改为另一个静态资源。结合前面的Post,理解也就不难了。

    最后,我们还会发现一个有趣的问题:这个例子虽然是ControlTempalte,但工程名称却是SimpleStyle,从这一点我们也可以看出:Style和Template通常是配合使用才能真正的实现丰富的自定义功能。

  • 相关阅读:
    mac上finalShell的安装
    c 字符串与字符串操作
    .net5 MailKit
    c 99乘法表
    element 动态表单加自定义校验
    遇到的问题 vscode 问题
    vue-element-admin eslint 规则查询表
    利用html2canvas 导出网页 (只是用于自己的笔记,如果需要看配置,自行查找插件api)
    git 常用命令
    uniapp中自动打包微信小程序后自动上传代码
  • 原文地址:https://www.cnblogs.com/shawnzxx/p/2710046.html
Copyright © 2020-2023  润新知