一、背景介绍
在上一篇《c#,使用WPF的Adorner实现iPhone上新邮件或消息提示效果----实现(一)》中,我们通过PromptableButton,PromptAdorner,PromptChrome实现提示效果,其中PromptableButton提供PromptCount代表提示数量供PromptChrome绑定显示,PromptChrome负责显示提示效果,而PromptAdorner负责呈现PromptChrome。
但是,这里有个问题,为了要使用PromptAdorner这个装饰件,难道所有的控件都要像PromptableButton控件一样提供PromptCount供装饰件绑定? 如果这样做,如果样装饰一个图片控件,那我们又要继承重写一个?如果直接要在常用控件上直接使用装饰件不就行不通了? 如果不能解决这3个问题,那么耦合度还是太高,通用性也不强。
基于上面的问题,我们采用依赖属性中另外一个利器--“附加属性”,比如:我们要在Canvas内放置一个按钮并且指定其位置时我们都会写<Button Canvas.Left="100" Canvas.Top="100"/>,这里的Canvas.Left就是附加属性的实际使用。以此参考方法,我们也通过编写附件属性来改进目前的问题。
二、改进分解说明
1、改进PromptButton。
该自定义按钮内不再拥有PromptCount属性,因为我们要把依赖属性挪挪地方了。
internal class PromptableButton : Button { public ImageSource CoverImageSource { get { return (ImageSource)GetValue(CoverImageSourceProperty); } set { SetValue(CoverImageSourceProperty, value); } } public static readonly DependencyProperty CoverImageSourceProperty = DependencyProperty.Register("CoverImageSource", typeof(ImageSource), typeof(PromptableButton), new UIPropertyMetadata(null)); public PromptableButton() { } static PromptableButton() { DefaultStyleKeyProperty.OverrideMetadata(typeof(PromptableButton), new FrameworkPropertyMetadata(typeof(PromptableButton))); } }
2、改进PromptAdorner。
我们需要两个依赖属性:IsPromptEnabled,PromptCount。
IsPromptEnabled用来控制装饰件是否可用,PromptCount从PromptButton处挪过来。
这两个依赖属性的定义时,使用DependencyProperty.RegisterAttached方法注册,说明这是附加属性,并且采用两个静态方法(Get/Set)作为附加属性的访问器。
public static readonly DependencyProperty PromptCountProperty = DependencyProperty.RegisterAttached("PromptCount", typeof(int), typeof(PromptAdorner), new FrameworkPropertyMetadata(0, new PropertyChangedCallback(PromptCountChangedCallBack), new CoerceValueCallback(CoercePromptCountCallback))); public static int GetPromptCount(DependencyObject obj) { return (int)obj.GetValue(PromptCountProperty); } public static void SetPromptCount(DependencyObject obj, int value) { obj.SetValue(PromptCountProperty, value); } public static readonly DependencyProperty IsPromptEnabledProperty = DependencyProperty.RegisterAttached("IsPromptEnabled", typeof(bool), typeof(PromptAdorner), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(IsPromptEnabledChangedCallBack), null)); public static bool GetIsPromptEnabled(DependencyObject obj) { return (bool)obj.GetValue(IsPromptEnabledProperty); } public static void SetIsPromptEnabled(DependencyObject obj, bool value) { obj.SetValue(IsPromptEnabledProperty, value); }
接下来我们在IsPromptEnabledChangedCallBack方法中实现装饰件的创建和移除,该方法会在IsPromptEnabled属性变化时调用:
public static void IsPromptEnabledChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e) { var source = d as FrameworkElement; bool isEnabled = (bool)e.NewValue; if (isEnabled) { //装饰件可用,添加装饰件 AdornerLayer layer = AdornerLayer.GetAdornerLayer(source); if (layer != null) { //能够获取装饰层,说明已经load过了,直接生成装饰件 var adorner = new PromptAdorner(source); layer.Add(adorner); } else { //layer为null,说明还未load过(整个可视化树中没有装饰层的情况不考虑) //在控件的loaded事件内生成装饰件 source.Loaded += (s1, e1) => { var adorner = new PromptAdorner(source); AdornerLayer.GetAdornerLayer(source).Add(adorner); }; } } else { //装饰件不可用,移除装饰件 AdornerLayer layer = AdornerLayer.GetAdornerLayer(source); if (layer != null) { Adorner[] AllAdorners = layer.GetAdorners(source); if (AllAdorners != null) { IEnumerable<Adorner> desAdorners = AllAdorners.Where(p => p is PromptAdorner); if (desAdorners != null && desAdorners.Count() > 0) { desAdorners.ToList().ForEach(p => layer.Remove(p)); } } } } }
3、PromtpChrome的改进。
主要修改该自定义控件描述皮肤的XAML中,对PromptCount的绑定写法。
这里的绑定使用了附加属性绑定的写法,列如:
<Label Content="{Binding Path=(local:PromptAdorner.PromptCount)}" .... />
<Style TargetType="{x:Type local:PromptChrome}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:PromptChrome}"> <Grid x:Name="container"> <!--最外圈的白色圆框,并对其作阴影效果--> <Ellipse Fill="White"> <Ellipse.Effect> <DropShadowEffect BlurRadius="6" ShadowDepth="6" Opacity="0.8" Direction="270" RenderingBias="Performance"/> </Ellipse.Effect> </Ellipse> <!--内部的上半圆--> <Ellipse Margin="3"> <Ellipse.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Offset="0" Color="#FFF4AEB1"/> <GradientStop Offset="0.5" Color="#FFE3313A"/> <GradientStop Offset="1" Color="#FFE3313A"/> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> <!--内部的下半圆,通过采用Exclude模式合并上下两个圆来完成--> <Path HorizontalAlignment="Center" VerticalAlignment="Center"> <Path.Data> <CombinedGeometry GeometryCombineMode="Exclude" > <CombinedGeometry.Geometry1> <EllipseGeometry Center="14 14" RadiusX="14" RadiusY="14" /> </CombinedGeometry.Geometry1> <CombinedGeometry.Geometry2> <EllipseGeometry Center="14 0" RadiusX="18" RadiusY="14"/> </CombinedGeometry.Geometry2> </CombinedGeometry> </Path.Data> <Path.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Offset="0" Color="#FFDF151F"/> <GradientStop Offset="1" Color="#FFBA0004"/> </LinearGradientBrush> </Path.Fill> </Path> <Viewbox Stretch="Uniform" > <!--绑定上文中的PromptCount属性--> <Label Content="{Binding Path=(local:PromptAdorner.PromptCount)}" x:Name="label" Foreground="White" FontWeight="Bold" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Viewbox> </Grid> <ControlTemplate.Triggers> <!--使用数据触发器,当PromptCount为0时,隐藏提示--> <DataTrigger Binding="{Binding Path=(local:PromptAdorner.PromptCount)}" Value="0"> <Setter TargetName="container" Property="Visibility" Value="Hidden"/> </DataTrigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
三、使用
在原先PromptableButton的XAML代码使用处,我们增加了两个依赖属性用来控制和显示装饰件。
<local:PromptableButton x:Name="button1" Grid.Row="1" CoverImageSource="001.png" Width="128" Height="128" local:PromptAdorner.IsPromptEnabled="True" local:PromptAdorner.PromptCount="0"/>
我们也可以直接在一个常用控件上使用带提示效果的装饰件。
<Button x:Name="button2" Grid.Row="4" Width="150" Height="60" Content="按钮2"
local:PromptAdorner.IsPromptEnabled="True"
local:PromptAdorner.PromptCount="1"//>
至此,改进完成,改进后的装饰件已具备松耦合,通用性的能力。
其余内容详见源代码。
四、代码分享
http://files.cnblogs.com/kongxianghai/Test008.rar