WPF异步载入图片,附带载入中动画

WPF异步载入图片,附带载入中动画

Video_2012-09-16_170249最近,在做一个WPF项目。项目中有一个需求,就是以列表的方式显示出项目图片。这些图片有的存在于互联网上,有的存在于本地磁盘。存在本地磁盘的文件好说,主要是存在于网络的图片。因为存在于网络的图片,在载入时需要耗费时间,如果直接给Image控件绑定URI属性的话,会造成界面卡顿。为了提供更好的体验,要求有类似网页中图片载入中的特效。

经过两天的研究,我翻看了爱壁纸HD For Windows的源代码(你懂得)。终于完成了这个功能。实现的效果如右图所示:

显示图片列表的,肯定是一个ListBox。通过自定义ListBox的ItemsPanel和ItemTemplate,可以实现ListBox的子项横排,以及设置子项为图片。

在做WPF项目时,我们通常是通过绑定为控件的属性赋值,所以我们要先构造一个数据源并且做一个ViewModel。

数据源,就设定为一个简单的文本文件(list.txt)。每行,一个图片地址。

示例代码如下:

 
[html]  view plain  copy
  1. http://img11.360buyimg.com//n3/g2/M00/06/1D/rBEGEVAkffUIAAAAAAB54F55qh8AABWrQLxLr0AAHn4106.jpg  
  2. C:\Users\Soar\Pictures\lovewallpaper\18451,106.jpg  
  3. http://img12.360buyimg.com//n3/g1/M00/06/1D/rBEGDVAkffQIAAAAAAB0mDavAccAABWrQMCUdwAAHSw197.jpg  
  4. C:\Users\Soar\Pictures\lovewallpaper\367448,106.jpg  
  5. http://img13.360buyimg.com//n3/g2/M00/06/1D/rBEGElAkffIIAAAAAADVR1yd_X0AABWrQKlu2MAANVf537.jpg  
  6. C:\Users\Soar\Pictures\lovewallpaper\359090,106.jpg  
  7. http://img10.360buyimg.com//n3/g5/M02/1C/00/rBEIC1Akfe8IAAAAAABDtsBt3bQAAFeCQAh13kAAEPO445.jpg  
  8. http://img11.360buyimg.com//n3/g3/M00/06/1D/rBEGE1AkfgIIAAAAAACfm_MhwRYAABWrQMmK8kAAJ-z240.jpg  
  9. http://img12.360buyimg.com//n3/g3/M00/06/1D/rBEGFFAkfhQIAAAAAABHekJE6jQAABWrQOGiEUAAEeS965.jpg  
  10. http://img13.360buyimg.com//n3/g2/M00/06/1D/rBEGElAkfegIAAAAAAClvhjSNQoAABWrQJ0KTIAAKXW818.jpg  
  11. http://img14.360buyimg.com//n3/g1/M00/06/1D/rBEGDlAkfe4IAAAAAABQsM9eGEoAABWrQJ4WIwAAFDI883.jpg  
  12. http://img10.360buyimg.com//n3/g3/M00/06/1D/rBEGE1AkfgQIAAAAAACBZc_HeVAAABWrQM293sAAIF9407.jpg  
  13. http://img11.360buyimg.com//n3/g3/M00/06/1D/rBEGE1AkfgkIAAAAAAC_6A3AnhwAABWrQOfht8AAMAA406.jpg  
  14. http://img12.360buyimg.com//n3/g5/M02/1C/00/rBEDilAkfeAIAAAAAACdJBYljH0AAFeCQAuIsMAAJ08326.jpg  
  15. http://img13.360buyimg.com//n3/g1/M00/06/1D/rBEGDVAkfe4IAAAAAACXzwGDqfoAABWrQKpCmEAAJfn685.jpg  
  16. http://img12.360buyimg.com//n3/g3/M00/06/1D/rBEGE1AkfgcIAAAAAAC5nK25hEQAABWrQOCa3sAALm0258.jpg  
  17. http://img14.360buyimg.com//n3/g2/M00/06/1D/rBEGEFAkfdUIAAAAAACZblNaX_kAABWrQJ0zwgAAJmG566.jpg  
  18. http://img14.360buyimg.com//n3/g2/M00/06/1D/rBEGEFAkfewIAAAAAACfqQVJlNoAABWrQOirGwAAJ_B820.jpg  
  19. http://img11.360buyimg.com//n3/g2/M01/06/1D/rBEGEFAkffMIAAAAAACgY4EpzwYAABWrgAfHyIAAKB7880.jpg  

