博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WPF异步载入图片,附带载入中动画
阅读量:5879 次
发布时间:2019-06-19

本文共 12174 字,大约阅读时间需要 40 分钟。

原文:

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

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

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

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

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

示例代码如下:

 
http://img11.360buyimg.com//n3/g2/M00/06/1D/rBEGEVAkffUIAAAAAAB54F55qh8AABWrQLxLr0AAHn4106.jpgC:\Users\Soar\Pictures\lovewallpaper\18451,106.jpghttp://img12.360buyimg.com//n3/g1/M00/06/1D/rBEGDVAkffQIAAAAAAB0mDavAccAABWrQMCUdwAAHSw197.jpgC:\Users\Soar\Pictures\lovewallpaper\367448,106.jpghttp://img13.360buyimg.com//n3/g2/M00/06/1D/rBEGElAkffIIAAAAAADVR1yd_X0AABWrQKlu2MAANVf537.jpgC:\Users\Soar\Pictures\lovewallpaper\359090,106.jpghttp://img10.360buyimg.com//n3/g5/M02/1C/00/rBEIC1Akfe8IAAAAAABDtsBt3bQAAFeCQAh13kAAEPO445.jpghttp://img11.360buyimg.com//n3/g3/M00/06/1D/rBEGE1AkfgIIAAAAAACfm_MhwRYAABWrQMmK8kAAJ-z240.jpghttp://img12.360buyimg.com//n3/g3/M00/06/1D/rBEGFFAkfhQIAAAAAABHekJE6jQAABWrQOGiEUAAEeS965.jpghttp://img13.360buyimg.com//n3/g2/M00/06/1D/rBEGElAkfegIAAAAAAClvhjSNQoAABWrQJ0KTIAAKXW818.jpghttp://img14.360buyimg.com//n3/g1/M00/06/1D/rBEGDlAkfe4IAAAAAABQsM9eGEoAABWrQJ4WIwAAFDI883.jpghttp://img10.360buyimg.com//n3/g3/M00/06/1D/rBEGE1AkfgQIAAAAAACBZc_HeVAAABWrQM293sAAIF9407.jpghttp://img11.360buyimg.com//n3/g3/M00/06/1D/rBEGE1AkfgkIAAAAAAC_6A3AnhwAABWrQOfht8AAMAA406.jpghttp://img12.360buyimg.com//n3/g5/M02/1C/00/rBEDilAkfeAIAAAAAACdJBYljH0AAFeCQAuIsMAAJ08326.jpghttp://img13.360buyimg.com//n3/g1/M00/06/1D/rBEGDVAkfe4IAAAAAACXzwGDqfoAABWrQKpCmEAAJfn685.jpghttp://img12.360buyimg.com//n3/g3/M00/06/1D/rBEGE1AkfgcIAAAAAAC5nK25hEQAABWrQOCa3sAALm0258.jpghttp://img14.360buyimg.com//n3/g2/M00/06/1D/rBEGEFAkfdUIAAAAAACZblNaX_kAABWrQJ0zwgAAJmG566.jpghttp://img14.360buyimg.com//n3/g2/M00/06/1D/rBEGEFAkfewIAAAAAACfqQVJlNoAABWrQOirGwAAJ_B820.jpghttp://img11.360buyimg.com//n3/g2/M01/06/1D/rBEGEFAkffMIAAAAAACgY4EpzwYAABWrgAfHyIAAKB7880.jpg
下面是ViewModel的代码(MainViewModel.cs):
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.IO;namespace WebImageList{    public class MainViewModel    {        public MainViewModel()        {            using (var sr = new StreamReader("list.txt"))            {                this._Images = new List
(); while (!sr.EndOfStream) { this._Images.Add(sr.ReadLine()); } } } private List
_Images; public List
Images { get { return _Images; } set { _Images = value; } } }}

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

原图片如下:

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

