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<string, ImageSource>(); //初始化静态图缓存字典
GifImageCacheList = new ConcurrentDictionary<string, ObjectAnimationUsingKeyFrames>(); //初始化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
/// <summary>
/// 本地路径正则
/// </summary>
private const string LocalRegex = @"^([C-J]):\\([^:&]+\\)*([^:&]+).(jpg|jpeg|png|gif)$"; /// <summary>
/// 网络路径正则
/// </summary>
private const string HttpRegex = @"^(https|http):\/\/[^*+@!]+$"; private Image _image; /// <summary>
/// 是否允许加载图像
/// </summary>
private bool LoadEventFlag; /// <summary>
/// 静态图缓存
/// </summary>
private static IDictionary<string, ImageSource> ImageCacheList; /// <summary>
/// 动态图缓存
/// </summary>
private static IDictionary<string, ObjectAnimationUsingKeyFrames> GifImageCacheList; /// <summary>
/// 动画播放控制类
/// </summary>
private ImageAnimationController gifController; /// <summary>
/// 解码宽度
/// </summary>
public double DecodePixelWidth
{
get { return (double)GetValue(DecodePixelWidthProperty); }
set { SetValue(DecodePixelWidthProperty, value); }
} /// <summary>
/// 异步加载时的文字提醒
/// </summary>
public string LoadingText
{
get { return GetValue(LoadingTextProperty) as string; }
set { SetValue(LoadingTextProperty, value); }
} /// <summary>
/// 加载状态
/// </summary>
public bool IsLoading
{
get { return (bool)GetValue(IsLoadingProperty); }
set { SetValue(IsLoadingProperty, value); }
} /// <summary>
/// 图片路径
/// </summary>
public string UrlSource
{
get { return GetValue(UrlSourceProperty) as string; }
set { SetValue(UrlSourceProperty, value); }
} /// <summary>
/// 图像源
/// </summary>
public ImageSource ImageSource
{
get { return GetValue(ImageSourceProperty) as ImageSource; }
set { SetValue(ImageSourceProperty, value); }
} /// <summary>
/// 是否启用缓存
/// </summary> public bool IsCache
{
get { return (bool)GetValue(IsCacheProperty); }
set { SetValue(IsCacheProperty, value); }
} /// <summary>
/// 图像填充类型
/// </summary>
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
} /// <summary>
/// 缓存分组标识
/// </summary>
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 = , Local = , Http = , Resources =
} public enum ImageType
{
Invalid = , Gif = , Jpg = , Png = , Bmp =
} /// <summary>
/// 验证路径类型
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
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;
} /// <summary>
/// 根据文件头判断格式图片
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
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, , bytes.Length);
return bytes;
}
}
}
catch (Exception ex)
{
Console.WriteLine("Read Resource Failed : {0}", ex.Message);
return null;
}
return null;
}

加载静态图

        /// <summary>
/// 加载静态图像
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="imgBytes"></param>
/// <param name="pixelWidth"></param>
/// <param name="isCache"></param>
/// <returns></returns>
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 != )
{
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的核心代码,从图片帧的元数据中使用路径表达式获取当前帧的详细信息 (大小/边距/显示时长/显示方式)

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

创建WPF动画播放对象

        /// <summary>
/// 加载Gif图像动画
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="imgBytes"></param>
/// <param name="pixelWidth"></param>
/// <param name="isCache"></param>
/// <returns></returns>
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)
{ } }

定义外观

<Style TargetType="{x:Type local:AsyncImage}">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:AsyncImage}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
<Grid>
<Image x:Name="image"
Stretch="{TemplateBinding Stretch}"
RenderOptions.BitmapScalingMode="HighQuality"/>
<TextBlock Text="{TemplateBinding LoadingText}"
FontSize="{TemplateBinding FontSize}"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
x:Name="txtLoading"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsLoading" Value="False">
<Setter Property="Visibility" Value="Collapsed" TargetName="txtLoading"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

调用示例

   <local:AsyncImage UrlSource="{Binding Url}"/>
<local:AsyncImage UrlSource="{Binding Url}" IsCache="False"/>
<local:AsyncImage UrlSource="{Binding Url}" DecodePixelWidth="" />
<local:AsyncImage UrlSource="{Binding Url}" LoadingText="正在加载图像请稍后"/>