下面是ViewModel的代码(MainViewModel.cs):
[csharp]  view plain  copy
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.IO;  
  6.   
  7. namespace WebImageList  
  8. {  
  9.     public class MainViewModel  
  10.     {  
  11.         public MainViewModel()  
  12.         {  
  13.             using (var sr = new StreamReader("list.txt"))  
  14.             {  
  15.                 this._Images = new List<String>();  
  16.                 while (!sr.EndOfStream)  
  17.                 {  
  18.                     this._Images.Add(sr.ReadLine());  
  19.                 }  
  20.             }  
  21.         }  
  22.         private List<String> _Images;  
  23.   
  24.         public List<String> Images  
  25.         {  
  26.             get { return _Images; }  
  27.             set { _Images = value; }  
  28.         }  
  29.   
  30.     }  
  31. }  

在图上,大家可以看到,有一个载入中的效果,我们的下一个任务,就是把这个效果给做出来。(这个,我照搬的。。)

原图片如下:

loading

WPF原生并不支持GIF格式的图片,并且GIF格式的图片色彩也很有限,所以这个载入中效果是PNG图片加旋转动画完成的。首先,我们要添加一个用户控件。这个用户控件中只有一个Image子控件。在XAML文件中,将Image控件的URI设置为此图片,并且在Image的图片载入完成后,开始动画。XAML(WaitingProgress.xaml)代码如下:

[html]  view plain  copy
  1. <UserControl x:Class="WebImageList.WaitingProgress"  
  2.              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
  4.              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"   
  5.              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"   
  6.              mc:Ignorable="d"   
  7.              d:DesignHeight="300" d:DesignWidth="300">  
  8.     <UserControl.Resources>  
  9.         <Storyboard x:Key="waiting" Name="waiting">  
  10.             <DoubleAnimation Storyboard.TargetName="SpinnerRotate" Storyboard.TargetProperty="(RotateTransform.Angle)" From="0" To="359" Duration="0:0:02" RepeatBehavior="Forever" />  
  11.         </Storyboard>  
  12.     </UserControl.Resources>  
  13.     <Image Name="image" Source="loading.png" RenderTransformOrigin="0.5,0.5" Stretch="None" Loaded="Image_Loaded_1">  
  14.         <Image.RenderTransform>  
  15.             <RotateTransform x:Name="SpinnerRotate" Angle="0" />  
  16.         </Image.RenderTransform>  
  17.     </Image>  
  18. </UserControl>  

