原文:WPF中的ControlTemplate(控件模板)

WPF中的ControlTemplate(控件模板)
                                                                                                                        周银辉

WPF包含数据模板和控件模板,其中控件模板又包括ControlTemplate和ItemsPanelTemplate,这里讨论一下ControlTemplate。

其实WPF的每一个控件都有一个默认的模板,该模板描述了控件的外观以及外观对外界刺激所做出的反应。我们可以自定义一个模板来替换掉控件的默认模板以便打造个性化的控件。

与Style不同,Style只能改变控件的已有属性值(比如颜色字体)来定制控件,但控件模板可以改变控件的内部结构(VisualTree,视觉树)来完成更为复杂的定制,比如我们可以定制这样的按钮:在它的左办部分显示一个小图标而它的右半部分显示文本。

要替换控件的模板,我们只需要声明一个ControlTemplate对象,并对该ControlTemplate对象做相应的配置,然后将该ControlTemplate对象赋值给控件的Template属性就可以了。

ControlTemplate包含两个重要的属性:
1,VisualTree,该模板的视觉树,其实我们就是使用这个属性来描述控件的外观的
2,Triggers,触发器列表,里面包含一些触发器Trigger,我们可以定制这个触发器列表来使控件对外界的刺激发生反应,比如鼠标经过时文本变成粗体等。

参考以下代码

    <Button>
        <Button.Template>
          <ControlTemplate>
            <!--定义视觉树-->
            <Grid>
              <Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}"  Fill="{TemplateBinding Button.Background}"/>
              <TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center"  HorizontalAlignment="Center"  Text="{TemplateBinding Button.Content}" />
            </Grid>
            <!--定义视觉树_end-->            
          </ControlTemplate>
        </Button.Template>
      </Button>

在上面的代码中,我们修改了Button的Template属性,我们定义了一个ControlTemplate,在<ControlTemplate> ... </ControlTemplate>之间包含的是模板的视觉树,也就是如何显示控件的外观,我们这里使用了一个Ellipse(椭圆)和一个TextBlock(文本块)来定义控件的外观。
很容易联想到一个问题:控件(Button)的一些属性,比如高度、宽度、文本等如何在新定义的外观中表现出来呢?
我们使用TemplateBinding 将控件的属性与新外观中的元素的属性关联起来Width="{TemplateBinding Button.Width}" ,这样我们就使得椭圆的宽度与按钮的宽度绑定在一起而保持一致,同理我们使用Text="{TemplateBinding Button.Content}"将TextBlock的文本与按钮的Content属性绑定在一起。

除了定义控件的默认外观外,也许我们想还定义当外界刺激我们的控件时,控件外观做出相应的变化,这是我们需要触发器。参考以下代码:

<Button Content="test btn" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Grid.RowSpan="1"  >
        <Button.Template>
          <ControlTemplate>
            <!--定义视觉树-->
            <Grid>
              <Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}"  Fill="{TemplateBinding Button.Background}"/>
              <TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center"  HorizontalAlignment="Center"  Text="{TemplateBinding Button.Content}" />
            </Grid>
            <!--定义视觉树_end-->
            <!--定义触发器-->
            <ControlTemplate.Triggers>
              <Trigger  Property="Button.IsMouseOver"  Value="True">
                <Setter Property="Button.Foreground" Value="Red" />               
              </Trigger>
            </ControlTemplate.Triggers>
            <!--定义触发器_End-->
          </ControlTemplate>
        </Button.Template>
      </Button>

在上面的代码中注意到<ControlTemplate.Triggers>... </ControlTemplate.Triggers>之间的部分,我们定义了触发器 <Trigger  Property="Button.IsMouseOver"  Value="True">,其表示当我们Button的IsMouseIOver属性变成True时,将使用设置器<Setter Property="Button.Foreground" Value="Red" />  来将Button的Foreground属性设置为Red。这里有一个隐含的意思是:当Button的IsMouseIOver属性变成False时,设置器中设置的属性将回复原值。

你可以粘贴以下代码到XamlPad查看效果:

