WPF里最简单的控件的Style你能写对么?(默认Style是有问题的)
第一个问题,WPF最简单的常用控件是什么?(先声明Path不算控件)
无论从控件的功能还是从控件的外观来看,最简单都是那个在菜单和Ribbon里很常见的分隔线。没错,它已经从WinForm里的一个字符串(-)进化成一个独立控件(Separator)了。
Separator继承于Control,而且没有添加一个属性或事件。当仁不让地成为了WPF中最简单控件的首选。
其全部Style代码如下所示:
<Style x:Key="DefaultSeparatorStyle" TargetType="{x:Type Separator}">
<Setter Property="Background"
Value="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}"/>
<Setter Property="Margin" Value="0,2,0,2"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Separator}">
<Border Height="1" SnapsToDevicePixels="true"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
这么简单的Style,还拿出来说什么?似乎也没有什么好学的啊。不就个TemplateBinding和绑定到SystemColors看上去还高深一些么?
好现在让你做个事情你就明白了。如果我们想要一个正方形的Button会怎么办?注意是Button,不是Separator。
代码如下:
<Button Width="30" Height="30"/>
很简单,那么如果我要一个高度为2个像素的Separator怎么办?也一样写:
<Separator Height="2"/>
行么?
请再鄙视一眼那个默认Style,行么?如果可行,我就不写这篇文章了。
看到Style里的Border了么?第一个属性就是Height=1。这个属性NB啊,无论你Separator多高,我就是1个像素了。那如果就是想画一个2个像素高的Separator要怎么办啊?
重写Style!就是把默认Style里的Height=1去了,再用Setter加个默认高为1,再在想让Separator为2个像素高的地方设置Height=2。万事大吉。要重写Style啊。
这篇文章主要不是来介绍如果写个高度为2的Separator的。而是来强调Style的灵活性的。如果Style不够灵活,这次因为改Height就要改Style,下次是不是改颜色也要Style呢?还有BorderBrush,BorderThickness,Foreground,OpacityMask等等,是不是改个属性,就要新建一个Style呢?
肯定不行啊。所以在你写个Style的第一行的时候,就应该时刻惦记着,你在Style做的样子,Style的使用者是不是可以很方便改变这个Style的默认外观,做一些定制化。有时的确不能保证所有的Template内的控件都可以定制化,这时就要做权衡,让重要的控件的外观可定制化,次要的可以Hard Code在Style内部。
所以在WPF控件的默认Style里,你可以看到很多TemplateBinding,这些Binding的存在就是为了不在Template里Hard Code控件的属性。
但是看到Separator的默认Style,我已经有些出离愤怒了。这可是打着Microsoft商标的微软特产啊,而且是其中技术含量最低的一个Style。结果却犯了最低级的错误,而且还不只一个。
错误如下:
1. Hard Code高度为1
2. Hard Code SnapsToDevicePixels为Ture
3. 错误地使用了Border去绘制直线。应该使用Path或Line。请不要为什么,谢谢。最基本的OOD了。即使不考虑逻辑,功能上也不正确的。
在此更提醒为数不多的WPF初学者们,学习要专注,可以沉迷于微软的技术,但不要迷信微软的技术。我也没有非难或是指摘微软员工的意思,Bug哪都有。找到Bug,Fix或是Workaround才是主要的。
正确的Style有很多,下面一种供大家参考。
<Style x:Key="CorrectSeparatorStyle" TargetType="{x:Type Separator}">
<Setter Property="Background"
Value="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}"/>
<Setter Property="Margin" Value="0,2,0,2"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="Height" Value="1"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Separator}">
<Line Stretch="Fill" X2="1"
Stroke="{TemplateBinding Background}"
StrokeThickness="{TemplateBinding Height}"
StrokeStartLineCap="Square" StrokeEndLineCap="Square"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
解释一下:
1. 首先,这里的X2=”1”并不属于Hard Code。
2. StrokeStartLineCap和StrokeEndLineCap属于Hard Code。但是不影响当然Separator的使用。Separator本身就没有这些属性让人设置的。
3. Height和SnapsToDevicePixel在Setter中设置默认值。
如果让我来写个Separator这个控件,我会把Line或是Path的一些有用的属性添加给Separator,就可以让Separator更加灵活。
在实际的项目中,如果的确需要一个强大的Separator控件,外观很灵活的,又不想写个单独的AdvSeparator控件。最简单的一个办法就是不用Separator,直接用Line,然后给Line定义一个SeparatorLineStyle。参考示例如下:
<Style x:Key="SeparatorLineStyle"
TargetType="{x:Type Line}">
<Setter Property="X2" Value="1"/>
<Setter Property="Height" Value="1"/>
<Setter Property="Stretch" Value="Fill"/>
<Setter Property="StrokeEndLineCap" Value="Square"/>
<Setter Property="StrokeStartLineCap" Value="Square"/>
<Setter Property="StrokeThickness"
Value="{Binding Height, RelativeSource={RelativeSource Self}}"/>
<Setter Property="Stroke"
Value="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}"/>
</Style>
这样,既可以用到Line的所有高级功能去绘制一条Separator,又不需要自定义一个控件。
Style可以很强悍,也可以很破烂。