Silverlight 图像下载指示器使用 MVVM 模式






4.67/5 (9投票s)
用于在 Silverlight 应用程序中提供丰富外观的图像下载指示器
引言
Microsoft Silverlight 提供了一个 BitmapImage
类,该类支持一系列用于图像显示的特性,包括一些有用的事件,例如 DownloadProgress
、ImageFailed
和 ImageOpened
事件。这些事件可以有效地用于在动态从 Web 下载图像时通过显示下载进度来获得具有良好外观和感觉的用户界面。
也许,有些人可能已经对此场景有解决方案了。但我希望许多开发者不会以同样的方式思考。所以,我有一些基于我的经验可以分享的东西。我的方法涉及简单的 SpinAnimation
自定义控件实现。同时,我的解决方案可能无法提供专业级别的功能,但至少可以帮助探索实现方法 :。
本文探讨了几个概念;首先,它涉及创建一个带有动画的微型自定义 SpinAnimation
控件;其次,实现一个 ViewModel(遵循 MVVM 模式),其中包含向 View 提供进度指示所需的属性;最后,创建 View 以将 Model 属性绑定到 UI 设计中。
必备组件
- .NET Framework 3.5 或更高版本
- Windows XP, 2003, Vista, Windows7, VS2008
自定义 Silverlight 控件 – SpinAnimationControl
由于有很多关于如何在 Silverlight 中创建自定义控件的文章,我将直接跳到与根据下载进度值更改控件可见性相关的核心代码。我知道,你现在在想,为什么是 SpinAnimation
控件?:) 我们不能根据模型属性值更改 SpinAnimation
控件的可见性吗?是的,我们可以更改,但我想在动画元素的可见性更改时执行一些操作。例如,在动画元素可见性值更改时停止和启动动画。WPF 和 SL 默认都不提供 Visibility
属性更改的回调。但在 WPF 中,您可以通过属性元数据覆盖逻辑来实现。遗憾的是,Silverlight 没有像 WPF 那样覆盖属性元数据的功能。所以我们可以继续使用一个通用的解决方案,该方案可以同时在 SL 和 WPF 中工作。让我们从 SpinAnimation
控件的构造函数开始,它非常基础。
为自定义控件设置默认样式键
public class SpinAnimationControl : Control
{
public SpinAnimationControl ()
{
this.DefaultStyleKey = typeof(SpinAnimationControl);
}
}
创建一个名为 Progress
的依赖属性,该属性从应用程序端接收下载进度值。请注意,我在 PropertyMetadata
中提到了属性值更改回调 OnProgressChanged
。在此回调中,如果新值小于 100,我假设图像尚未下载。如果值为 100,则图像将毫无问题地下载。因此,我可以根据新值更改 SpinAnimation
控件的可见性。此外,在我更改可见性后,我还可以停止和启动动画。有关更多详细信息,请参阅下面的属性值更改回调方法。
public int Progress
{
get { return (int)GetValue(ProgressProperty); }
set { SetValue(ProgressProperty, value); }
}
public static readonly DependencyProperty ProgressProperty =
DependencyProperty.Register("Progress", typeof(int),
typeof(SpinAnimationControl), new PropertyMetadata(0, OnProgressChanged));
覆盖 OnApplyTemplate
方法以获取 root
元素资源部分中包含动画时间线的模板子项。您可以使用 GetTemplateChild
方法获取模板子项。Grid
是控件模板中的根元素。
Grid mainGrid = null;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
mainGrid = GetTemplateChild("MainGrid") as Grid;
}
属性值更改回调
private bool isAnimationStarted = false;
private static void OnProgressChanged(object sender,
DependencyPropertyChangedEventArgs args)
{
if (sender is SpinAnimationControl)
{
(sender as SpinAnimationControl).OnProgressChanged(args);
}
}
public void OnProgressChanged(DependencyPropertyChangedEventArgs args)
{
int newValue = (int)args.NewValue;
Storyboard storyBrd = null;
if (mainGrid != null)
{
// Storyboard from grid resource.
storyBrd = mainGrid.Resources["CircleAnimationStory"] as Storyboard;
}
if (newValue < 100)
{
this.Visibility = Visibility.Visible;
if (!isAnimationStarted)
{
if (storyBrd != null)
{
// Start the animation
storyBrd.Begin();
isAnimationStarted = true;
}
}
}
else
{
this.Visibility = Visibility.Collapsed;
if (isAnimationStarted)
{
if (storyBrd != null)
{
// Stop the animation.
storyBrd.Stop();
isAnimationStarted = false;
}
}
}
}
SpinAnimation 控件模板 – generic.xaml
这是 SpinAnimation
控件的模板。我放了一些带有 storyboard
的椭圆来为下载进度指示器设置动画。请参考 generic.xaml 中的完整模板代码。
<ControlTemplate TargetType="local:SpinAnimationControl">
<Grid x:Name="SpinGrid" Height="{TemplateBinding Height}"
VerticalAlignment="Center" HorizontalAlignment="Center"
Width="{TemplateBinding Width}">
<Grid.Resources>
<Storyboard x:Name="RetrievalCircleAnimation" RepeatBehavior="Forever" SpeedRatio="4">
<!-- Animation for ellipse -->
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ellipse"
Storyboard.TargetProperty=
"(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
<EasingDoubleKeyFrame KeyTime="00:00:01.5000000" Value="0.5"/>
<EasingDoubleKeyFrame KeyTime="00:00:03.5000000" Value="0.5"/>
<EasingDoubleKeyFrame KeyTime="00:00:04" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<!- More codes -->
</Grid.Resources>
<Ellipse x:Name="ellipse" Height="12" Width="12" HorizontalAlignment="Center"
VerticalAlignment="Top" RenderTransformOrigin="0.5,0.5"
Fill="{StaticResource ballbrush}">
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.5" ScaleY="0.5"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
<!- More codes -->
</Grid>
</ControlTemplate>
SpinAnimation 控件快照

