WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表
创始人
2024-04-04 22:31:00

一.前言

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

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

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

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

 

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

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

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

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

        /// /// 使用System.Drawing.Image创建WPF使用的ImageSource类型缩略图(不放大小图)///  /// System.Drawing.Image 对象 /// 指定宽度 /// 指定高度 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; }

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

        /// /// 创建WPF使用的ImageSource类型缩略图(不放大小图)///  /// 本地图片路径 /// 指定宽度 /// 指定高度 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; }

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

        /// /// 从一个Bitmap创建ImageSource///  /// Bitmap对象 ///  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; } }

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

        /// /// 从数据流创建缩略图///  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); } } }

三.自定义缩略图控件ThumbnailImage

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

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

3.1 多种类型的缩略图扩展

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

    /// /// 缩略图数据源源类型///  public enum EnumThumbnail { Image, Vedio, WebImage, Auto, FileX, }

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

    /// /// 缩略图创建服务接口///  public interface IThumbnailProvider { ///  /// 创建缩略图。fileName:文件路径;width:图片宽度;height:高度 ///  ImageSource GenereateThumbnail(object fileSource, double width, double height); }

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

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

    /// /// 本地图片缩略图创建服务///  internal class ImageThumbnailProvider : IThumbnailProvider { ///  /// 创建缩略图。fileName:文件路径;width:图片宽度;height:高度 ///  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; } } }

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

    /// /// 网络图片缩略图创建服务///  internal class WebImageThumbnailProvider : IThumbnailProvider { ///  /// 创建缩略图。fileName:文件路径;width:图片宽度;height:高度 ///  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; } } }

  简单工厂ThumbnailProviderFactory实现:  

    /// /// 缩略图创建服务简单工厂///  public class ThumbnailProviderFactory : System.Utility.Patterns.ISimpleFactory { ///  /// 根据key获取实例 ///  public virtual IThumbnailProvider GetInstance(EnumThumbnail key) { switch (key) { case EnumThumbnail.Image: return Singleton.GetInstance(); case EnumThumbnail.Vedio: return Singleton.GetInstance(); case EnumThumbnail.WebImage: return Singleton.GetInstance(); } return null; } }

3.2 缩略图控件ThumbnailImage

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

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

   /** 较大的图片,视频,网络图片要做缓存处理:缓存缩略图为本地文件,或内存缩略图对象。*////  /// 缩略图图片显示控件,同时支持图片和视频缩略图 ///  public class ThumbnailImage : Image { ///  /// 是否启用缓存,默认false不启用 ///  public bool CacheEnable { get { return (bool)GetValue(CacheEnableProperty); } set { SetValue(CacheEnableProperty, value); } } ///  /// 是否启用缓存,默认false不启用.默认缓存时间是180秒 ///  public static readonly DependencyProperty CacheEnableProperty = DependencyProperty.Register("CacheEnable", typeof(bool), typeof(ThumbnailImage), new PropertyMetadata(false)); ///  /// 缓存时间,单位秒。默认180秒 ///  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)); ///  /// 是否启用异步加载,网络图片建议启用,本地图可以不需要。默认不起用异步 ///  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)); ///  /// 缩略图类型,默认Image图片 ///  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)); ///  /// 缩略图数据源:文件物理路径 ///  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)); ///  /// 缩略图 ///  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); } ///  /// 属性更改处理事件 ///  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); } } ///  /// 绑定缩略图 ///  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(fileSource.GetHashCode().ToString(), time, () => { return thumbnailProvider.GenereateThumbnail(fileSource, w, h); }); } else img = thumbnailProvider.GenereateThumbnail(fileSource, w, h); image.Source = img; }), DispatcherPriority.ApplicationIdle); } ///  /// 异步线程池绑定缩略图 /// 

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...