通常,当WPF常用的控件不能满足我们的需求时,我们就会创建自己的控件,有用户控件和自定义控件两种。就很想winform中的,用户控件可以基于当前的控件组合成我们需要的控件,而自定义控件那么就是从0做起,不过继承自一个和我们功能相似的类会效果更好,比如CustomControl继承Button的一些方法特性。
UserControl非常好理解,网上有许多教程。
CustomControl由于其没有Xaml界面,界面是放到Themes/Generic.xaml中来定义的,所以在开发中,特别是初学者带来一些困惑,今天怒搞一天,把自己的经验分享一下,也希望前辈们能多多指教。
好,创建一个CustomControl,发现目录机构如下
我们并没有看到CustomControl.xaml文件,打开CustomControl1.cs,代码如下
static TemControl() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TemControl), new FrameworkPropertyMetadata(typeof(TemControl))); }
其实,这里就是为我们的控件指定了一个默认的外观,这个外观在Themes/Generic.xaml里面,这是整个资源文件的代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:TemControl"> <Style TargetType="{x:Type local:TemControl}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:TemControl}"> <ControlTemplate.Resources> <Storyboard x:Key="Alarm"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="background"> <EasingDoubleKeyFrame KeyTime="0" Value="1"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </ControlTemplate.Resources> <!--<ControlTemplate.Triggers> <EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown" SourceName="background"> <BeginStoryboard Storyboard="{StaticResource Alarm}"/> </EventTrigger> </ControlTemplate.Triggers>--> <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="Tempreature" Width="250" Height="280" Clip="F1 M 0,0L 250,0L 250,280L 0,280L 0,0"> <Canvas x:Name="background" Width="250" Height="280" Canvas.Left="0" Canvas.Top="0" RenderTransformOrigin="0.5,0.5"> <Canvas.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform/> <TranslateTransform/> </TransformGroup> </Canvas.RenderTransform> <Path x:Name="Path" Width="230" Height="230" Canvas.Left="10" Canvas.Top="4.99991" Stretch="Fill" Data="F1 M 20,4.99991L 230,4.99991C 235.523,4.99991 240,9.47702 240,14.9999L 240,225C 240,230.523 235.523,235 230,235L 20,235C 14.4772,235 10,230.523 10,225L 10,14.9999C 10,9.47702 14.4772,4.99991 20,4.99991 Z "> <Path.Fill> <LinearGradientBrush StartPoint="0.5,1.25168" EndPoint="0.5,-0.25168"> <LinearGradientBrush.GradientStops> <GradientStop Color="#FF000000" Offset="0"/> <GradientStop Color="#FF4C4E4C" Offset="0.748837"/> <GradientStop Color="#FF989D98" Offset="1"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Path.Fill> <Path.Effect> <DropShadowEffect BlurRadius="8" ShadowDepth="3.77953" Opacity="0.599998" Color="#FF000000" Direction="315"/> </Path.Effect> </Path> </Canvas> <Canvas x:Name="tempreature" Width="250" Height="280" Canvas.Left="0" Canvas.Top="0"> <Viewbox x:Name="Group" Width="58" Height="191" Canvas.Left="45" Canvas.Top="25"> <Canvas Width="58" Height="191"> <Path x:Name="Path_0" Width="20" Height="147" Canvas.Left="16.115" Canvas.Top="1.34552" Stretch="Fill" StrokeThickness="6" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Stroke="#FF96C240" Fill="#0096C240" Data="F1 M 26.115,4.34552L 26.115,4.34552C 29.981,4.34552 33.115,7.47954 33.115,11.3455L 33.115,138.345C 33.115,142.211 29.981,145.345 26.115,145.345L 26.115,145.345C 22.249,145.345 19.115,142.211 19.115,138.345L 19.115,11.3455C 19.115,7.47954 22.249,4.34552 26.115,4.34552 Z "> <Path.Effect> <DropShadowEffect BlurRadius="8" ShadowDepth="3.77953" Opacity="0.599998" Color="#FF000000" Direction="315"/> </Path.Effect> </Path> <Ellipse x:Name="Ellipse" Width="49.6471" Height="46.8889" Canvas.Left="1.29151" Canvas.Top="136.303" Stretch="Fill" Fill="#FF96C240"> <Ellipse.Effect> <DropShadowEffect BlurRadius="8" ShadowDepth="3.77953" Opacity="0.599998" Color="#FF000000" Direction="315"/> </Ellipse.Effect> </Ellipse> </Canvas> </Viewbox> </Canvas> </Canvas> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
我们只需要关注ControTemplate这一标记,内部其实就是实现我们自定义控件的外观。好,进入正题,今天之所以研究一天是因为对于它的动画故事版。
故事版我们可以在Blend中设计,然后移植到这里即可,但是需要注意的是:Resouce和Trigger都应该放到ControlTemplate下,而不是Style下。
有时某些动画的控制我需要在后台控制。那就是只需要获取到故事版,然后在相应的事件上让故事版播放即可。
那么如何获取放在Themes/Generic.xaml下的故事版呢?
查阅MSDN,要访问ControlTemplate下的资源,可以使用Template.Resources属性获得资源字典,得到资源字典后,那就是根据Key值获取到相应的资源了。这里需要注意,要获得资源字典
必须重写父类的OnApplyTemplate方法,从中不只能得到资源,也能得到ControlTemplate下的控件。
public override void OnApplyTemplate() { ResourceDictionary r = this.Template.Resources; sb = r["Alarm"] as Storyboard; ca = this.GetTemplateChild("background") as Canvas; base.OnApplyTemplate(); }
好,得到故事版了,那么在重写单击事件,然后让故事版播放,发现出错了
看我们故事版的代码
<Storyboard x:Key="Alarm"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="background"> <EasingDoubleKeyFrame KeyTime="0" Value="1"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/> </DoubleAnimationUsingKeyFrames> </Storyboard>
其中Storyboard.TargetName="background"这一段出错,至于为何报错,为何不能查找到,还请前辈们告之,这里我找到了解决办法。
参看http://social.msdn.microsoft.com/Forums/zh-TW/802/thread/1365449e-965d-4df7-82df-e3e0e8d0ca19
那么我们需要为故事版的Begin方法将Target对象传进去。background是Canvas对象,前面提到使用GetTemplateChild很容易获取到ControlTemplate下的控件。
获取到background后,带入Begin方法,功能实现。
public override void OnApplyTemplate() { ResourceDictionary r = this.Template.Resources; sb = r["Alarm"] as Storyboard; ca = this.GetTemplateChild("background") as Canvas; base.OnApplyTemplate(); } protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { sb.Begin(ca); base.OnMouseLeftButtonDown(e); }
这样,就通过后台控制到了动画的播放。