对应的CS代码(WaitingProgress.xaml.cs)如下:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Animation;using System.Windows.Media.Imaging;using System.Windows.Navigation;using System.Windows.Shapes;namespace WebImageList{    ///     /// WaitingProgress.xaml 的交互逻辑    ///     public partial class WaitingProgress : UserControl    {        private Storyboard story;        public WaitingProgress()        {            InitializeComponent();            this.story = (base.Resources["waiting"] as Storyboard);        }        private void Image_Loaded_1(object sender, RoutedEventArgs e)        {            this.story.Begin(this.image, true);        }        public void Stop()        {            base.Dispatcher.BeginInvoke(new Action(() => {                this.story.Pause(this.image);                base.Visibility = System.Windows.Visibility.Collapsed;            }));        }    }}

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

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

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Net;using System.Windows.Controls;using System.Windows.Media.Imaging;using System.Threading;using System.IO;namespace WebImageList{    ///     /// 图片下载队列    ///     public static class ImageQueue    {        #region 辅助类别        private class ImageQueueInfo        {            public Image image { get; set; }            public String url { get; set; }        }        #endregion        public delegate void ComplateDelegate(Image i, string u, BitmapImage b);        public static event ComplateDelegate OnComplate;        private static AutoResetEvent autoEvent;        private static Queue
Stacks; static ImageQueue() { ImageQueue.Stacks = new Queue
(); autoEvent = new AutoResetEvent(true); Thread t = new Thread(new ThreadStart(ImageQueue.DownloadImage)); t.Name = "下载图片"; t.IsBackground = true; t.Start(); } private static void DownloadImage() { while (true) { ImageQueueInfo t = null; lock (ImageQueue.Stacks) { if (ImageQueue.Stacks.Count > 0) { t = ImageQueue.Stacks.Dequeue(); } } if (t != null) { Uri uri = new Uri(t.url); BitmapImage image = null; try { if ("http".Equals(uri.Scheme, StringComparison.CurrentCultureIgnoreCase)) { //如果是HTTP下载文件 WebClient wc = new WebClient(); using (var ms = new MemoryStream(wc.DownloadData(uri))) { image = new BitmapImage(); image.BeginInit(); image.CacheOption = BitmapCacheOption.OnLoad; image.StreamSource = ms; image.EndInit(); } } else if ("file".Equals(uri.Scheme, StringComparison.CurrentCultureIgnoreCase)) { using (var fs = new FileStream(t.url, FileMode.Open)) { image = new BitmapImage(); image.BeginInit(); image.CacheOption = BitmapCacheOption.OnLoad; image.StreamSource = fs; image.EndInit(); } } if (image != null) { if (image.CanFreeze) image.Freeze(); t.image.Dispatcher.BeginInvoke(new Action
((i, bmp) => { if (ImageQueue.OnComplate != null) { ImageQueue.OnComplate(i.image, i.url, bmp); } }),new Object[] { t, image }); } } catch(Exception e) { System.Windows.MessageBox.Show(e.Message); continue; } } if (ImageQueue.Stacks.Count > 0) continue; autoEvent.WaitOne(); } } public static void Queue(Image img, String url) { if (String.IsNullOrEmpty(url)) return; lock (ImageQueue.Stacks) { ImageQueue.Stacks.Enqueue(new ImageQueueInfo { url = url, image = img }); ImageQueue.autoEvent.Set(); } } }}

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

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

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

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

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows;using System.Windows.Controls;using System.Windows.Media.Animation;using System.Windows.Media.Imaging;namespace WebImageList{    public static class ImageDecoder    {        public static readonly DependencyProperty SourceProperty;        public static string GetSource(Image image)        {            if (image == null)            {                throw new ArgumentNullException("Image");            }            return (string)image.GetValue(ImageDecoder.SourceProperty);        }        public static void SetSource(Image image, string value)        {            if (image == null)            {                throw new ArgumentNullException("Image");            }            image.SetValue(ImageDecoder.SourceProperty, value);        }        static ImageDecoder()        {            ImageDecoder.SourceProperty = DependencyProperty.RegisterAttached("Source", typeof(string), typeof(ImageDecoder), new PropertyMetadata(new PropertyChangedCallback(ImageDecoder.OnSourceWithSourceChanged)));            ImageQueue.OnComplate += new ImageQueue.ComplateDelegate(ImageDecoder.ImageQueue_OnComplate);        }        private static void ImageQueue_OnComplate(Image i, string u, BitmapImage b)        {            //System.Windows.MessageBox.Show(u);            string source = ImageDecoder.GetSource(i);            if (source == u.ToString())            {                i.Source = b;                Storyboard storyboard = new Storyboard();                DoubleAnimation doubleAnimation = new DoubleAnimation(0.0, 1.0, new Duration(TimeSpan.FromMilliseconds(500.0)));                Storyboard.SetTarget(doubleAnimation, i);                Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("Opacity", new object[0]));                storyboard.Children.Add(doubleAnimation);                storyboard.Begin();                if (i.Parent is Grid)                {                    Grid grid = i.Parent as Grid;                    foreach (var c in grid.Children)                    {                        if (c is WaitingProgress && c != null)                        {                            (c as WaitingProgress).Stop();                            break;                        }                    }                }            }        }        private static void OnSourceWithSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)        {            ImageQueue.Queue((Image)o, (string)e.NewValue);        }    }}

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

另附下载地址:

转载地址:http://lbdix.baihongyu.com/

你可能感兴趣的文章
KDTree 板子
查看>>
.NET深入解析LINQ框架2
查看>>
股票新闻速递 隐私声明
查看>>
获取synchronized锁中的阻塞队列中的线程是非公平的
查看>>
在浏览器中输入Google.com并且按下回车之后发生了什么?[转]
查看>>
js刷新页面方法
查看>>
java递归理解
查看>>
工业大数据分析平台的应用价值探讨
查看>>
使用maven引入slf4j、logback时发生冲突
查看>>
课堂作业之找小水王
查看>>
团队编程需求分析
查看>>
Repost: Move semantics and smart pointers by Alex --- learncpp
查看>>
js中的 !! 和 ! 的区别
查看>>
ueditor内容带格式回显(html字符串回显)
查看>>
Mysql 函数
查看>>
spring源码-增强容器xml解析-3.1
查看>>
使用iSCSI Target创建集中式安全存储(一)
查看>>
18.一篇文章,从源码深入详解ThreadLocal内存泄漏问题
查看>>
Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理
查看>>
CSS列表
查看>>