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

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

最近,在做一个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.jpg
C:\Users\Soar\Pictures\lovewallpaper\18451,106.jpg
http://img12.360buyimg.com//n3/g1/M00/06/1D/rBEGDVAkffQIAAAAAAB0mDavAccAABWrQMCUdwAAHSw197.jpg
C:\Users\Soar\Pictures\lovewallpaper\367448,106.jpg
http://img13.360buyimg.com//n3/g2/M00/06/1D/rBEGElAkffIIAAAAAADVR1yd_X0AABWrQKlu2MAANVf537.jpg
C:\Users\Soar\Pictures\lovewallpaper\359090,106.jpg
http://img10.360buyimg.com//n3/g5/M02/1C/00/rBEIC1Akfe8IAAAAAABDtsBt3bQAAFeCQAh13kAAEPO445.jpg
http://img11.360buyimg.com//n3/g3/M00/06/1D/rBEGE1AkfgIIAAAAAACfm_MhwRYAABWrQMmK8kAAJ-z240.jpg
http://img12.360buyimg.com//n3/g3/M00/06/1D/rBEGFFAkfhQIAAAAAABHekJE6jQAABWrQOGiEUAAEeS965.jpg
http://img13.360buyimg.com//n3/g2/M00/06/1D/rBEGElAkfegIAAAAAAClvhjSNQoAABWrQJ0KTIAAKXW818.jpg
http://img14.360buyimg.com//n3/g1/M00/06/1D/rBEGDlAkfe4IAAAAAABQsM9eGEoAABWrQJ4WIwAAFDI883.jpg
http://img10.360buyimg.com//n3/g3/M00/06/1D/rBEGE1AkfgQIAAAAAACBZc_HeVAAABWrQM293sAAIF9407.jpg
http://img11.360buyimg.com//n3/g3/M00/06/1D/rBEGE1AkfgkIAAAAAAC_6A3AnhwAABWrQOfht8AAMAA406.jpg
http://img12.360buyimg.com//n3/g5/M02/1C/00/rBEDilAkfeAIAAAAAACdJBYljH0AAFeCQAuIsMAAJ08326.jpg
http://img13.360buyimg.com//n3/g1/M00/06/1D/rBEGDVAkfe4IAAAAAACXzwGDqfoAABWrQKpCmEAAJfn685.jpg
http://img12.360buyimg.com//n3/g3/M00/06/1D/rBEGE1AkfgcIAAAAAAC5nK25hEQAABWrQOCa3sAALm0258.jpg
http://img14.360buyimg.com//n3/g2/M00/06/1D/rBEGEFAkfdUIAAAAAACZblNaX_kAABWrQJ0zwgAAJmG566.jpg
http://img14.360buyimg.com//n3/g2/M00/06/1D/rBEGEFAkfewIAAAAAACfqQVJlNoAABWrQOirGwAAJ_B820.jpg
http://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<String>();while (!sr.EndOfStream){this._Images.Add(sr.ReadLine());}}}private List<String> _Images;public List<String> Images{get { return _Images; }set { _Images = value; }}}
}

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

原图片如下:

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

<UserControl x:Class="WebImageList.WaitingProgress"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"><UserControl.Resources><Storyboard x:Key="waiting" Name="waiting"><DoubleAnimation Storyboard.TargetName="SpinnerRotate" Storyboard.TargetProperty="(RotateTransform.Angle)" From="0" To="359" Duration="0:0:02" RepeatBehavior="Forever" /></Storyboard></UserControl.Resources><Image Name="image" Source="loading.png" RenderTransformOrigin="0.5,0.5" Stretch="None" Loaded="Image_Loaded_1"><Image.RenderTransform><RotateTransform x:Name="SpinnerRotate" Angle="0" /></Image.RenderTransform></Image>
</UserControl>