WPF自定义控件之图片控件 AsyncImage的更多相关文章

  1. 解决WPF两个图片控件显示相同图片因线程占用,其中一个显示不全的问题

    在做项目的过程中遇到这样一个问题,下面提出一种解决方法,主要思想是图片的Copy,如还有其他方法,欢迎交流. 在前端图片控件绑定显示时,使用转换器进行转义绑定   (1)转换器: public cla ...

  2. WPF自定义控件二:Border控件与TextBlock控件轮播动画

    需求:实现Border轮播动画与TextBlock动画 XAML代码如下: <Window.Resources> <Storyboard x:Key="OnLoaded1& ...

  3. WPF 后台C#设置控件背景图片

    原文:WPF 后台C#设置控件背景图片 以前的程序中有做过,当时只是记得uri很长一大段就没怎么记.今天有人问了也就写下来.   这是一个Button,设置了Background后的效果. 前台的设置 ...

  4. 【WPF/C#】拖拽Image图片控件

    需求:使得Image图片控件能够被拖动. 思路:关键是重写Image控件的几个鼠标事件,实现控制. 前台: <Image Source="C:\Users\Administrator\ ...

  5. WPF后台设置xaml控件的样式System.Windows.Style

    WPF后台设置xaml控件的样式System.Windows.Style 摘-自 :感谢 作者: IT小兵   http://3w.suchso.com/projecteac-tual/wpf-zhi ...

  6. WPF中的image控件的Source赋值

    WPF中的Image控件Source的设置 1.XAML中 简单的方式(Source="haha.png"); image控件的Source设置为相对路径后(Source=&quo ...

  7. Android自定义控件1--自定义控件介绍

    Android控件基本介绍 Android本身提供了很多控件比如我们常用的有文本控件TextView和EditText:按钮控件Button和ImageButton状态开关按钮ToggleButton ...

  8. WPF Step By Step 控件介绍

    WPF Step By Step 控件介绍 回顾 上一篇,我们主要讨论了WPF的几个重点的基本知识的介绍,本篇,我们将会简单的介绍几个基本控件的简单用法,本文会举几个项目中的具体的例子,结合这些 例子 ...

  9. WPF编程,将控件所呈现的内容保存成图像的一种方法。

    原文:WPF编程,将控件所呈现的内容保存成图像的一种方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/article/detai ...

随机推荐

  1. 【洛谷5794】[THUSC2015] 解密运算(模拟)

    点此看题面 大致题意: 对于一个字符串,我们在其末尾添加一个'.',将字符串视作一个环,则可以从\(n+1\)个位置断开得到\(n+1\)个新串.现将这\(n+1\)个新串按字典序排序('.'的字典序 ...

  2. MySql索引背后的数据结构及算法

    本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree ...

  3. tensorflow convert_variables_to_constants

    在使用tf.train.Saver函数保存模型文件的时候,是保存所有的参数信息,而有些时候我们并不需要所有的参数信息.我们只需要知道神经网络的输入层经过前向传播计算得到输出层即可,所以在保存的时候,我 ...

  4. Chrome远程调试手机端UC浏览器

    今天在手机UC上发现我的一个网页打不开,而在PC上是正常的,因此需要通过Chrome远程调试手机端UC浏览器查下问题,折腾了老久才弄好. 获取 Google USB 驱动程序 首先将手机通过USB接口 ...

  5. ASP.NET MVC教程一:ASP.NET MVC简介

    一.MVC模式简介 MVC模式是一种流行的Web应用架构技术,它被命名为模型-视图-控制器(Model-View-Controller).在分离应用程序内部的关注点方面,MVC是一种强大而简洁的方式, ...

  6. SAP 表汇总

    SAP 表整理:VBKPF-预制凭证抬头表: VBKPF-预制凭证抬头表 VBKPF-预制凭证抬头表 VBSEG-预制凭证行项目表: VBSEG-预制凭证行项目表 VBSEG-预制凭证行项目表 VBS ...

  7. 关于HACLON程序导出C#程序,运行报错解决方法

    摘要:一些环境配置异常的解决方法. 一,打不开相机: 1.打开系统高级设置--环境变量中是否有 HALCONROOT+安装目录名,若无进行添加. 2.关闭计算机其他连接相机的软件,例如海康的MVS,H ...

  8. ASP.NET MVC IOC依赖注入之Autofac系列(一)- MVC当中应用

    话不多说,直入主题看我们的解决方案结构: 分别对上面的工程进行简单的说明: 1.TianYa.DotNetShare.Model:为demo的实体层 2.TianYa.DotNetShare.Repo ...

  9. ASP.Net 连接多个数据库之间的切换

    本次两个的两个数据是SQL Server 和ORCAL 首先在Web.congfig中 <connectionStrings> </connectionStrings>里面添加 ...

  10. 甲方安全之安卓App第三方加固对比

    前段时间公司要给 Android 应用进行加固,由笔者来选一家加固产品.然后发现,加固产品何其之多,且鱼龙混杂.各种问题也是层出不穷,比如,有些加固时间非常久.有些加固会失败.有些运行会崩溃等等问题. ...