WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

一.前言

  申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接。

  本文主要针对WPF项目开发中图片的各种使用问题,经过总结,把一些经验分享一下。内容包括:

  • WPF常用图像数据源ImageSource的创建;
  • 自定义缩略图控件ThumbnailImage,支持网络图片、大图片、图片异步加载等特性;
  • 动态图片gif播放控件;
  • 图片列表样式,支持大数据量的虚拟化;

二. WPF常用图像数据源ImageSource的创建

<Image Source="../Images/qq.png"></Image> 

  这是一个普通Image控件的使用,Source的数据类型是ImageSource,在XAML中可以使用文件绝对路径或相对路径,ImageSource是一个抽象类,我们一般使用BitmapSource、BitmapImage等。

  但在实际项目中,有各种各样的需求,比如:

    • 从Bitmap创建ImageSource对象;
    • 从数据流byte[]创建ImageSource对象;
    • 从System.Drawing.Image创建ImageSource对象;
    • 从一个大图片文件创建一个指定大小的ImageSource对象;

2.1 从System.Drawing.Image创建指定大小ImageSource对象  

WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表
        /// <summary>
        /// 使用System.Drawing.Image创建WPF使用的ImageSource类型缩略图(不放大小图)
        /// </summary>
        /// <param name="sourceImage">System.Drawing.Image 对象</param>
        /// <param name="width">指定宽度</param>
        /// <param name="height">指定高度</param>
        public static ImageSource CreateImageSourceThumbnia(System.Drawing.Image sourceImage, double width, double height)
        {
            if (sourceImage == null) return null;
            double rw = width / sourceImage.Width;
            double rh = height / sourceImage.Height;
            var aspect = (float)Math.Min(rw, rh);
            int w = sourceImage.Width, h = sourceImage.Height;
            if (aspect < 1)
            {
                w = (int)Math.Round(sourceImage.Width * aspect); h = (int)Math.Round(sourceImage.Height * aspect);
            }
            Bitmap sourceBmp = new Bitmap(sourceImage, w, h);
            IntPtr hBitmap = sourceBmp.GetHbitmap();
            BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty,
                   BitmapSizeOptions.FromEmptyOptions());
            bitmapSource.Freeze();
            System.Utility.Win32.Win32.DeleteObject(hBitmap);
            sourceImage.Dispose();
            sourceBmp.Dispose();
            return bitmapSource;
        }
WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

2.2 从一个大图片文件创建一个指定大小的ImageSource对象  

WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表
        /// <summary>
        /// 创建WPF使用的ImageSource类型缩略图(不放大小图)
        /// </summary>
        /// <param name="fileName">本地图片路径</param>
        /// <param name="width">指定宽度</param>
        /// <param name="height">指定高度</param>
        public static ImageSource CreateImageSourceThumbnia(string fileName, double width, double height)
        {
            System.Drawing.Image sourceImage = System.Drawing.Image.FromFile(fileName);
            double rw = width / sourceImage.Width;
            double rh = height / sourceImage.Height;
            var aspect = (float)Math.Min(rw, rh);
            int w = sourceImage.Width, h = sourceImage.Height;
            if (aspect < 1)
            {
                w = (int)Math.Round(sourceImage.Width * aspect); h = (int)Math.Round(sourceImage.Height * aspect);
            }
            Bitmap sourceBmp = new Bitmap(sourceImage, w, h);
            IntPtr hBitmap = sourceBmp.GetHbitmap();
            BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty,
                   BitmapSizeOptions.FromEmptyOptions());

            bitmapSource.Freeze();
            System.Utility.Win32.Win32.DeleteObject(hBitmap);
            sourceImage.Dispose();
            sourceBmp.Dispose();
            return bitmapSource;
        }
WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

2.3 从Bitmap创建指定大小的ImageSource对象  

WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表
        /// <summary>
        /// 从一个Bitmap创建ImageSource
        /// </summary>
        /// <param name="image">Bitmap对象</param>
        /// <returns></returns>
        public static ImageSource CreateImageSourceFromImage(Bitmap image)
        {
            if (image == null) return null;
            try
            {
                IntPtr ptr = image.GetHbitmap();
                BitmapSource bs = Imaging.CreateBitmapSourceFromHBitmap(ptr, IntPtr.Zero, Int32Rect.Empty,
                                                                        BitmapSizeOptions.FromEmptyOptions());
                bs.Freeze();
                image.Dispose();
                System.Utility.Win32.Win32.DeleteObject(ptr);
                return bs;
            }
            catch (Exception)
            {
                return null;
            }
        }
WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

2.4 从数据流byte[]创建指定大小的ImageSource对象  

WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表
        /// <summary>
        /// 从数据流创建缩略图
        /// </summary>
        public static ImageSource CreateImageSourceThumbnia(byte[] data, double width, double height)
        {
            using (Stream stream = new MemoryStream(data, true))
            {
                using (Image img = Image.FromStream(stream))
                {
                    return CreateImageSourceThumbnia(img, width, height);
                }
            }
        }
WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

三.自定义缩略图控件ThumbnailImage

  ThumbnailImage控件的主要解决的问题:

WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

  为了能扩展支持多种类型的缩略图,设计了一个简单的模式,用VS自带的工具生成的代码视图:

WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

3.1 多种类型的缩略图扩展

  首先定义一个图片类型枚举:  

WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表
    /// <summary>
    /// 缩略图数据源源类型
    /// </summary>
    public enum EnumThumbnail
    {
        Image,
        Vedio,
        WebImage,
        Auto,
        FileX,
    }
WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

  然后定义了一个接口,生成图片数据源ImageSource  

WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表
    /// <summary>
    /// 缩略图创建服务接口
    /// </summary>
    public interface IThumbnailProvider
    {
        /// <summary>
        /// 创建缩略图。fileName:文件路径;width:图片宽度;height:高度
        /// </summary>
        ImageSource GenereateThumbnail(object fileSource, double width, double height);
    }
WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

  如上面的代码视图,有三个实现,视频缩略图VedioThumbnailProvider没有实现完成,基本方法是利用一个第三方工具ffmpeg来获取第一帧图像然后创建ImageSource。

  ImageThumbnailProvider:普通图片缩略图实现(调用的2.2方法)

WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表
    /// <summary>
    /// 本地图片缩略图创建服务
    /// </summary>
    internal class ImageThumbnailProvider : IThumbnailProvider
    {
        /// <summary>
        /// 创建缩略图。fileName:文件路径;width:图片宽度;height:高度
        /// </summary>
        public ImageSource GenereateThumbnail(object fileName, double width, double height)
        {
            try
            {
                var path = fileName.ToSafeString();
                if (path.IsInvalid()) return null;
                return System.Utility.Helper.Images.CreateImageSourceThumbnia(path, width, height);
            }
            catch
            {
                return null;
            }
        }
    }
WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

  WebImageThumbnailProvider:网络图片缩略图实现(下载图片数据后调用2.1方法):  

WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表
    /// <summary>
    /// 网络图片缩略图创建服务
    /// </summary>
    internal class WebImageThumbnailProvider : IThumbnailProvider
    {
        /// <summary>
        /// 创建缩略图。fileName:文件路径;width:图片宽度;height:高度
        /// </summary>
        public ImageSource GenereateThumbnail(object fileName, double width, double height)
        {
            try
            {
                var path = fileName.ToSafeString();
                if (path.IsInvalid()) return null;
                var request = WebRequest.Create(path);
                request.Timeout = 20000;
                var stream = request.GetResponse().GetResponseStream();
                var img = System.Drawing.Image.FromStream(stream);
                return System.Utility.Helper.Images.CreateImageSourceThumbnia(img, width, height);
            }
            catch
            {
                return null;
            }
        }
    }
WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

  简单工厂ThumbnailProviderFactory实现:  

WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表
    /// <summary>
    /// 缩略图创建服务简单工厂
    /// </summary>
    public class ThumbnailProviderFactory : System.Utility.Patterns.ISimpleFactory<EnumThumbnail, IThumbnailProvider>
    {
        /// <summary>
        /// 根据key获取实例
        /// </summary>
        public virtual IThumbnailProvider GetInstance(EnumThumbnail key)
        {
            switch (key)
            {
                case EnumThumbnail.Image:
                    return Singleton<ImageThumbnailProvider>.GetInstance();
                case EnumThumbnail.Vedio:
                    return Singleton<VedioThumbnailProvider>.GetInstance();
                case EnumThumbnail.WebImage:
                    return Singleton<WebImageThumbnailProvider>.GetInstance();
            }
            return null;
        }
    }
WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

 

3.2 缩略图控件ThumbnailImage

  先看看效果图吧,下面三张图片,图1是本地图片,图2是网络图片,图3也是网络图片,为什么没显示呢,这张图片用的是国外的图片链接地址,异步加载(加载比较慢,还没出来的!)

WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

  ThumbnailImage实际是继承在微软的图片控件Image,因此没有样式代码,继承之后,主要的目的就是重写Imagesource的处理过程,详细代码:

WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表
WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表
   /*
     * 较大的图片,视频,网络图片要做缓存处理:缓存缩略图为本地文件,或内存缩略图对象。
     */

    /// <summary>
    /// 缩略图图片显示控件,同时支持图片和视频缩略图
    /// </summary>
    public class ThumbnailImage : Image
    {
        /// <summary>
        /// 是否启用缓存,默认false不启用
        /// </summary>
        public bool CacheEnable
        {
            get { return (bool)GetValue(CacheEnableProperty); }
            set { SetValue(CacheEnableProperty, value); }
        }
        /// <summary>
        /// 是否启用缓存,默认false不启用.默认缓存时间是180秒
        /// </summary>
        public static readonly DependencyProperty CacheEnableProperty =
            DependencyProperty.Register("CacheEnable", typeof(bool), typeof(ThumbnailImage), new PropertyMetadata(false));

        /// <summary>
        /// 缓存时间,单位秒。默认180秒
        /// </summary>
        public int CacheTime
        {
            get { return (int)GetValue(CacheTimeProperty); }
            set { SetValue(CacheTimeProperty, value); }
        }
        public static readonly DependencyProperty CacheTimeProperty =
            DependencyProperty.Register("CacheTime", typeof(int), typeof(ThumbnailImage), new PropertyMetadata(180));

        /// <summary>
        /// 是否启用异步加载,网络图片建议启用,本地图可以不需要。默认不起用异步
        /// </summary>
        public bool AsyncEnable
        {
            get { return (bool)GetValue(AsyncEnableProperty); }
            set { SetValue(AsyncEnableProperty, value); }
        }
        public static readonly DependencyProperty AsyncEnableProperty =
            DependencyProperty.Register("AsyncEnable", typeof(bool), typeof(ThumbnailImage), new PropertyMetadata(false));

        /// <summary>
        /// 缩略图类型,默认Image图片
        /// </summary>
        public EnumThumbnail ThumbnailType
        {
            get { return (EnumThumbnail)GetValue(ThumbnailTypeProperty); }
            set { SetValue(ThumbnailTypeProperty, value); }
        }
        public static readonly DependencyProperty ThumbnailTypeProperty =
            DependencyProperty.Register("ThumbnailType", typeof(EnumThumbnail), typeof(ThumbnailImage), new PropertyMetadata(EnumThumbnail.Image));

        /// <summary>
        /// 缩略图数据源:文件物理路径
        /// </summary>
        public object ThumbnailSource
        {
            get { return GetValue(ThumbnailSourceProperty); }
            set { SetValue(ThumbnailSourceProperty, value); }
        }
        public static readonly DependencyProperty ThumbnailSourceProperty = DependencyProperty.Register("ThumbnailSource", typeof(object),
            typeof(ThumbnailImage), new PropertyMetadata(OnSourcePropertyChanged));

        /// <summary>
        /// 缩略图
        /// </summary>
        protected static ThumbnailProviderFactory ThumbnailProviderFactory = new ThumbnailProviderFactory();

        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            this.Loaded += ThumbnailImage_Loaded;
        }

        void ThumbnailImage_Loaded(object sender, RoutedEventArgs e)
        {
            BindSource(this);
        }

        /// <summary>
        /// 属性更改处理事件
        /// </summary>
        private static void OnSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            ThumbnailImage img = sender as ThumbnailImage;
            if (img == null) return;
            if (!img.IsLoaded) return;
            BindSource(img);
        }
        private static void BindSource(ThumbnailImage image)
        {
            var w = image.Width;
            var h = image.Height;
            object source = image.ThumbnailSource;
            //bind
            if (image.AsyncEnable)
            {
                BindThumbnialAync(image, source, w, h);
            }
            else
            {
                BindThumbnial(image, source, w, h);
            }
        }

        /// <summary>
        /// 绑定缩略图
        /// </summary>
        private static void BindThumbnial(ThumbnailImage image, object fileSource, double w, double h)
        {
            IThumbnailProvider thumbnailProvider = ThumbnailProviderFactory.GetInstance(image.ThumbnailType);
            image.Dispatcher.BeginInvoke(new Action(() =>
            {
                var cache = image.CacheEnable;
                var time = image.CacheTime;
                ImageSource img = null;
                if (cache)
                {
                    img = CacheManager.GetCache<ImageSource>(fileSource.GetHashCode().ToString(), time, () =>
                    {
                        return thumbnailProvider.GenereateThumbnail(fileSource, w, h);
                    });
                }
                else img = thumbnailProvider.GenereateThumbnail(fileSource, w, h);
                image.Source = img;
            }), DispatcherPriority.ApplicationIdle);
        }

        /// <summary>
        /// 异步线程池绑定缩略图
        /// </summary>
        private 
上一篇:WPF自定义控件与样式(14)-轻量MVVM模式实践


下一篇:移动数据文件的方法