WPF異步載入圖片,附帶載入中動畫


Video_2012-09-16_170249最近,在做一個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; }
        }

    }
}

在圖上,大家可以看到,有一個載入中的效果,我們的下一個任務,就是把這個效果給做出來。(這個,我照搬的。。)

原圖片如下:

loading

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM