编码前
在上一篇博客中,写的是一个UserControl的子类,它具有固定的外观(虽然也可以通过样式来进行修改,但收到的限制很大)。如果你想要使用这个空间的逻辑,但是希望在使用的时候可以更改控件的外观,比如希望将预览颜色的矩形放在滑动条的左边(控件中是放在右侧),这个时候你就可以定义一个无外观的控件(但是你可以编写他的默认模板)。在使用这个无外观控件的时候,你可以使用模板来改变控件的外观,而你仍然可以使用这个控件中所定义的属性、事件等控件的功能。
1.依赖属性、标准属性(属性包装器)、路由事件等和之前定义的ColorPicker是一样的。
2.如果你让控件呈现默认的样子,你可以在/Themes/Generic.xaml中写一个默认模板,并在ColorPicker的构造函数中进行设置。
3.将控件中设置一些部件,以便在控件使用者可以更容易地重写模板,并且不失控件的功能。
编码
- 常见一个类,命名为ColorPicker,继承自Control。因为控件需要做到与用户的交互,所以继承自再上层的类(如FrameworkElement)就不太合适了。
-
因为此控件要使用自定义的一个默认样式,所以需要通知系统。方式就是在控件的构造函数中使用DefaultStyleKeyProperty的属性。具体如下:
1 public ColorPicker() 2 { 3 DefaultStyleKey = typeof (ColorPicker); 4 }
这样在使用控件的时候,系统就会去查找他的默认样式
- 现在可以写它的的默认样式。
<1>新建一个Themes的文件夹
<2>在Themes文件夹下新加一个名为Generic的类。新建成功后将类的内容清空,并将Generic的扩展名改为.xaml
<3>在空白的xaml文件中加入如下内容<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CustomeControl"> </ResourceDictionary>
记得要引入你的自定义控件的命名空间
<4>下面就可以写你所要自定的默认样式了<Style TargetType="local:ColorPicker"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:ColorPicker"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <Slider x:Name="PART_RedSlider" Minimum="0" Maximum="255"/> <Slider x:Name="PART_GreenSlider" Grid.Row="1" Minimum="0" Maximum="255" /> <Slider x:Name="PART_BlueSlider" Grid.Row="2" Minimum="0" Maximum="255" /> <Rectangle Grid.Column="1" Grid.RowSpan="3" Width="100" Height="100" Stroke="White" StrokeThickness="2"> <Rectangle.Fill> <SolidColorBrush x:Name="PART_PreviewBrush"/> </Rectangle.Fill> </Rectangle> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
当使用控件时会自动查找到这个样式。控件显示你所定义的样式效果。
- 这个时候自定义控件中只有一个构造函数,现在要定义控件要会使用的属性、路由事件等。内容和上一篇博客中一样,就不写了。
- 我们的这个控件需要与模板中的一些元素建立联系。
<1>首先要重载Control中的OnApplyTemplate函数。
<2>这里我们使用到Silverlight中的绑定来建立联系,那么怎样取到模板中的对象呢,答案是使用GetTemplateChild函数。
<3>取到模板中的对象以后就可以进行绑定了,将元素中的某些依赖属性绑定到你想要的控件中的依赖属性。完整的代码如下:1 public override void OnApplyTemplate() 2 { 3 base.OnApplyTemplate(); 4 Slider redSlider = GetTemplateChild("PART_RedSlider") as Slider; 5 Slider greenSlider = GetTemplateChild("PART_GreenSlider") as Slider; 6 Slider blueSlider = GetTemplateChild("PART_BlueSlider") as Slider; 7 8 BindingSlider(redSlider, "Red"); 9 BindingSlider(greenSlider, "Green"); 10 BindingSlider(blueSlider, "Blue"); 11 12 SolidColorBrush brush = GetTemplateChild("PART_PreviewBrush") as SolidColorBrush; 13 if (brush != null) 14 { 15 Binding binding = new Binding("Color"); 16 binding.Source = this; 17 binding.Mode = BindingMode.TwoWay; 18 BindingOperations.SetBinding(brush, SolidColorBrush.ColorProperty, binding); 19 } 20 } 21 22 private void BindingSlider(Slider slider, string color) 23 { 24 if (slider == null) 25 return; 26 Binding binding=new Binding(); 27 binding.Source = this; 28 binding.Path =new PropertyPath(color); 29 binding.Mode=BindingMode.TwoWay; 30 slider.SetBinding(Slider.ValueProperty, binding); 31 }
需要注意的地方:1、为了增强控件的容错能力,要有个检验空值的情况。
2、由于SolidColorBrush不是继承自FrameworkElement,所以他没有像Slider的SetBinding函数,而要使用BindingOperations来实行绑定(这个问题纠结了好久)。
3、还有就是绑定源和绑定目标要区分清楚,马虎的我又纠结了一会儿。
4、将将控件预期的FrameworkElement对象(比如此例中的PART_RedSlider和PART_PreviewBrush等)使用TemplatePartAttribute,指定预期元素的名称和类型。[TemplatePart(Name = "PART_RedSlider", Type = typeof(Slider)), TemplatePart(Name = "PART_GreenSlider", Type = typeof(Slider)), TemplatePart(Name = "PART_BlueSlider", Type = typeof(Slider)), TemplatePart(Name = "PART_PreviewBrush", Type = typeof(SolidColorBrush))] public class ColorPicker :Control { ...... }
- 现在自定义控件就算是完成了。如下使用即可(记得引入程序集和命名空间啊):
<control:ColorPicker Margin="12" Color="Violet" ColorChanged="ColorPicker_OnColorChanged"></control:ColorPicker>
- 如果你想使用重新编写的样式,你可以在你所在的页面资源或是其他地方定义一个新的样式,例如
<phone:PhoneApplicationPage.Resources> <Style x:Key="style" TargetType="control:ColorPicker"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="control:ColorPicker"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Ellipse Stroke="Red" VerticalAlignment="Top" StrokeThickness="2" Width="100" Height="200"> <Ellipse.Fill> <SolidColorBrush x:Name="PART_PreviewBrush"/> </Ellipse.Fill> </Ellipse> <StackPanel Grid.Column="1" > <Slider Height="200" Minimum="0" Maximum="255" Orientation="Vertical" x:Name="PART_RedSlider"/> <TextBlock TextAlignment="Center" Text="Red" Foreground="Red"/> </StackPanel> <StackPanel Grid.Column="2"> <Slider Height="200" Minimum="0" Maximum="255" Orientation="Vertical" x:Name="PART_GreenSlider"/> <TextBlock TextAlignment="Center" Text="Green" Foreground="Green"/> </StackPanel> <StackPanel Grid.Column="3"> <Slider Height="200" Minimum="0" Maximum="255" Orientation="Vertical" x:Name="PART_BlueSlider"/> <TextBlock TextAlignment="Center" Text="Blue" Foreground="Blue"/> </StackPanel> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </phone:PhoneApplicationPage.Resources>
这里重新设计了控件的样式,只要在控件的时候引用的可以了:
<control:ColorPicker Style="{StaticResource style}" Margin="12" Color="Violet" ColorChanged="ColorPicker_OnColorChanged"></control:ColorPicker>
此时重现的效果图就是这个样子:
关于样式和模板,请参考MSDN即可。
编码后
- 自定义控件的时候将你所要使用到与逻辑部分有关的模板里的空间进行命名,类似这里的Part_RedSlider,为了控件使用者能够方便正常的重载样式,将这些做成部件。
做成部件后,控件使用者必须在他自己的样式中使用这个部件,才使得控件功能的完整 - 还可以在自定义控件中加入状态,根据不同的状态有不同的显示,在状态的转换中可以加入一些动画来增强效果,下一篇将会涉及到此类内容。
- 关于使用ControlTemplate来定义现有控件外观以及使用ControlTemplate来创建新的控件,可以查看MSDN上的相关详细的介绍。