<Window 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="ControlTemplateTest" Height="300" Width="300"
    >
    <Grid ShowGridLines="True">
      
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="0.2*"/>
        <ColumnDefinition Width="0.6*"/>
        <ColumnDefinition Width="0.2*"/>
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition Height="0.3*"/>
        <RowDefinition Height="0.3*"/>
        <RowDefinition Height="0.4*"/>
      </Grid.RowDefinitions>

      <Button Content="test btn" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Grid.RowSpan="1"  >
        <Button.Template>
          <ControlTemplate>
            <!--定义视觉树-->
            <Grid>
              <Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}"  Fill="{TemplateBinding Button.Background}"/>
              <TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center"  HorizontalAlignment="Center"  Text="{TemplateBinding Button.Content}" />
            </Grid>
            <!--定义视觉树_end-->
            <!--定义触发器-->
            <ControlTemplate.Triggers>
              <Trigger  Property="Button.IsMouseOver"  Value="True">
                <Setter Property="Button.Foreground" Value="Red" />               
              </Trigger>
            </ControlTemplate.Triggers>
            <!--定义触发器_End-->
          </ControlTemplate>
        </Button.Template>
      </Button>
        
    </Grid>
</Window>

接下来的一个问题是:如果我要重用我的模板,应该怎么办呢?
你需要将模板定义为资源,其实大多数情况下,我们也是这样做的
参考以下代码:

  <Window.Resources>
    <ControlTemplate TargetType="Button" x:Key="ButtonTemplate">
        <!--定义视觉树-->
        <Grid>
          <Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}"  Fill="{TemplateBinding Button.Background}"/>
          <TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center"  HorizontalAlignment="Center"  Text="{TemplateBinding Button.Content}" />
        </Grid>
        <!--定义视觉树_end-->
        <!--定义触发器-->
        <ControlTemplate.Triggers>
          <Trigger  Property="Button.IsMouseOver"  Value="True">
            <Setter Property="Button.Foreground" Value="Red" />
          </Trigger>
        </ControlTemplate.Triggers>
        <!--定义触发器_End--> 
    </ControlTemplate>
  </Window.Resources>
  

上面的代码将我们原来的模板定义为窗体范围内的资源,其中TargetType="Button"指示我们的模板作用对象为Button,这样在整个窗体范围内的按钮都可以使用这个模板了,模板的使用方法也很简单:

<Button Content="test btn" Template="{StaticResource ButtonTemplate}" />

其中的ButtonTemplate是我们定义的模板的x:Key

你可以粘贴以下代码到XamlPad查看效果:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="ControlTemplateTest" Height="300" Width="300"
    >

  <Window.Resources>
    <ControlTemplate TargetType="Button" x:Key="ButtonTemplate">
        <!--定义视觉树-->
        <Grid>
          <Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}"  Fill="{TemplateBinding Button.Background}"/>
          <TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center"  HorizontalAlignment="Center"  Text="{TemplateBinding Button.Content}" />
        </Grid>
        <!--定义视觉树_end-->
        <!--定义触发器-->
        <ControlTemplate.Triggers>
          <Trigger  Property="Button.IsMouseOver"  Value="True">
            <Setter Property="Button.Foreground" Value="Red" />
          </Trigger>
        </ControlTemplate.Triggers>
        <!--定义触发器_End--> 
    </ControlTemplate>
  </Window.Resources>
  
  
    <Grid ShowGridLines="True">
      
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="0.2*"/>
        <ColumnDefinition Width="0.6*"/>
        <ColumnDefinition Width="0.2*"/>
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition Height="0.3*"/>
        <RowDefinition Height="0.3*"/>
        <RowDefinition Height="0.4*"/>
      </Grid.RowDefinitions>

      <Button Content="test btn1" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1"  />
      <Button Content="test btn2" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Grid.RowSpan="1"  Template="{StaticResource ButtonTemplate}" />
      <Button Content="test btn2" Grid.Column="2" Grid.ColumnSpan="1" Grid.Row="2" Grid.RowSpan="1"  Template="{StaticResource ButtonTemplate}" />
        
        
    </Grid>
</Window>

额外提一下的是,我们也可以在触发器中,调用一个故事板来达到对事件响应时的动画效果
参考以下代码

 <!--定义动画资源-->
      <ControlTemplate.Resources>
        <Storyboard x:Key="MouseClickButtonStoryboard">
          <DoubleAnimationUsingKeyFrames Storyboard.TargetName="faceEllipse" Storyboard.TargetProperty="Width" BeginTime="00:00:00">
            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="50"/>
            <SplineDoubleKeyFrame KeyTime="00:00:00.3" Value="100"/>
          </DoubleAnimationUsingKeyFrames>
        </Storyboard>
      </ControlTemplate.Resources>

