65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (9投票s)

2010年8月5日

CPOL

4分钟阅读

viewsIcon

38732

downloadIcon

459

用于在 Silverlight 应用程序中提供丰富外观的图像下载指示器

引言

Microsoft Silverlight 提供了一个 BitmapImage 类,该类支持一系列用于图像显示的特性,包括一些有用的事件,例如 DownloadProgressImageFailed 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 类具有 UriImageSource (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日:初始发布
© . All rights reserved.