一、背景介绍
在上一篇《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属性,因为我们要把依赖属性挪挪地方了。
View Code
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"//>
至此,改进完成,改进后的装饰件已具备松耦合,通用性的能力。
其余内容详见源代码。