对应的CS代码(WaitingProgress.xaml.cs)如下:
[csharp]  view plain  copy
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Windows;  
  6. using System.Windows.Controls;  
  7. using System.Windows.Data;  
  8. using System.Windows.Documents;  
  9. using System.Windows.Input;  
  10. using System.Windows.Media;  
  11. using System.Windows.Media.Animation;  
  12. using System.Windows.Media.Imaging;  
  13. using System.Windows.Navigation;  
  14. using System.Windows.Shapes;  
  15.   
  16. namespace WebImageList  
  17. {  
  18.     /// <summary>  
  19.     /// WaitingProgress.xaml 的交互逻辑  
  20.     /// </summary>  
  21.     public partial class WaitingProgress : UserControl  
  22.     {  
  23.         private Storyboard story;  
  24.         public WaitingProgress()  
  25.         {  
  26.             InitializeComponent();  
  27.             this.story = (base.Resources["waiting"as Storyboard);  
  28.         }  
  29.         private void Image_Loaded_1(object sender, RoutedEventArgs e)  
  30.         {  
  31.             this.story.Begin(this.image, true);  
  32.         }  
  33.         public void Stop()  
  34.         {  
  35.             base.Dispatcher.BeginInvoke(new Action(() => {  
  36.                 this.story.Pause(this.image);  
  37.                 base.Visibility = System.Windows.Visibility.Collapsed;  
  38.             }));  
  39.         }  
  40.     }  
  41. }  

接着,咱们就该分析如何获得图片了。因为图片可能存在本地磁盘上,也可能存在网络上,所以需要根据不同的存储位置,使用不同的方法获取图片(PS:如果使用BitmapImage作为图像源,并且通过URI加载图像,那么,在异步设置Image的Source为此图像时,会发生对象不属于此线程的错误{大概就是这个错误。}。这个错误是BitmapImage报错的,并不是Image控件报错的。具体原因,不太了解。)

在效果图中,我们可以看到,这些图片不是一下子全部显示出来的,而是一张接着一张显示出来的。这里,就要用到一个泛型先入先出集合Queue<T>。在为列表绑定源时,将数据加载到Queue集合中。然后,创建一个后台线程,由这个后台线程不断的从集合中取出数据,并且转化为BitmapImage。并且,在转换完成后会通知Image控件,图片我给你下载完成了,你自己看着办吧。当所有图片都下载完成后,这个后台进程也不会停止,而是处于等待状态,只要有新的绑定进来,那么线程就立刻激活,继续干自己该干的事情。这里我们又要用到一个类型:AutoResetEvent。原理,就是这么一个原理,且看代码(ImageQueue.cs)如下:

[csharp]  view plain  copy
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Net;  
  6. using System.Windows.Controls;  
  7. using System.Windows.Media.Imaging;  
  8. using System.Threading;  
  9. using System.IO;  
  10.   
  11. namespace WebImageList  
  12. {  
  13.     /// <summary>  
  14.     /// 图片下载队列  
  15.     /// </summary>  
  16.     public static class ImageQueue  
  17.     {  
  18.         #region 辅助类别  
  19.         private class ImageQueueInfo  
  20.         {  
  21.             public Image image { getset; }  
  22.             public String url { getset; }  
  23.         }  
  24.         #endregion  
  25.         public delegate void ComplateDelegate(Image i, string u, BitmapImage b);  
  26.         public static event ComplateDelegate OnComplate;  
  27.         private static AutoResetEvent autoEvent;  
  28.         private static Queue<ImageQueueInfo> Stacks;  
  29.         static ImageQueue()  
  30.         {  
  31.             ImageQueue.Stacks = new Queue<ImageQueueInfo>();  
  32.             autoEvent = new AutoResetEvent(true);  
  33.             Thread t = new Thread(new ThreadStart(ImageQueue.DownloadImage));  
  34.             t.Name = "下载图片";  
  35.             t.IsBackground = true;  
  36.             t.Start();  
  37.         }  
  38.         private static void DownloadImage()  
  39.         {  
  40.             while (true)  
  41.             {  
  42.                 ImageQueueInfo t = null;  
  43.                 lock (ImageQueue.Stacks)  
  44.                 {  
  45.                     if (ImageQueue.Stacks.Count > 0)  
  46.                     {  
  47.                         t = ImageQueue.Stacks.Dequeue();  
  48.                     }  
  49.                 }  
  50.                 if (t != null)  
  51.                 {  
  52.                     Uri uri = new Uri(t.url);  
  53.                     BitmapImage image = null;  
  54.                     try  
  55.                     {  
  56.                         if ("http".Equals(uri.Scheme, StringComparison.CurrentCultureIgnoreCase))  
  57.                         {  
  58.                             //如果是HTTP下载文件  
  59.                             WebClient wc = new WebClient();  
  60.                             using (var ms = new MemoryStream(wc.DownloadData(uri)))  
  61.                             {  
  62.                                 image = new BitmapImage();  
  63.                                 image.BeginInit();  
  64.                                 image.CacheOption = BitmapCacheOption.OnLoad;  
  65.                                 image.StreamSource = ms;  
  66.                                 image.EndInit();  
  67.                             }  
  68.                         }  
  69.                         else if ("file".Equals(uri.Scheme, StringComparison.CurrentCultureIgnoreCase))  
  70.                         {  
  71.                             using (var fs = new FileStream(t.url, FileMode.Open))  
  72.                             {  
  73.                                 image = new BitmapImage();  
  74.                                 image.BeginInit();  
  75.                                 image.CacheOption = BitmapCacheOption.OnLoad;  
  76.                                 image.StreamSource = fs;  
  77.                                 image.EndInit();  
  78.                             }  
  79.                         }  
  80.                         if (image != null)  
  81.                         {  
  82.                             if (image.CanFreeze) image.Freeze();  
  83.                             t.image.Dispatcher.BeginInvoke(new Action<ImageQueueInfo, BitmapImage>((i, bmp) =>   
  84.                             {  
  85.                                 if (ImageQueue.OnComplate != null)  
  86.                                 {  
  87.                                     ImageQueue.OnComplate(i.image, i.url, bmp);  
  88.                                 }  
  89.                             }),new Object[] { t, image });  
  90.                         }  
  91.                     }  
  92.                     catch(Exception e)  
  93.                     {  
  94.                         System.Windows.MessageBox.Show(e.Message);  
  95.                         continue;  
  96.                     }  
  97.                 }  
  98.                 if (ImageQueue.Stacks.Count > 0) continue;  
  99.                 autoEvent.WaitOne();  
  100.             }  
  101.         }  
  102.         public static void Queue(Image img, String url)  
  103.         {  
  104.             if (String.IsNullOrEmpty(url)) return;  
  105.             lock (ImageQueue.Stacks)  
  106.             {  
  107.                 ImageQueue.Stacks.Enqueue(new ImageQueueInfo { url = url, image = img });  
  108.                 ImageQueue.autoEvent.Set();  
  109.             }  
  110.         }  
  111.     }  
  112. }  

代码中,我们定义了一个委托和一个事件。通知Image控件的重任,就交给这两元大将了。

接着,我们就来做图片显示列表(ListBox)的效果。我们知道,在Grid控件中,如果一个子控件不设置任何位置或者大小属性的话,这个子控件是会填满这个Grid的。所以我们要自定义ListBox的ItemTemplate。让其包含一个Grid控件。这个Grid控件有两个子控件,一个是刚才做的进度条控件,无定位、大小属性。另一是Image控件,这个控件设置大小属性。

[html]  view plain  copy
  1. <Window x:Class="WebImageList.MainWindow"  
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
  4.         xmlns:local="clr-namespace:WebImageList"  
  5.         Title="MainWindow" Height="600" Width="600" WindowStartupLocation="CenterScreen">  
  6.     <StackPanel>  
  7.         <Button Content="载入图片" Click="Button_Click_1"></Button>  
  8.         <ListBox ItemsSource="{Binding Images}"  ScrollViewer.HorizontalScrollBarVisibility="Disabled">  
  9.             <ListBox.ItemTemplate>  
  10.                 <DataTemplate>  
  11.                     <Grid >  
  12.                         <local:WaitingProgress/>  
  13.                         <Image Stretch="UniformToFill" Width="130" Height="130" local:ImageDecoder.Source="{Binding}"></Image>  
  14.                     </Grid>  
  15.                 </DataTemplate>  
  16.             </ListBox.ItemTemplate>  
  17.             <ListBox.ItemsPanel>  
  18.                 <ItemsPanelTemplate>  
  19.                     <WrapPanel Name="wrapPanel" HorizontalAlignment="Stretch" />  
  20.                 </ItemsPanelTemplate>  
  21.             </ListBox.ItemsPanel>  
  22.         </ListBox>  
  23.     </StackPanel>  
  24. </Window>  

首先,我们先让进度条控件显示出来,接着,为ListBox绑定数据源,同时,把要下载的数据压入下载队列,并且注册下载完成的事件,在事件执行时,将进度条隐藏掉,为Image控件设置Source属性为取到的值并且添加一个渐变动画。

要实现上述功能,我们就需要添加一个依赖属性。

[csharp]  view plain  copy
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Windows;  
  6. using System.Windows.Controls;  
  7. using System.Windows.Media.Animation;  
  8. using System.Windows.Media.Imaging;  
  9.   
  10. namespace WebImageList  
  11. {  
  12.     public static class ImageDecoder  
  13.     {  
  14.         public static readonly DependencyProperty SourceProperty;  
  15.         public static string GetSource(Image image)  
  16.         {  
  17.             if (image == null)  
  18.             {  
  19.                 throw new ArgumentNullException("Image");  
  20.             }  
  21.             return (string)image.GetValue(ImageDecoder.SourceProperty);  
  22.         }  
  23.         public static void SetSource(Image image, string value)  
  24.         {  
  25.             if (image == null)  
  26.             {  
  27.                 throw new ArgumentNullException("Image");  
  28.             }  
  29.             image.SetValue(ImageDecoder.SourceProperty, value);  
  30.         }  
  31.         static ImageDecoder()  
  32.         {  
  33.             ImageDecoder.SourceProperty = DependencyProperty.RegisterAttached("Source"typeof(string), typeof(ImageDecoder), new PropertyMetadata(new PropertyChangedCallback(ImageDecoder.OnSourceWithSourceChanged)));  
  34.             ImageQueue.OnComplate += new ImageQueue.ComplateDelegate(ImageDecoder.ImageQueue_OnComplate);  
  35.         }  
  36.         private static void ImageQueue_OnComplate(Image i, string u, BitmapImage b)  
  37.         {  
  38.             //System.Windows.MessageBox.Show(u);  
  39.             string source = ImageDecoder.GetSource(i);  
  40.             if (source == u.ToString())  
  41.             {  
  42.                 i.Source = b;  
  43.                 Storyboard storyboard = new Storyboard();  
  44.                 DoubleAnimation doubleAnimation = new DoubleAnimation(0.0, 1.0, new Duration(TimeSpan.FromMilliseconds(500.0)));  
  45.                 Storyboard.SetTarget(doubleAnimation, i);  
  46.                 Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("Opacity"new object[0]));  
  47.                 storyboard.Children.Add(doubleAnimation);  
  48.                 storyboard.Begin();  
  49.                 if (i.Parent is Grid)  
  50.                 {  
  51.                     Grid grid = i.Parent as Grid;  
  52.                     foreach (var c in grid.Children)  
  53.                     {  
  54.                         if (c is WaitingProgress && c != null)  
  55.                         {  
  56.                             (c as WaitingProgress).Stop();  
  57.                             break;  
  58.                         }  
  59.                     }  
  60.                 }  
  61.             }  
  62.         }  
  63.         private static void OnSourceWithSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)  
  64.         {  
  65.             ImageQueue.Queue((Image)o, (string)e.NewValue);  
  66.         }  
  67.     }  
  68. }  

至此,这个Demo就算是做完了。我接触WPF的时间虽然不短,但是真正用的却是不多。这篇文章中用的很多东西,都是在做这个Demo的时候学的,欢迎大家与我交流。

另附下载地址:http://files.cnblogs.com/Soar1991/WebImageList.rar


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值