- 前言
Windows Phone为开发者提供了很多原生控件,但在很多场景下我们需要对默认的功能或样式做一定的修改才能满足我们的需求,自定义控件应运而生。本文通过以自定义控件进度环(ProgressRing)为例,向大家介绍Windows Phone中如何创建和使用自定义控件。
1、控件基类
通常自定义控件继承自Control、ItemsControl、ContentControl等。
Control:代表使用ControlTemplate来定义样式的UI控件的基类。
System.Object
System.Windows.DependencyObject
System.Windows.UIElement
System.Windows.FrameworkElement
System.Windows.Controls.Control
ItemsControl:代表一个可以用于表现一个集合对象的控件。
System.Object
System.Windows.DependencyObject
System.Windows.UIElement
System.Windows.FrameworkElement
System.Windows.Controls.Control
System.Windows.Controls.ItemsControl
ContentControl:代表一个具有单独块级内容元素的控件。比如像Button,CheckBox,ScrollViewer都直接或间接的继承于它。
System.Object
System.Windows.DependencyObject
System.Windows.UIElement
System.Windows.FrameworkElement
System.Windows.Controls.Control
System.Windows.Controls.ContentControl
2、创建自定义控件
下面我们就来创建一个继承自Control的用户控件ProgressRing的类。
namespace WindowsPhone.Controls { public class ProgressRing : Control { public ProgressRing() { DefaultStyleKey = typeof(ProgressRing); } public override void OnApplyTemplate() { base.OnApplyTemplate(); } public bool IsActive { get { return (bool)GetValue(IsActiveProperty); } set { SetValue(IsActiveProperty, value); } } public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register("IsActive", typeof(bool), typeof(ProgressRing), new PropertyMetadata(false, new PropertyChangedCallback(IsActiveChanged))); private static void IsActiveChanged(DependencyObject d, DependencyPropertyChangedEventArgs args) { var pr = (ProgressRing)d; var isActive = (bool)args.NewValue; } } }
通过DependencyProperty IsActiveProperty来代表进度环的状态,DependencyProperty和普通的属性的区别为,DependencyProperty属性可以为值表达式、数据绑定、动画和属性更改通知提供支持。举个例子,如果你声明了一个Style,你可以通过 <Setter Property="Background" Value="Red"/> 的形式来设置背景色,因为Background是DependencyProperty属性,但是你不能对一般的属性进行同样的操作,因为他们不是DependencyProperty属性。
DefaultStyleKey代表默认样式,若要为继承自 Control 的控件提供默认的 Style,请将 DefaultStyleKey 属性设置为相同类型的 TargetType 属性。 如果您没有设置 DefaultStyleKey,则将使用基类的默认样式。 例如,如果称为 NewButton 的控件继承自 Button,要使用新的默认 Style,请将 DefaultStyleKey 设置为类型 NewButton。 如果您没有设置 DefaultStyleKey,则会将 Style 用于 Button。
在一些复杂的场景中,比如你想要获取ControlTemplate中某个对象的实例,就有必要override OnApplyTemplate方法。这个方法在控件展示在屏幕前被调用,在这种场景下OnApplyTemplate比Loaded事件更适合来调整由template创建的visual tree,因为Loaded事件可能会在页面应用模板之前被调用,所以可能无法获取到ControlTemplate中的对象的实例,也就可能无法完成之前调整模板的功能。
3、创建默认模板
接下来就该创建自定义控件的默认样式。
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}" /> <Setter Property="IsHitTestVisible" Value="False" /> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="MinHeight" Value="20" /> <Setter Property="MinWidth" Value="20" /> <Setter Property="IsTabStop" Value="False" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="controls:ProgressRing"> <Border x:Name="ProgressRingRoot" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}"> <Border.Resources> <Style x:Key="ProgressRingEllipseStyle" TargetType="Ellipse"> <Setter Property="Opacity" Value="0" /> <Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="VerticalAlignment" Value="Top" /> </Style> </Border.Resources> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="SizeStates"> <VisualState x:Name="Large"> <Storyboard> <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="SixthCircle" Storyboard.TargetProperty="Visibility"> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Visible</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Small" /> </VisualStateGroup> <VisualStateGroup x:Name="ActiveStates"> <VisualState x:Name="Inactive" /> <VisualState x:Name="Active"> <Storyboard RepeatBehavior="Forever"> <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="Ring" Storyboard.TargetProperty="Visibility"> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Visible</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E1" Storyboard.TargetProperty="Opacity" BeginTime="0"> <DiscreteDoubleKeyFrame KeyTime="0" Value="1" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" /> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E2" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.167"> <DiscreteDoubleKeyFrame KeyTime="0" Value="1" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" /> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E3" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.334"> <DiscreteDoubleKeyFrame KeyTime="0" Value="1" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" /> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E4" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.501"> <DiscreteDoubleKeyFrame KeyTime="0" Value="1" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" /> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E5" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.668"> <DiscreteDoubleKeyFrame KeyTime="0" Value="1" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" /> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E6" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.835"> <DiscreteDoubleKeyFrame KeyTime="0" Value="1" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" /> <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" /> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E1R" BeginTime="0" Storyboard.TargetProperty="Angle"> <SplineDoubleKeyFrame KeyTime="0" Value="-110" KeySpline="0.13,0.21,0.1,0.7"/> <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="10" KeySpline="0.02,0.33,0.38,0.77"/> <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="93"/> <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="205" KeySpline="0.57,0.17,0.95,0.75"/> <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="357" KeySpline="0,0.19,0.07,0.72"/> <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="439"/> <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="585" KeySpline="0,0,0.95,0.37"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E2R" BeginTime="00:00:00.167" Storyboard.TargetProperty="Angle"> <SplineDoubleKeyFrame KeyTime="0" Value="-116" KeySpline="0.13,0.21,0.1,0.7"/> <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="4" KeySpline="0.02,0.33,0.38,0.77"/> <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="87"/> <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="199" KeySpline="0.57,0.17,0.95,0.75"/> <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="351" KeySpline="0,0.19,0.07,0.72"/> <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="433"/> <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="579" KeySpline="0,0,0.95,0.37"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E3R" BeginTime="00:00:00.334" Storyboard.TargetProperty="Angle"> <SplineDoubleKeyFrame KeyTime="0" Value="-122" KeySpline="0.13,0.21,0.1,0.7"/> <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-2" KeySpline="0.02,0.33,0.38,0.77"/> <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="81"/> <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="193" KeySpline="0.57,0.17,0.95,0.75"/> <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="345" KeySpline="0,0.19,0.07,0.72"/> <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="427"/> <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="573" KeySpline="0,0,0.95,0.37"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E4R" BeginTime="00:00:00.501" Storyboard.TargetProperty="Angle"> <SplineDoubleKeyFrame KeyTime="0" Value="-128" KeySpline="0.13,0.21,0.1,0.7"/> <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-8" KeySpline="0.02,0.33,0.38,0.77"/> <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="75"/> <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="187" KeySpline="0.57,0.17,0.95,0.75"/> <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="339" KeySpline="0,0.19,0.07,0.72"/> <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="421"/> <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="567" KeySpline="0,0,0.95,0.37"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E5R" BeginTime="00:00:00.668" Storyboard.TargetProperty="Angle"> <SplineDoubleKeyFrame KeyTime="0" Value="-134" KeySpline="0.13,0.21,0.1,0.7"/> <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-14" KeySpline="0.02,0.33,0.38,0.77"/> <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="69"/> <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="181" KeySpline="0.57,0.17,0.95,0.75"/> <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="331" KeySpline="0,0.19,0.07,0.72"/> <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="415"/> <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="561" KeySpline="0,0,0.95,0.37"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E6R" BeginTime="00:00:00.835" Storyboard.TargetProperty="Angle"> <SplineDoubleKeyFrame KeyTime="0" Value="-140" KeySpline="0.13,0.21,0.1,0.7"/> <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-20" KeySpline="0.02,0.33,0.38,0.77"/> <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="63"/> <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="175" KeySpline="0.57,0.17,0.95,0.75"/> <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="325" KeySpline="0,0.19,0.07,0.72"/> <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="409"/> <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="555" KeySpline="0,0,0.95,0.37"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Grid x:Name="Ring" Margin="{TemplateBinding Padding}" MaxWidth="{Binding TemplateSettings.MaxSideLength, RelativeSource={RelativeSource TemplatedParent}}" MaxHeight="{Binding TemplateSettings.MaxSideLength, RelativeSource={RelativeSource TemplatedParent}}" Visibility="Collapsed" RenderTransformOrigin=".5,.5" FlowDirection="LeftToRight"> <Canvas RenderTransformOrigin=".5,.5"> <Canvas.RenderTransform> <RotateTransform x:Name="E1R" /> </Canvas.RenderTransform> <Ellipse x:Name="E1" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding TemplateSettings.EllipseDiameter, RelativeSource={RelativeSource TemplatedParent}}" Height="{Binding TemplateSettings.EllipseDiameter, RelativeSource={RelativeSource TemplatedParent}}" Margin="{Binding TemplateSettings.EllipseOffset, RelativeSource={RelativeSource TemplatedParent}}" Fill="{TemplateBinding Foreground}"/> </Canvas> <Canvas RenderTransformOrigin=".5,.5"> <Canvas.RenderTransform> <RotateTransform x:Name="E2R" /> </Canvas.RenderTransform> <Ellipse x:Name="E2" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding TemplateSettings.EllipseDiameter, RelativeSource={RelativeSource TemplatedParent}}" Height="{Binding TemplateSettings.EllipseDiameter, RelativeSource={RelativeSource TemplatedParent}}" Margin="{Binding TemplateSettings.EllipseOffset, RelativeSource={RelativeSource TemplatedParent}}" Fill="{TemplateBinding Foreground}"/> </Canvas> <Canvas RenderTransformOrigin=".5,.5"> <Canvas.RenderTransform> <RotateTransform x:Name="E3R" /> </Canvas.RenderTransform> <Ellipse x:Name="E3" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding TemplateSettings.EllipseDiameter, RelativeSource={RelativeSource TemplatedParent}}" Height="{Binding TemplateSettings.EllipseDiameter, RelativeSource={RelativeSource TemplatedParent}}" Margin="{Binding TemplateSettings.EllipseOffset, RelativeSource={RelativeSource TemplatedParent}}" Fill="{TemplateBinding Foreground}"/> </Canvas> <Canvas RenderTransformOrigin=".5,.5"> <Canvas.RenderTransform> <RotateTransform x:Name="E4R" /> </Canvas.RenderTransform> <Ellipse x:Name="E4" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding TemplateSettings.EllipseDiameter, RelativeSource={RelativeSource TemplatedParent}}" Height="{Binding TemplateSettings.EllipseDiameter, RelativeSource={RelativeSource TemplatedParent}}" Margin="{Binding TemplateSettings.EllipseOffset, RelativeSource={RelativeSource TemplatedParent}}" Fill="{TemplateBinding Foreground}"/> </Canvas> <Canvas RenderTransformOrigin=".5,.5"> <Canvas.RenderTransform> <RotateTransform x:Name="E5R" /> </Canvas.RenderTransform> <Ellipse x:Name="E5" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding TemplateSettings.EllipseDiameter, RelativeSource={RelativeSource TemplatedParent}}" Height="{Binding TemplateSettings.EllipseDiameter, RelativeSource={RelativeSource TemplatedParent}}" Margin="{Binding TemplateSettings.EllipseOffset, RelativeSource={RelativeSource TemplatedParent}}" Fill="{TemplateBinding Foreground}"/> </Canvas> <Canvas RenderTransformOrigin=".5,.5" Visibility="Collapsed" x:Name="SixthCircle"> <Canvas.RenderTransform> <RotateTransform x:Name="E6R" /> </Canvas.RenderTransform> <Ellipse x:Name="E6" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding TemplateSettings.EllipseDiameter, RelativeSource={RelativeSource TemplatedParent}}" Height="{Binding TemplateSettings.EllipseDiameter, RelativeSource={RelativeSource TemplatedParent}}" Margin="{Binding TemplateSettings.EllipseOffset, RelativeSource={RelativeSource TemplatedParent}}" Fill="{TemplateBinding Foreground}"/> </Canvas> </Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
这个步骤定义了我们自定义控件的样子,注意,ControlTemplate是非常重要的属性,它是多种UI Element的组合,决定着我们控件的visual tree的结构和状态。
TemplateBinding用来连接模板中的属性的值和自定义控件中属性的值。TemplateBinding仅支持由模板产生的FrameworkElements,它的数据源引用会指向模板中的父级元素。TemplateBinding最主要的用途是内置在模板中绑定模板化元素的属性。
4、使用自定义控件
首先在页面引入自定义控件的命名空间。
xmlns:controls="clr-namespace:WindowsPhone.Controls"
再在页面添加自定义控件即可。
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <controls:ProgressRing Width="70" IsActive="True" Height="70"/> </Grid>