创建 ViewModel 和 View 以显示带有下载指示器的图像
如果您是 SL 或 WPF 开发者,我相信您只倾向于 MVVM 方法,尽管如此,我一直偏爱 MVVM,因为它至今仍然是 SL 或 WPF 应用程序唯一强大的模式。这就是我决定从 ViewModel 实现图像 Uri 和进度通知的原因。
ImageAppViewModel 类
ImageAppViewModel
类有一个名为 ImageList
的属性,类型为 ObservableCollection
。这是我们将在 View
中的 Listbox
控件上绑定为 ItemsSource
的属性。
public ImageAppViewModel()
{
//Took some random images link from web for demonstration purpose only.
ImagesList = new ObservableCollection();
ImagesList.Add(new ImageExt() {
Uri = "http://www.pdssb.com.my/images/stories/gallery/car.jpg" });
ImagesList.Add(new ImageExt() {
Uri = "http://joshua.maruskadesign.com/blog/uploaded_images/Car-02-723748.png" });
ImagesList.Add(new ImageExt() { Uri = "http://www.carbodydesign.com/archive/2009/02/
13-automotive-designers-show-program/Autodesk-Concept-Car-rendering-lg.jpg" });
ImagesList.Add(new ImageExt() {
Uri = "http://www2.hiren.info/desktopwallpapers/other/car-silver-87c.jpg" });
ImagesList.Add(new ImageExt() {
Uri = "http://brianhansford.com/wp-content/uploads/2010/02/red-sports-car.jpg" });
}
private ObservableCollection<ImageExt> m_ImagesList;
public ObservableCollection<ImageExt> ImagesList
{
get
{
return m_ImagesList;
}
set
{
m_ImagesList = value;
OnPropertyChanged("ImagesList");
}
}
ImageExt 类
ImageExt
类具有 Uri
、ImageSource (BitmapImage 类型)
和 Progress
属性。为什么 ViewModel 中有两个图像源属性?通常,我们将 Image uri
作为 string
类型发送。请参见下面的示例。
Uri= “http://brianhansford.com/wp-content/uploads/2010/02/red-sports-car.jpg"
但我们需要使用此 uri
构造 BitmapImage
对象,并在属性 getter 中注册 DownloadProgress
事件。在下载进度事件处理程序中,更新 ImageExt
类中的 Progress
属性,该属性用于指示特定图像已下载的字节百分比。现在,仅在 View
中 ListBox
的 ItemTemplate
中绑定 ImageSource
属性。
public class ImageExt : INotifyPropertyChanged
{
private int m_Progress;
public int Progress
{
get
{
return m_Progress;
}
set
{
m_Progress = value;
OnPropertyChanged("Progress");
}
}
private string m_Uri;
public string Uri
{
get
{
return m_Uri;
}
set
{
m_Uri = value;
OnPropertyChanged("Uri");
}
}
BitmapImage image = null;
public BitmapImage ImageSource
{
get
{
image = new BitmapImage(new Uri(Uri, UriKind.Absolute));
image.DownloadProgress +=
new EventHandler<DownloadProgressEventArgs>(image_DownloadProgress);
return image;
}
}
}
DownloadProgress 事件处理程序
根据 DownloadProgressEventArgs
值更新 Progress
属性值,并在下载完成后取消注册事件。
void image_DownloadProgress(object sender, DownloadProgressEventArgs e)
{
Progress = e.Progress;
if (e.Progress == 100)
{
image.DownloadProgress -=
new EventHandler<DownloadProgressEventArgs>(image_DownloadProgress);
}
}
ImageAppView 和 ImageAppView.xaml
现在,创建 ImageAppView.xaml 并放置简单的列表框控件,并进行 ViewModel
属性绑定。请参阅下面的代码片段,ListBox ItemsSource
属性绑定到我们在 ViewModel
中声明的 ImageList
属性。
<ListBox ItemsSource="{Binding ImageList}"
Height="110" BorderBrush="Blue" BorderThickness="2">
</ListBox>
将 ItemsPanelTemplate
设置为 StackPanel
以水平显示图像。
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
设置 ItemTemplate
以托管我们的 SpinAnimation
和 Image
控件,以便在下载完成后显示实际图像。我还放了一些文本框来显示下载百分比详细信息。
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Image Name="icon" Width="100" Height="100"
Source ="{Binding ImageSource}" Stretch="Uniform"
VerticalAlignment="Center" HorizontalAlignment="Center" />
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
HorizontalAlignment="Center" Visibility="{Binding Path=Visibility,
ElementName=spinAnimationControl}">
<TextBlock FontFamily="Cambria" FontSize="10" Text="{Binding Progress}"
FontWeight="Bold" Foreground="Black" TextAlignment="Center"
TextWrapping="Wrap" LineStackingStrategy="BlockLineHeight"
LineHeight="9" VerticalAlignment="Center" HorizontalAlignment="Stretch"
Margin="1"/>
<TextBlock Text="%" FontFamily="Cambria" FontSize="10"
FontWeight="Bold" Foreground="Black" TextAlignment="Center"
TextWrapping="Wrap" LineStackingStrategy="BlockLineHeight"
LineHeight="9" VerticalAlignment="Center" HorizontalAlignment="Stretch" Margin="1"/>
</StackPanel>
<control:SpinAnimationControl Name="spinAnimationControl"
Width="50" Height="50" VerticalAlignment="Center"
HorizontalAlignment="Stretch" Progress="{Binding Progress}" Margin="1"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
最后,在 View 的 DataContext
中分配 ViewModel
。
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
this.DataContext = new ImageAppViewModel();
}
}
演示应用程序快照
历史
- 2010年8月5日:初始发布