Silverlight 3 的可定制加载指示器






4.94/5 (15投票s)
一篇关于为 Silverlight 3 创建可定制加载指示器的文章

引言
我一年前写了这篇文章的第一个版本。我的 Silverlight 应用程序需要一个简单的加载指示器,类似于旋转的轮子。我找到了一些控件,但是...有些太复杂了,有些很难定制。最后,我创建了自己的控件并在 CodeProject 上发表了一篇文章。收到的反馈迫使我回到代码并使其更好更简单。
这是第二次尝试。点击链接在我的网站上查看工作的 加载指示器演示。
第二个版本中做了哪些更改
- 为 Silverlight 4 重新打包
- 删除了一些无用的属性
- 简化了控件模板
- 优化了动画
背景
关于我计划创建的内容的一些说明。首先,它应该是一个简单的、可定制的控件。唯一的限制 - 我不想硬编码它的几何形状,即动画元素的形状。此元素在 Generic.xaml 文件中定义,但可以作为用户 Style
的一部分重新定义。
如果您查看 Generic.xaml,您将只看到一个动画元素 AnimationElementTemplate
被定义为 DataTemplate
。问题是如何复制它。我很惊讶地发现,在 Silverlight 中进行深度对象克隆是一项具有挑战性的任务。如果在 WPF 中需要三行代码(这是指向 示例 的链接),那么对于 Silverlight 来说,没有简单的解决方案。
起初,我从 Thilo Ruppert 的一篇精彩文章 Professional Drag and Drop Manager Control for Silverlight 2 中借用了一段克隆对象的代码。但后来,我遵循了 Nicolas Dorier's 使用 DataTemplate
作为动画元素的建议,并根据需要多次加载它。感谢 Nicolas 的评论。
所以,现在我们有了一个动画元素,并且我们知道如何制作它的副本。让我们看看它是如何工作的。
工作原理
下载我的演示解决方案并在 Visual Studio 中打开它。这是它的结构

SilverFlow.Controls
项目包含以下文件
LoadingIndicator.cs | 定义控件的属性和行为 |
Generic.xaml | 定义控件的默认模板 |
首先,让我们先看看 Generic.xaml
<Style TargetType="controls:LoadingIndicator">
<Setter Property="Width" Value="42" />
<Setter Property="Height" Value="42" />
<Setter Property="Count" Value="12" />
<Setter Property="Duration" Value="0:0:1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:LoadingIndicator">
<Canvas x:Name="PART_AnimationElementContainer"
Background="{TemplateBinding Background}" />
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="AnimationElementTemplate">
<Setter.Value>
<DataTemplate>
<Rectangle Fill="#00C0FF" Height="10"
Width="6" RadiusY="2" RadiusX="3" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
它定义了控件的两个最重要的部分:PART_AnimationElementContainer
和 AnimationElementTemplate
。PART_AnimationElementContainer
用作容器。动画元素将被复制 Count
次并放置到容器的内切圆中。它执行以下代码
for (int i = 0; i < this.Count; i++)
{
// Copy base element
FrameworkElement element = (FrameworkElement)AnimationElementTemplate.LoadContent();
element.Opacity = 0;
TranslateTransform tt = new TranslateTransform() { X = x, Y = y };
RotateTransform rt = new RotateTransform()
{ Angle = i * angle + 180, CenterX = (width / 2), CenterY = -innerRadius };
TransformGroup tg = new TransformGroup();
tg.Children.Add(rt);
tg.Children.Add(tt);
element.RenderTransform = tg;
animationElementContainer.Children.Add(element);
DoubleAnimation animation = new DoubleAnimation()
{
From = animationElement.Opacity,
To = EndOpacity,
Duration = this.Duration,
RepeatBehavior = RepeatBehavior.Forever,
BeginTime = TimeSpan.FromMilliseconds
((this.Duration.TotalMilliseconds / this.Count) * i)
};
Storyboard.SetTargetProperty(animation, new PropertyPath("Opacity"));
Storyboard.SetTarget(animation, element);
Storyboard sb = new Storyboard();
sb.Children.Add(animation);
sb.Begin();
storyboardList.Add(sb);
}
一个小技巧
感谢 Aaginor - 他指出了一个小问题。如果您将指示器的可见性更改为 Collapsed
,动画仍然会运行,消耗资源。第一个想法是捕获类似 OnVisibilityChanged
的事件,但您很难找到这样的事件。好吧,有一个 LayoutUpdated
事件。并且它肯定会在控件更改其可见性时发生。但不仅在这种情况下。
这就是为什么我决定使用一个小技巧 - 创建一个 private ControlVisibility
属性并将它绑定到控件的 Visibility
属性
Binding binding = new Binding()
{
Source = this,
Path = new PropertyPath("Visibility")
};
this.SetBinding(LoadingIndicator.ControlVisibilityProperty, binding);
这使我可以在 OnControlVisibilityPropertyChanged
方法中拦截可见性的更改。当指示器变为不可见时,这里是停止动画的好地方。
当您运行演示时,您将看到一个“显示/隐藏指示器”按钮。它切换指示器的可见性。我添加它是为了检查我的代码是否真的可以处理不存在的 OnVisibilityChanged
事件。它可以!
Using the Code
加载指示器控件有两个 public
属性
Count |
动画元素的数量。默认值为 12。 |
持续时间 |
一个动画循环持续时间。默认值为一秒。 |
查看 Styles.xaml,其中包含演示中显示的加载指示器的三个模板。第一个控件使用 Generic.xaml 中定义的内置样式。其他三个控件使用他们自己的样式。
历史
- 2009 年 11 月 24 日:首次发布
- 2009 年 12 月 8 日:添加了“一个小技巧”部分
- 2010 年 11 月 28 日:简化了控件模板