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; }
        }
        #endregion
        public 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

时间: 2024-10-11 03:42:03

WPF异步载入图片,附带载入中动画的相关文章

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

一.问题描写叙述 使用LruCache.AsyncTask实现批量图片的载入并达到下列技术要求 1.从缓存中读取图片,若不在缓存中,则开启异步线程(AsyncTask)载入图片,并放入缓存中 2.及时移除无效的异步线程;保证异步载入图片时不会乱序 3.仅仅对当前屏幕可见部分进行缓存.异步载入图片 4.优化性能杜绝OOM 二.案例介绍 案例实现照片墙效果 三.主要技术 LruCache 内存缓存技术.在Android中 专门用来做图片缓存处理的组件,主要使用步骤 (1) 设置缓存图片的内存大小,如

WPF实现QQ群文件列表动画(二)

上篇(WPF实现QQ群文件列表动画(一))介绍了WPF实现QQ群文件列表动画的大致思路,结合我之前讲过的WPF里ItemsControl的分组实现,实现起来问题不大,以下是效果图: 其实就是个ListBox,使用了它的分组样式,而分组样式其实就是一组Expander,这就让我有机会使用自定义的Expander样式,这个Expander样式已经实现了动画伸缩.后台做的就相当简单了,只需要给这个ListBox指定ItemSource并且给它添加一个分组依据就行了. 注意一个小细节:我把自带的Head

WPF异步调用WCF服务

wpf调用wcf时,第一次访问总耗时到达几秒,影响界面的用户体验,因此在wpf加载界面和加载数据时采用异步加载,即异步访问wcf服务, 由于是否采用异步加载和服务端无关,仅仅由客户端自己根据需要来选择,则我们只需要在客户端(WPF)程序中采用异步的方法.如下: //1.创建一个异步对象,访问wcfFunc<string, string, List<NodeItem>> GetDataFromWCF = new Func<string, string, List<Node

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

网页加载过程中提示“载入中…”,特别是使用动画效果,可以一个“等待”的温馨提示,用户体验很不错.下面介绍几种方法. 第一种: 原理就是,在网页载入时在页面最中间打入一个层上面显示,"网页正在载入中...."当网页下载完毕,,用JS关闭这个层....... 先在首页HTML最上面...任意位置都行..加入 <div id=loading style="position:absolute; left:423px; top:261px; width:227px; height

整合大量开源库项目(八)能够载入Gif动画的GifImageView

转载请注明出处王亟亟的大牛之路 上周大多数时间都是依据兴起,想到什么做什么写了几个自己定义控件,把Soyi丢在那没怎么动,今天就把写的东西整合进来,顺便把SOyi"个人研发的结构理一下". 先上一下今天整合之后的效果,以及新加进来的几个库: 依照惯例,贴一下Gradle的配置: dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') testCompile 'junit:junit:4.12' compile '

WPF实现渐变淡入淡出的动画效果

原文:WPF实现渐变淡入淡出的动画效果 1.实现原理 1.1 利用UIElement.OpacityMask属性,用于改变对象区域的不透明度的画笔.可以使元素的特定区域透明或部分透明,从而实现比较新颖的效果. 1.2 OpacityMask属性接受任何画刷,可利用LinearGradientBrush线性渐变画刷,通过对渐变画刷中各颜色点加以动画处理即可. 2.渐变淡入实现 渐变淡入效果,可通过事件触发器触发Loaded事件实现,所以可以仅用前端XAML语言实现. 2.1 设置对象的Opacit

WPF - 如何引用external dll中图片

前几天一直想引用别的DLL里面的一个图片.总是出现各种各样问题,一气之下将图片拷贝到当前Project中,运行良好.虽然知道引用图片,就1.2.列出来的2点就够了. 1. The Build Action should be Resource instead of Embedded Resource. 2. In the WPF application with the following XAML: <Image Source="/ClassLibraryName;Component/i

iOS中动画的简单使用

//***简单动画 [UIView animateWithDuration:3 animations:^{ //      _animationView.center = CGPointMake(arc4random()%320, arc4random()%480);//改变中心点 //      _animationView.frame = CGRectMake(arc4random()%320, arc4random()%480,arc4random()%320, arc4random()%

jQuery中动画animate(下)

jQuery中动画animate(下) animate在执行动画中,如果需要观察动画的一些执行情况,或者在动画进行中的某一时刻进行一些其他处理,我们可以通过animate提供的第二种设置语法,传递一个对象参数,可以拿到动画执行状态一些通知 .animate( properties, options ) options参数 duration - 设置动画执行的时间 easing - 规定要使用的 easing 函数,过渡使用哪种缓动函数 step:规定每个动画的每一步完成之后要执行的函数 prog