style和template的区别
Style是一个对象用来设置一个控件的各种属性
Style所能做的是设置控件上已经存在的属性的值
设想我们的控件是一辆车, 一个样式可以说成类似"轮子大小=17","车身颜色=樱桃红色"...等等
Template实际上是定义车有哪些部分组成. 举例来说, 一个便宜的敞篷车的模板可能根本没有顶棚, 或者一辆车的模板能决定他有两个门或是四个门, 有4个或8个车轮等等.
我在解释两者的时候总是告诉别人, 拿海盗来说模板定义了他的骨架,样式就是他如何打扮(我喜欢拿海盗做比喻因为一些有一只眼睛,或一条腿,或一只胳膊等,是模板使用的很好的样例)。
有意思的是通过样式可以设置控件的任何属性, 包括模板本身, 因为他就是一个属性, 所以你看到的那些工具(Blend)最常做的就是当你想要覆盖一个控件的模板的时候, 他们覆盖了那个控件整个的样式和模板属性.
不明白没关系, 下面的文章可以让你明白的.
generic.xaml的神奇之处是用来定义一个控件的默认外观. 让我们假设我们想创建一个GelButton控件:
代码:
public class GelButton : Button
{
}
足够简单, 现在我们想在Page.xaml里使用它, 我们在页面里增加了namespace和这个控件的定义.
代码:
<UserControl x:Class="StylingSample.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300"
xmlns:samples="clr-namespace:StylingSample">
<StackPanel Width="50" >
<Button Content="Top" Height="50"/>
<samples:GelButton Content="Cream" Height="50"></samples:GelButton>
<Button Content="Bottom" Height="50"> </Button>
</StackPanel>
</UserControl>
看到下面的结果你会感到惊奇吗?
我不能告诉你你应该感到惊讶或不惊讶,但我可以告诉你发生了什么!
控件默认是无外观的, 你需要给它定义外观. 这是通过给控件指定一个合法的控件模板来完成的[通过Control类的Template属性]
要指定这个模板属性的话, 你可以像下面这么做:
代码:
public GelButton ()
{
this.Template = XamlReader.Load ("<ControlTemplate xmlns='http://schemas.microsoft.com/client/2007'
><Grid ..> </Grid></ControlTemplate>");
}
但是一个更好的方式是把控件模板的定义保存在一个叫"generic.xaml"的资源文件(Resource Dictionary), 然后神奇的运行时会把这个文件自动加载上. 你的模板会通过TargetType 属性关联到你的控件上. 这个模板将成为我们所称的“内置样式”(“built-in style”)。 这里有些创建内置样式的细节.
Generic.xaml是一个包含在你程序集里的资源字典文件. 默认的generic.xaml看上去应该这样:
代码:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
</ResourceDictionary>
为了定义我们的GelButton控件的外观, 我们需要从一些默认的模板开始入手. 我建议用David Anson的 Stylebrowser application工具从Button中拷贝默认样式到generic.xaml文件里.
[不幸的是button的默认模板太大了, 因此本着练习的目的我用了一个更简单的模板].
代码:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:samples="clr-namespace:StylingSample;assembly=StylingSample"
>
<Style TargetType="samples:GelButton">
<Setter Property="Background" Value="Black" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="samples:GelButton">
<Grid x:Name="RootElement" >
<Ellipse Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Fill="{TemplateBinding Background}"
/>
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Foreground="{TemplateBinding Foreground}"
VerticalAlignment="Center" HorizontalAlignment="Center"
/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
让我们剖析需要创建该模板的工作:
1. 增加一个xmlns:samples到资源字典里.
2. 下一步我们定义样式并且设置我们想要使用这个样式的目标控件类型(TargetType); 通常你在定义样式时设置目标类型是为了你的模板能发现属性和使他们生效, 但是当在generic.xaml里做这个设置的时候, 神奇的事情是通过这个声明信息把样式和目标控件类型建立关联; 现在一旦当控件被实例化后, 如果没有其他样式被使用, 这个样式会被当作为默认样式.
3. 剩下的是简单的样式定义. 模板绑定(TemplateBinding) 可能是最有意思的部分, 这会创建一个在我们设置的属性和实际控件属性的绑定. 举例来说:
<ContentPresenter Foreground={TemplateBinding Foreground}" > 创建了一个绑定,把 content presenter的Foreground属性和实际控件的Foreground属性联系起来.
这可以让我们在设计工具里更容易定义用户界面样式. 在Blend或是XAML代码里你可以声明一个按钮 <GelButton Foreground=”Red” > 或是 <GelButton Foreground=White>
更多信息你可以看看Karen Corby的 MIX presentation on “Rich,Dynamic UIs”.
现在我们可以运行上面相同的代码, 只是更改了一下资源字典里的样式:
既然我已经对Background和Foreground创建了一个模板绑定(Templatebinding ), 我可以更进一步
代码:
<UserControl x:Class="StylingSample.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300"
xmlns:samples="clr-namespace:StylingSample">
<StackPanel Width="50" Margin="0,20,0,0">
<samples:GelButton Content="" Height="20.4"
RenderTransformOrigin="0.5,0.5" Width="48.8" Canvas.ZIndex="2">
<samples:GelButton.Background>
<RadialGradientBrush>
<GradientStop Color="#FFF5DEB3"/>
<GradientStop Color="#FFE0B05C" Offset="0.826"/>
</RadialGradientBrush>
</samples:GelButton.Background>
</samples:GelButton>
<samples:GelButton Content="Ham" Height="16" Canvas.ZIndex="1">
<samples:GelButton.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFD64141"/>
<GradientStop Color="#FFE23939" Offset="1"/>
<GradientStop Color="#FEDAB6B6" Offset="0.43299999833106995"/>
</LinearGradientBrush>
</samples:GelButton.Background>
</samples:GelButton>
<samples:GelButton Content="" Height="16" >
<samples:GelButton.Background>
<RadialGradientBrush>
<GradientStop Color="#FFF5DEB3"/>
<GradientStop Color="#FFECC06E" Offset="0.991"/>
</RadialGradientBrush>
</samples:GelButton.Background>
</samples:GelButton>
</StackPanel>
</UserControl>
我只浪费你10分钟和我的40分钟时间来介绍generic.xaml和内置样式。我曾答应回答几个问题,他们是:
1 . 内置样式的好处, 为啥用generic.xaml代替硬编码的模板?
相对于在代码设置模板来说, 把你的模板放到资源字典是一种很好的方式因为你能方便的替换这些模板. 想象一下如果你需要创建三个主题样式, 硬编码方式处理的话会很难.
另外如果你把模板放到资源字典文件中这些模板可以引用该字典中的其他资源.
2. 为啥我看过的所有例子都没用使用内置样式, 总是告诉我们使用在App.xaml保存的内嵌样式?
内置样式被设计为提供给控件作者使用, 当你编写一个控件时, 你提供控件的外观和操作. 大多数例子纯粹是简单的自定义一个按钮样式, 因此他们采取了不同的方式.(直接在xaml里声明相关属性)
3. 在上面的演示里,你继承了按钮,除了提供内置样式外什么都没做,这是最好的做法吗?
我喜欢这一做法(但我来自一个企业背景下,我们创造臃肿的框架,往往只是为了继承来创建一个抽象对象适应以后一些事情的变化), 一个好处是你可以在任何地方使用你的按钮不用有明确指定一个样式。当然缺点是继承需要额外的一点性能和内存,但从我所看到过的来说这是非常微不足道的。
同样,我不能称之为最佳实践,更多是我个人的喜好。
4. 内置样式好像可以让我在一个地方改变所有控件样式?我不想每个按钮就添加<Button Style=”{StaticResource GelButtonStyle}” >.
这是对的,如果你能继承并且类不是密封的。尽管如此,经过创建一些解决方案,我意识到我有一个集中的错觉[是我编的名词]。
理由是使用内置样式我可以在一个地方改变样式. 这和使用App.xaml是相同你可以在一个地方为所有对象改变样式. 你是复制了很多是样式的名称,但风格本身在一个地方。
5. 什么时候不能使用内置样式?
如果类是密封的或是他们有一个受保护的Template属性, 那么你就不能使用内置样式.
6. 我能否仅创建一个generic.xaml覆盖System.Windows.Controls的模板而不继承?
据我所知不能这样做. 听上去不是一个好主意, 我曾试着这么做想看看能否可行, 但是他并不工作.
7. 使用内置样式会不会破坏或影响控件的状态和其他组件?
不会, 如果您的样式使用相同的名称,代码仍然会选择一切就好像是一个内联样式。