对应的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
{/// <summary>/// WaitingProgress.xaml 的交互逻辑/// </summary>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
{/// <summary>/// 图片下载队列/// </summary>public static class ImageQueue{#region 辅助类别private class ImageQueueInfo{public Image image { get; set; }public String url { get; set; }}#endregionpublic delegate void ComplateDelegate(Image i, string u, BitmapImage b);public static event ComplateDelegate OnComplate;private static AutoResetEvent autoEvent;private static Queue<ImageQueueInfo> Stacks;static ImageQueue(){ImageQueue.Stacks = new Queue<ImageQueueInfo>();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<ImageQueueInfo, BitmapImage>((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控件,这个控件设置大小属性。

<Window x:Class="WebImageList.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:WebImageList"Title="MainWindow" Height="600" Width="600" WindowStartupLocation="CenterScreen"><StackPanel><Button Content="载入图片" Click="Button_Click_1"></Button><ListBox ItemsSource="{Binding Images}"  ScrollViewer.HorizontalScrollBarVisibility="Disabled"><ListBox.ItemTemplate><DataTemplate><Grid ><local:WaitingProgress/><Image Stretch="UniformToFill" Width="130" Height="130" local:ImageDecoder.Source="{Binding}"></Image></Grid></DataTemplate></ListBox.ItemTemplate><ListBox.ItemsPanel><ItemsPanelTemplate><WrapPanel Name="wrapPanel" HorizontalAlignment="Stretch" /></ItemsPanelTemplate></ListBox.ItemsPanel></ListBox></StackPanel>
</Window>

首先,我们先让进度条控件显示出来,接着,为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://files.cnblogs.com/Soar1991/WebImageList.rar

posted on 2015-04-10 09:30 NET未来之路 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/4413637.html

WPF异步载入图片,附带载入中动画相关推荐

  1. Android批量图片载入经典系列——使用LruCache、AsyncTask缓存并异步载入图片

    一.问题描写叙述 使用LruCache.AsyncTask实现批量图片的载入并达到下列技术要求 1.从缓存中读取图片,若不在缓存中,则开启异步线程(AsyncTask)载入图片,并放入缓存中 2.及时 ...

  2. html 载入中,用纯CSS实现加载中动画效果

    今天要介绍的是用简单的CSS--只用CSS,不用Gif--制作"加载中-"动画效果.先看看效果: 上面的这个加载中效果,以前是用gif动图实现的,但随着CSS的进步,CSS动画功能 ...

  3. 现在无法开始异步操作。异步操作只能在异步处理程序或模块中开始,或在页生存期中的特定事件过程中开始...

    异常处理汇总-后端系列 http://www.cnblogs.com/dunitian/p/4523006.html 这篇没啥技术含量,用来小记一番 错误信息 "System.Invalid ...

  4. ajax异步获取右侧html,Ajax异步获取html数据中包含js方法无效的解决方法

    Ajax异步获取html数据中包含js方法无效的解决方法 页面上使用js写了一个获取后台数据的方法 function data() { var tab = $("#dic") $. ...

  5. 正在载入中......loading页面的几种方法

    网页加载过程中提示"载入中-",特别是使用动画效果,可以一个"等待"的温馨提示,用户体验很不错.下面介绍几种方法. 第一种: 原理就是,在网页载入时在页面最中间 ...

  6. 原生JavaScript中动画与特效的实现原理

    现如今,许多页面上均有一些动画效果.适当的动画效果可以在一定程度上提高页面的美观度,具有提示效果的动画可以增强页面的易用性. 实现页面动画的途径一般有两种. 一种是通过操作JavaScript间接操作 ...

  7. Android中动画详细讲解

    一.前言 Android动画经常会在切换activity.数据加载时会用到动画效果,以前接触的比较少,用的都是封装好的动画效果,自己写的比较少,今天心血来潮想写一个自己设计的动画效果,发现学习动画代码 ...

  8. QCon演讲速递:异步处理在分布式系统中的优化作用

    本文根据阿里巴巴技术保障研究员赵海平在2015年QCon全球软件开发大会(北京站)主题演讲整理而成. \\ 赵海平在Facebook工作8年期间,主要针对后端进行性能优化的工作,包括PHP的优化,me ...

  9. 【WPF】拖拽ListBox中的Item

    原文:[WPF]拖拽ListBox中的Item 整理了两个关于WPF拖拽ListBox中的Item的功能.项目地址 https://github.com/Guxin233/WPF-DragItemIn ...

最新文章

  1. Eclipse for Tricore 的安装方法
  2. LeetCode每日一题:2.两数相加
  3. 鼠标点击后的CSS3跑马灯效果
  4. NSLog 输出格式集合
  5. 使数据可供ArcGIS Server访问
  6. centos ping 路由_centos服务器怎么ping 命令
  7. SameSite 属性
  8. FiddlerScript实现对QQ空间账号密码获取
  9. 【SuperMap】SuperMap.Geometry转GeoJSON
  10. 备品管理方案怎么写_备品备件管理制度62802
  11. 不知道如何录音转文字?分享两个实用方法
  12. mac 重置系统关机启动参数,重置电脑闪存数据
  13. 如何用电话扩大Android内存,扩大内存 Android开启App2SD+教程
  14. 电脑数据,电脑数据恢复软件,失易得数据恢复
  15. 连接数据库时出错 : The server time zone value is unrecognized or represents more than one time z
  16. 一文读懂 快速掌握示波器使用及原理
  17. vue2升级到 Vue3的异同(入门须知)
  18. linux bash语法检查,ShellCheck — Shell Script 语法检查工具
  19. echarts动态改变series,数据重合问题
  20. 【Linux】【CentOS】CentOS 网络管理

热门文章

  1. phpexcel设置AAA单元格,兼容大于702列数据
  2. springmvc基础入门,你确定你真的理解_双亲委派_了吗?
  3. 从入门到精通系列Java高级工程师路线介绍,附答案
  4. 【迁移学习(Transfer L)全面指南】Pytorch处理体积数据
  5. 【机器学习】支持向量机和ensemble method的解析应用
  6. 【深度学习】生动分析半监督学习与负相关学习算法
  7. 【响应式Web前端设计】在html页面实时显示系统时间
  8. python【蓝桥杯vip练习题库】ADV-297快速排序
  9. Keras【Deep Learning With Python】—Keras实现序贯模型
  10. 线程同步锁 java_java多线程同步之重入锁,详细解析