我们为模板定义了一个动画资源,此后在模板的触发器中我们就可以调用该资源来实现一个动画效果了:

       <EventTrigger RoutedEvent="Mouse.MouseDown" SourceName="faceEllipse">
          <EventTrigger.Actions>
            <BeginStoryboard Storyboard="{StaticResource MouseClickButtonStoryboard}"/>
          </EventTrigger.Actions>    
        </EventTrigger>

你可以粘贴以下代码到XamlPad查看效果:

<Window 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="ControlTemplateTest" Height="300" Width="300"
    >

  <Window.Resources>
    <ControlTemplate TargetType="Button" x:Key="ButtonTemplate">
      <!--定义视觉树-->
      <Grid>
        <Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}"  Fill="{TemplateBinding Button.Background}"/>
        <TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center"  HorizontalAlignment="Center"  Text="{TemplateBinding Button.Content}" />
      </Grid>
      <!--定义视觉树_end-->

      <!--定义动画资源-->
      <ControlTemplate.Resources>
        <Storyboard x:Key="MouseClickButtonStoryboard">
          <DoubleAnimationUsingKeyFrames Storyboard.TargetName="faceEllipse" Storyboard.TargetProperty="Width" BeginTime="00:00:00">
            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="50"/>
            <SplineDoubleKeyFrame KeyTime="00:00:00.3" Value="100"/>
          </DoubleAnimationUsingKeyFrames>
        </Storyboard>
      </ControlTemplate.Resources>
      <!--定义动画资源_end-->

      <!--定义触发器-->
      <ControlTemplate.Triggers>
        <Trigger  Property="Button.IsMouseOver"  Value="True">
          <Setter Property="Button.Foreground" Value="Red" />
        </Trigger>
        <EventTrigger RoutedEvent="Mouse.MouseDown" SourceName="faceEllipse">
          <EventTrigger.Actions>
            <BeginStoryboard Storyboard="{StaticResource MouseClickButtonStoryboard}"/>
          </EventTrigger.Actions>    
        </EventTrigger>
        <EventTrigger RoutedEvent="Mouse.MouseDown" SourceName="txtBlock">
          <EventTrigger.Actions>
            <BeginStoryboard Storyboard="{StaticResource MouseClickButtonStoryboard}"/>
          </EventTrigger.Actions>
        </EventTrigger>
      </ControlTemplate.Triggers>
      <!--定义触发器_End-->
      
    </ControlTemplate>


   
  </Window.Resources>
  
  
    <Grid ShowGridLines="True">
      
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="0.2*"/>
        <ColumnDefinition Width="0.6*"/>
        <ColumnDefinition Width="0.2*"/>
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition Height="0.3*"/>
        <RowDefinition Height="0.3*"/>
        <RowDefinition Height="0.4*"/>
      </Grid.RowDefinitions>

      <Button Content="test btn1" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1"  />
      <Button Content="test btn2" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Grid.RowSpan="1"  Template="{StaticResource ButtonTemplate}" />
      <Button Content="test btn2" Grid.Column="2" Grid.ColumnSpan="1" Grid.Row="2" Grid.RowSpan="1"  Template="{StaticResource ButtonTemplate}" />

        
    </Grid>
</Window>

最好的模板示例:我们知道每个控件都有自己默认的模板,这是MS编写的,如果我们能够得到这些模板的XAML代码,那么它将是学习模板的最好的示例,
要想获得某个控件ctrl的默认模板,请调用以下方法:

string GetTemplateXamlCode(Control ctrl)
        {

            FrameworkTemplate template = ctrl.Template;

            string xaml = "";

            if (template != null)
            {
                
                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);

                try
                {
                    XamlWriter.Save(template, xmlwrite);
                    xaml = strbuild.ToString();
                }
                catch (Exception exc)
                {
                    xaml = exc.Message;
                }
            }
            else
            {
                xaml = "no template";
            }

            return xaml;
        }

