一、简介
WPF包含数据模板和控件模板,其中控件模板又包括ControlTemplate和ItemsPanelTemplate,这里讨论一下ControlTemplate。
其实WPF的每一个控件都有一个默认的模板,该模板描述了控件的外观以及外观对外界刺激所做出的反应。我们可以自定义一个模板来替换掉控件的默认模板以便打造个性化的控件。
与Style不同,Style只能改变控件的已有属性值(比如颜色字体)来定制控件,但控件模板可以改变控件的内部结构(VisualTree,视觉树)来完成更为复杂的定制,比如我们可以定制这样的按钮:在它的左半部分显示一个小图标而它的右半部分显示文本。
要替换控件的模板,我们只需要声明一个ControlTemplate对象,并对该ControlTemplate对象做相应的配置,然后将该ControlTemplate对象赋值给控件的Template属性就可以了。
二、ControlTemplate包含两个重要的属性
VisualTree,该模板的视觉树,其实我们就是使用这个属性来描述控件的外观的。
Triggers,触发器列表,里面包含一些触发器Trigger,我们可以定制这个触发器列表来使控件对外界的刺激发生反应,比如鼠标经过时文本变成粗体等。
三、实例
1、定制可视化树(VisualTree)
<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>
comtroltemplate_test
</Button>
代码效果:
在上面的代码中,我们修改了Button的Template属性,我们定义了一个ControlTemplate,在<ControlTemplate> ... </ControlTemplate>之间包含的是模板的视觉树,也就是如何显示控件的外观,我们这里使用了一个Ellipse(椭圆)和一个TextBlock(文本块)来定义控件的外观。
很容易联想到一个问题:控件(Button)的一些属性,比如高度、宽度、文本等如何在新定义的外观中表现出来呢?
我们使用TemplateBinding 将控件的属性与新外观中的元素的属性关联起来Width="{TemplateBinding Button.Width}" ,这样我们就使得椭圆的宽度与按钮的宽度绑定在一起而保持一致,同理我们使用Text="{TemplateBinding Button.Content}"将TextBlock的文本与按钮的Content属性绑定在一起。
2、定制触发器Triggers
<Button Grid.Row="1" Grid.Column="0" Margin="10">
<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>
comtroltemplate_test
</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时,设置器中设置的属性将回复原值。
3、ControlTemplate的重用
你只需要将模板定义为资源,其实大多数情况下,我们也是这样做的。
<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,其中的ButtonTemplate是我们定义的模板的x:Key。
这样在整个窗体范围内的按钮都可以使用这个模板了,模板的使用方法也很简单:
<Button Grid.Column="0" Template="{StaticResource ButtonTemplate}" Content="comtroltemplate_test"/>
<Button Grid.Column="1" Template="{StaticResource ButtonTemplate}" Content="test btn2" />
<Button Grid.Column="2" Template="{StaticResource ButtonTemplate}" Content="test btn3" />
代码效果:
4、在trigger中使用storyboard,事件响应中添加的动画效果
<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="MouseClickStoryboard">
<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="{StaticResourceMouseClickStoryboard}"/>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseDown" SourceName="txtBlock">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource MouseClickStoryboard}"/>
</EventTrigger.Actions>
</EventTrigger>
</ControlTemplate.Triggers>
<!--定义触发器_End-->
</ControlTemplate>
</Window.Resources>
四、获取.Net中原生控件的的默认ControlTemplate
我们知道每个控件都有自己默认的模板,这是MS编写的,如果我们能够得到这些模板的XAML代码,那么它将是学习模板的最好的示例,
要想获得某个控件ctrl的默认模板,请调用以下方法:
string GetTemplateXamlCode(Control ctrl)
{
string xaml = "no template";
FrameworkTemplate template = ctrl.Template;
if (template != null)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = new string(' ', );
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;
}
} return xaml;
}