WPF自定义控件之图片控件 AsyncImage 您所在的位置:网站首页 山鸡头的图片 WPF自定义控件之图片控件 AsyncImage

WPF自定义控件之图片控件 AsyncImage

2024-07-14 20:03| 来源: 网络整理| 查看: 265

AsyncImage 是一个封装完善,使用简便,功能齐全的WPF图片控件,比直接使用Image相对来说更加方便,但它的内部仍然使用Image承载图像,只不过在其基础上进行了一次完善成熟的封装

AsyncImage解决了以下问题1) 异步加载及等待提示2) 缓存3) 支持读取多种形式的图片路径 (Local,Http,Resource)4) 根据文件头识别准确的图片格式5) 静态图支持设置解码大小6) 支持GIF

AsyncImage的工作流程

 

开始创建

首先声明一个自定义控件

public class AsyncImage : Control { static AsyncImage() { DefaultStyleKeyProperty.OverrideMetadata(typeof(AsyncImage), new FrameworkPropertyMetadata(typeof(AsyncImage))); ImageCacheList = new ConcurrentDictionary(); //初始化静态图缓存字典 GifImageCacheList = new ConcurrentDictionary(); //初始化gif缓存字典 } }

 

声明成员

#region DependencyProperty public static readonly DependencyProperty DecodePixelWidthProperty = DependencyProperty.Register("DecodePixelWidth", typeof(double), typeof(AsyncImage), new PropertyMetadata(0.0)); public static readonly DependencyProperty LoadingTextProperty = DependencyProperty.Register("LoadingText", typeof(string), typeof(AsyncImage), new PropertyMetadata("Loading")); public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register("IsLoading", typeof(bool), typeof(AsyncImage), new PropertyMetadata(false)); public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(AsyncImage)); public static readonly DependencyProperty UrlSourceProperty = DependencyProperty.Register("UrlSource", typeof(string), typeof(AsyncImage), new PropertyMetadata(string.Empty, new PropertyChangedCallback((s, e) => { var asyncImg = s as AsyncImage; if (asyncImg.LoadEventFlag) { Console.WriteLine("Load By UrlSourceProperty Changed"); asyncImg.Load(); } }))); public static readonly DependencyProperty IsCacheProperty = DependencyProperty.Register("IsCache", typeof(bool), typeof(AsyncImage), new PropertyMetadata(true)); public static readonly DependencyProperty StretchProperty = DependencyProperty.Register("Stretch", typeof(Stretch), typeof(AsyncImage), new PropertyMetadata(Stretch.Uniform)); public static readonly DependencyProperty CacheGroupProperty = DependencyProperty.Register("CacheGroup", typeof(string), typeof(AsyncImage), new PropertyMetadata("AsyncImage_Default")); #endregion #region Property /// /// 本地路径正则 /// private const string LocalRegex = @"^([C-J]):\\([^:&]+\\)*([^:&]+).(jpg|jpeg|png|gif)$"; /// /// 网络路径正则 /// private const string HttpRegex = @"^(https|http):\/\/[^*+@!]+$"; private Image _image; /// /// 是否允许加载图像 /// private bool LoadEventFlag; /// /// 静态图缓存 /// private static IDictionary ImageCacheList; /// /// 动态图缓存 /// private static IDictionary GifImageCacheList; /// /// 动画播放控制类 /// private ImageAnimationController gifController; /// /// 解码宽度 /// public double DecodePixelWidth { get { return (double)GetValue(DecodePixelWidthProperty); } set { SetValue(DecodePixelWidthProperty, value); } } /// /// 异步加载时的文字提醒 /// public string LoadingText { get { return GetValue(LoadingTextProperty) as string; } set { SetValue(LoadingTextProperty, value); } } /// /// 加载状态 /// public bool IsLoading { get { return (bool)GetValue(IsLoadingProperty); } set { SetValue(IsLoadingProperty, value); } } /// /// 图片路径 /// public string UrlSource { get { return GetValue(UrlSourceProperty) as string; } set { SetValue(UrlSourceProperty, value); } } /// /// 图像源 /// public ImageSource ImageSource { get { return GetValue(ImageSourceProperty) as ImageSource; } set { SetValue(ImageSourceProperty, value); } } /// /// 是否启用缓存 /// public bool IsCache { get { return (bool)GetValue(IsCacheProperty); } set { SetValue(IsCacheProperty, value); } } /// /// 图像填充类型 /// public Stretch Stretch { get { return (Stretch)GetValue(StretchProperty); } set { SetValue(StretchProperty, value); } } /// /// 缓存分组标识 /// public string CacheGroup { get { return GetValue(CacheGroupProperty) as string; } set { SetValue(CacheGroupProperty, value); } } #endregion

 

需要注意的是,当UrlSource发生改变时,也许AsyncImage本身并未加载完成,这个时候获取模板中的Image对象是获取不到的,所以要在其PropertyChanged事件中判断一下load状态,已经load过才能触发加载,否则就等待控件的load事件执行之后再加载

public static readonly DependencyProperty UrlSourceProperty = DependencyProperty.Register("UrlSource", typeof(string), typeof(AsyncImage), new PropertyMetadata(string.Empty, new PropertyChangedCallback((s, e) => { var asyncImg = s as AsyncImage; if (asyncImg.LoadEventFlag) //判断控件自身加载状态 { Console.WriteLine("Load By UrlSourceProperty Changed"); asyncImg.Load(); } }))); private void AsyncImage_Loaded(object sender, RoutedEventArgs e) { _image = this.GetTemplateChild("image") as Image; //获取模板中的Image Console.WriteLine("Load By LoadedEvent"); this.Load(); this.LoadEventFlag = true; //设置控件加载状态 } private void Load() { if (_image == null) return; Reset(); var url = this.UrlSource; if (!string.IsNullOrEmpty(url)) { var pixelWidth = (int)this.DecodePixelWidth; var isCache = this.IsCache; var cacheKey = string.Format("{0}_{1}", CacheGroup, url); this.IsLoading = !ImageCacheList.ContainsKey(cacheKey) && !GifImageCacheList.ContainsKey(cacheKey); Task.Factory.StartNew(() => { #region 读取缓存 if (ImageCacheList.ContainsKey(cacheKey)) { this.SetSource(ImageCacheList[cacheKey]); return; } else if (GifImageCacheList.ContainsKey(cacheKey)) { this.Dispatcher.BeginInvoke((Action)delegate { var animation = GifImageCacheList[cacheKey]; PlayGif(animation); }); return; } #endregion #region 解析路径类型 var pathType = ValidatePathType(url); Console.WriteLine(pathType); if (pathType == PathType.Invalid) { Console.WriteLine("invalid path"); return; } #endregion #region 读取图片字节 byte[] imgBytes = null; Stopwatch sw = new Stopwatch(); sw.Start(); if (pathType == PathType.Local) imgBytes = LoadFromLocal(url); else if (pathType == PathType.Http) imgBytes = LoadFromHttp(url); else if (pathType == PathType.Resources) imgBytes = LoadFromApplicationResource(url); sw.Stop(); Console.WriteLine("read time : {0}", sw.ElapsedMilliseconds); if (imgBytes == null) { Console.WriteLine("imgBytes is null,can't load the image"); return; } #endregion #region 读取文件类型 var imgType = GetImageType(imgBytes); if (imgType == ImageType.Invalid) { imgBytes = null; Console.WriteLine("无效的图片文件"); return; } Console.WriteLine(imgType); #endregion #region 加载图像 if (imgType != ImageType.Gif) { //加载静态图像 var imgSource = LoadStaticImage(cacheKey, imgBytes, pixelWidth, isCache); this.SetSource(imgSource); } else { //加载gif图像 this.Dispatcher.BeginInvoke((Action)delegate { var animation = LoadGifImageAnimation(cacheKey, imgBytes, isCache); PlayGif(animation); }); } #endregion }).ContinueWith(r => { this.Dispatcher.BeginInvoke((Action)delegate { this.IsLoading = false; }); }); } }

 

判断路径,判断文件格式,读取图片字节

public enum PathType { Invalid = 0, Local = 1, Http = 2, Resources = 3 } public enum ImageType { Invalid = 0, Gif = 7173, Jpg = 255216, Png = 13780, Bmp = 6677 } /// /// 验证路径类型 /// /// /// private PathType ValidatePathType(string path) { if (path.StartsWith("pack://")) return PathType.Resources; else if (Regex.IsMatch(path, AsyncImage.LocalRegex, RegexOptions.IgnoreCase)) return PathType.Local; else if (Regex.IsMatch(path, AsyncImage.HttpRegex, RegexOptions.IgnoreCase)) return PathType.Http; else return PathType.Invalid; } /// /// 根据文件头判断格式图片 /// /// /// private ImageType GetImageType(byte[] bytes) { var type = ImageType.Invalid; try { var fileHead = Convert.ToInt32($"{bytes[0]}{bytes[1]}"); if (!Enum.IsDefined(typeof(ImageType), fileHead)) { type = ImageType.Invalid; Console.WriteLine($"获取图片类型失败 fileHead:{fileHead}"); } else { type = (ImageType)fileHead; } } catch (Exception ex) { type = ImageType.Invalid; Console.WriteLine($"获取图片类型失败 {ex.Message}"); } return type; } private byte[] LoadFromHttp(string url) { try { using (WebClient wc = new WebClient() { Proxy = null }) { return wc.DownloadData(url); } } catch (Exception ex) { Console.WriteLine("network error:{0} url:{1}", ex.Message, url); } return null; } private byte[] LoadFromLocal(string path) { if (!System.IO.File.Exists(path)) { return null; } try { return System.IO.File.ReadAllBytes(path); } catch (Exception ex) { Console.WriteLine("Read Local Failed : {0}", ex.Message); return null; } } private byte[] LoadFromApplicationResource(string path) { try { StreamResourceInfo streamInfo = Application.GetResourceStream(new Uri(path, UriKind.RelativeOrAbsolute)); if (streamInfo.Stream.CanRead) { using (streamInfo.Stream) { var bytes = new byte[streamInfo.Stream.Length]; streamInfo.Stream.Read(bytes, 0, bytes.Length); return bytes; } } } catch (Exception ex) { Console.WriteLine("Read Resource Failed : {0}", ex.Message); return null; } return null; }

 

加载静态图

/// /// 加载静态图像 /// /// /// /// /// /// private ImageSource LoadStaticImage(string cacheKey, byte[] imgBytes, int pixelWidth, bool isCache) { if (ImageCacheList.ContainsKey(cacheKey)) return ImageCacheList[cacheKey]; var bit = new BitmapImage() { CacheOption = BitmapCacheOption.OnLoad }; bit.BeginInit(); if (pixelWidth != 0) { bit.DecodePixelWidth = pixelWidth; //设置解码大小 } bit.StreamSource = new System.IO.MemoryStream(imgBytes); bit.EndInit(); bit.Freeze(); try { if (isCache && !ImageCacheList.ContainsKey(cacheKey)) ImageCacheList.Add(cacheKey, bit); } catch (Exception ex) { Console.WriteLine(ex.Message); return bit; } return bit; }

 

关于GIF解析

博客园上的周银辉老师也做过Image支持GIF的功能,但我个人认为他的解析GIF部分代码不太友好,由于直接操作文件字节,导致如果阅读者没有研究过gif的文件格式,将晦涩难懂。几经周折我找到github上一个大神写的成熟的WPF播放GIF项目,源码参考 https://github.com/XamlAnimatedGif/WpfAnimatedGif

 解析GIF的核心代码,从图片帧的元数据中使用路径表达式获取当前帧的详细信息 (大小/边距/显示时长/显示方式)

/// /// 解析帧详细信息 /// /// 当前帧 /// private static FrameMetadata GetFrameMetadata(BitmapFrame frame) { var metadata = (BitmapMetadata)frame.Metadata; var delay = TimeSpan.FromMilliseconds(100); var metadataDelay = metadata.GetQueryOrDefault("/grctlext/Delay", 10); //显示时长 if (metadataDelay != 0) delay = TimeSpan.FromMilliseconds(metadataDelay * 10); var disposalMethod = (FrameDisposalMethod)metadata.GetQueryOrDefault("/grctlext/Disposal", 0); //显示方式 var frameMetadata = new FrameMetadata { Left = metadata.GetQueryOrDefault("/imgdesc/Left", 0), Top = metadata.GetQueryOrDefault("/imgdesc/Top", 0), Width = metadata.GetQueryOrDefault("/imgdesc/Width", frame.PixelWidth), Height = metadata.GetQueryOrDefault("/imgdesc/Height", frame.PixelHeight), Delay = delay, DisposalMethod = disposalMethod }; return frameMetadata; }

 

创建WPF动画播放对象

/// /// 加载Gif图像动画 /// /// /// /// /// /// private ObjectAnimationUsingKeyFrames LoadGifImageAnimation(string cacheKey, byte[] imgBytes, bool isCache) { var gifInfo = GifParser.Parse(imgBytes); var animation = new ObjectAnimationUsingKeyFrames(); foreach (var frame in gifInfo.FrameList) { var keyFrame = new DiscreteObjectKeyFrame(frame.Source, frame.Delay); animation.KeyFrames.Add(keyFrame); } animation.Duration = gifInfo.TotalDelay; animation.RepeatBehavior = RepeatBehavior.Forever; //animation.RepeatBehavior = new RepeatBehavior(3); if (isCache && !GifImageCacheList.ContainsKey(cacheKey)) { GifImageCacheList.Add(cacheKey, animation); } return animation; }

 

GIF动画的播放

创建动画控制器ImageAnimationController,使用动画时钟控制器AnimationClock  ,为控制器指定需要作用的控件属性

private readonly Image _image; private readonly ObjectAnimationUsingKeyFrames _animation; private readonly AnimationClock _clock; private readonly ClockController _clockController; public ImageAnimationController(Image image, ObjectAnimationUsingKeyFrames animation, bool autoStart) { _image = image; try { _animation = animation; //_animation.Completed += AnimationCompleted; _clock = _animation.CreateClock(); _clockController = _clock.Controller; _sourceDescriptor.AddValueChanged(image, ImageSourceChanged); // ReSharper disable once PossibleNullReferenceException _clockController.Pause(); //暂停动画 _image.ApplyAnimationClock(Image.SourceProperty, _clock); //将动画作用于该控件的指定属性 if (autoStart) _clockController.Resume(); //播放动画 } catch (Exception) { } }

 

定义外观

 

调用示例

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有