WPF中的ControlTemplate(控件模板)的更多相关文章

  1. WPF中的ControlTemplate(控件模板)(转)

    原文地址 http://www.cnblogs.com/zhouyinhui/archive/2007/03/28/690993.html WPF中的ControlTemplate(控件模板)     ...

  2. [转]WPF中的ControlTemplate(控件模板)

    WPF中的ControlTemplate(控件模板)                                                                           ...

  3. WPF 中动态改变控件模板

    在某些项目中,可能需要动态的改变控件的模板,例如软件中可以选择不同的主题,在不同的主题下软件界面.控件的样式都会有所不同,这时即可通过改变控件模板的方式实现期望的功能. 基本方法是当用户点击切换主题按 ...

  4. WPF基础篇之控件模板(ControlTemplate)

    WPF中每一个控件都有一个默认的模板,该模板描述了控件的外观以及外观对外界刺激所做出的反应.我们可以自定义一个模板来替换掉控件的默认模板以便打造个性化的控件. 与Style不同,Style只能改变控件 ...

  5. Xamarin XAML语言教程构建ControlTemplate控件模板 (四)

    Xamarin XAML语言教程构建ControlTemplate控件模板 (四) 2.在页面级别中构建控件模板 如果开发者要在页面级别中构建控件模板,首先必须将ResourceDictionary添 ...

  6. Xamarin XAML语言教程构建ControlTemplate控件模板 (二)

    Xamarin XAML语言教程构建ControlTemplate控件模板 (二) (2)打开MainPage.xaml文件,编写代码,将构建的控件模板应用于ContentView中.代码如下: &l ...

  7. Xamarin XAML语言教程构建ControlTemplate控件模板

    Xamarin XAML语言教程构建ControlTemplate控件模板 控件模板ControlTemplate ControlTemplate是从Xamarin.Forms 2.1.0开始被引入的 ...

  8. 在WPF中使用WinForm控件方法

    1.      首先添加对如下两个dll文件的引用:WindowsFormsIntegration.dll,System.Windows.Forms.dll. 2.      在要使用WinForm控 ...

  9. WPF中的image控件的Source赋值

    WPF中的Image控件Source的设置 1.XAML中 简单的方式(Source="haha.png"); image控件的Source设置为相对路径后(Source=&quo ...

随机推荐

  1. March 15 2017 Week 11 Wednesday

    The starting point of all achievements is desire. 成功的第一步是渴望. Only you desire for somethings, you can ...

  2. Python3条件控制语句

    Python条件语句是通过一条或多条语句的执行结果(True或者False)来决定执行的代码块. if语句 if 条件: 代码块 elif 条件: 代码块 else: 代码块 python中用elif ...

  3. 遍历目录树 - Unicode 模式

    =info     遍历目录树 支持 Unicode     Code by 523066680@163.com     2017-03         V0.5 使用Win32API判断目录硬链接 ...

  4. HDU 6034 6038

    6034:给每个字母26进制的贪心.例如一个字母 c = 7*26^89 + 6*26^50.... 这个字符串有10^5长度.普通的大整数会超时,这里要稀疏这个大数一个pair<int,int ...

  5. Python Day 15 递归、匿名函数、内置函数

    阅读目录 内容回顾 生成器的send方法 递归 匿名函数 内置函数 ##内容回顾 #1.带参装饰器 - 自定义 | wraps def wrap(info) def outer1(func): fro ...

  6. Java读取classpath下的文件

    写Java程序时会经常从classpath下读取文件,是时候该整理一下了,并在不断深入的过程中,陆续补充上. 现在Java project 都以maven项目居多, 比如像下面这样的一个项目结构: 编 ...

  7. 【luogu P1494 [国家集训队]小Z的袜子】 题解

    题目链接:https://www.luogu.org/problemnew/show/P1494 #include <cstdio> #include <algorithm> ...

  8. Android学习笔记_51_转android 加载大图片防止内存溢出

    首先来还原一下堆内存溢出的错误.首先在SD卡上放一张照片,分辨率为(3776 X 2520),大小为3.88MB,是我自己用相机拍的一张照片.应用的布局很简单,一个Button一个ImageView, ...

  9. 转载:C/C++ typedef用法

    原文链接:http://www.cnblogs.com/ggjucheng/archive/2011/12/27/2303238.html 引言 typedef 声明,简称 typedef,为现有类型 ...

  10. 关于In-App Purchase(内购)的注意事项

    前言:关于In-App Purchase(内购)的注意事项 一点注意事项: 内购应该有游客身份的购买选项,不然可能被拒,前一段时间我这边一直是因为没有游客身份购买被拒. 在验证内购结果的时候要注意使用 ...