在做图片相关的应用的时候,经常需要用大图片的缓存,默认的Image控件不支持缓存的支持,本文自定义一个支持图片缓存的控件

  当图片的地址是网络图片时候

    根据Url判断该图片是否存在本地,如果存在,则直接从本地读取,如果不存在,则通过Http请求下载该图片,保存到本地,然后读取到Image控件中

  当图片为本地地址的时候,直接从本地读取,设置到Image控件中

1、在定义可缓存图片控件之前,先封装一下文件存储的帮助类

using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Windows.ApplicationModel;
using Windows.Storage;
using Newtonsoft.Json;
using XTuOne.Common.Helpers; namespace XTuOne.Utility.Helpers
{
public class StorageHelper : IStorageHelper
{
#region 单例 public static IStorageHelper Instance { get; private set; } public static object LockObject; static StorageHelper()
{
Instance = new StorageHelper();
LockObject = new object();
} private StorageHelper()
{
} #endregion #region 同步读写方法 public Stream ReadFile(string filePath)
{
lock (LockObject)
{
using (var sf = IsolatedStorageFile.GetUserStoreForApplication())
{
if (!sf.FileExists(filePath))
{
throw new FileNotFoundException(string.Format("没有找到文件:{0}", filePath));
} using (var fs = sf.OpenFile(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
var stream = new MemoryStream();
fs.CopyTo(stream); stream.Seek(, SeekOrigin.Begin);
return stream;
}
}
}
} public string CreateFile(Stream stream, string filePath, bool replace = false)
{
lock (LockObject)
{
using (var sf = IsolatedStorageFile.GetUserStoreForApplication())
{
var directory = Path.GetDirectoryName(filePath);
if (directory != null && !sf.DirectoryExists(directory))
{
//如果目录不存在,则创建
sf.CreateDirectory(directory);
} if (FileExist(filePath))
{
if (!replace)
{
return filePath;
}
sf.DeleteFile(filePath);
}
//如果不存在或者存在且替换
using (var fs = sf.CreateFile(filePath))
{
stream.CopyTo(fs);
}
}
}
return filePath;
} public string CreateFile(byte[] data, string filePath, bool replace = false)
{
lock (LockObject)
{
using (var sf = IsolatedStorageFile.GetUserStoreForApplication())
{
var directory = Path.GetDirectoryName(filePath);
if (directory != null && !sf.DirectoryExists(directory))
{
//如果目录不存在,则创建
sf.CreateDirectory(directory);
} if (FileExist(filePath))
{
if (!replace)
{
return filePath;
}
sf.DeleteFile(filePath);
}
//如果不存在或者存在且替换
using (var fs = new IsolatedStorageFileStream(filePath, FileMode.OpenOrCreate, sf))
{
fs.Write(data, , data.Length);
}
}
}
return filePath;
} public string ReadAllText(string fileName)
{
using (var reader = new StreamReader(ReadFile(fileName)))
{
return reader.ReadToEnd();
}
} public string WriteAllText(string fileName, string text, bool replace)
{
return CreateFile(Encoding.UTF8.GetBytes(text), fileName, replace);
} #endregion #region 异步读写方法 public async Task<Stream> ReadFileAsync(string filePath)
{
var storageFile = await GetStorageFileAsync(filePath);
return await storageFile.OpenStreamForReadAsync();
} public async Task<string> CreateFileAsync(Stream stream, string filePath, bool replace = false)
{
var storageFile = await GetStorageFileAsync(filePath);
if (storageFile != null)
{
if (FileExist(filePath))
{
if (replace)
{
//替换先删除
await storageFile.DeleteAsync(StorageDeleteOption.PermanentDelete);
}
else
{
return filePath;
}
} storageFile = await GetStorageFileAsync(filePath);
var destStream = await storageFile.OpenStreamForWriteAsync();
await stream.CopyToAsync(destStream);
}
return filePath;
} public async Task<string> CreateFileAsync(byte[] data, string filePath, bool replace = false)
{
var storageFile = await GetStorageFileAsync(filePath);
if (storageFile != null)
{
if (FileExist(filePath))
{
if (replace)
{
//替换先删除
await storageFile.DeleteAsync(StorageDeleteOption.PermanentDelete);
}
else
{
return filePath;
}
} storageFile = await GetStorageFileAsync(filePath);
var destStream = await storageFile.OpenStreamForWriteAsync();
await destStream.WriteAsync(data, , data.Length);
}
return filePath;
} public async Task<string> ReadAllTextAsync(string fileName)
{
using (var reader = new StreamReader(await ReadFileAsync(fileName)))
{
return await reader.ReadToEndAsync();
}
} public async Task<string> WriteAllTextAsync(string fileName, string text, bool replace)
{
return await CreateFileAsync(Encoding.UTF8.GetBytes(text), fileName, replace);
} #endregion #region 普通方法:判断文件(文件夹)存在,创建(删除)文件夹,获取文件(文件夹) public bool FileExist(string fileName)
{
using (var sf = IsolatedStorageFile.GetUserStoreForApplication())
{
return sf.FileExists(fileName);
}
} public bool DirectoryExist(string directory)
{
using (var sf = IsolatedStorageFile.GetUserStoreForApplication())
{
return sf.DirectoryExists(directory);
}
} public void DeleteFile(string fileName)
{
using (var sf = IsolatedStorageFile.GetUserStoreForApplication())
{
if (sf.FileExists(fileName))
{
sf.DeleteFile(fileName);
}
}
} public void CreateDirectory(string directory)
{
using (var sf = IsolatedStorageFile.GetUserStoreForApplication())
{
if (sf.DirectoryExists(directory))
{
sf.DeleteDirectory(directory);
}
} } public void DeleteDirectory(string directory, bool isDeleteAll)
{
using (var sf = IsolatedStorageFile.GetUserStoreForApplication())
{
if (sf.DirectoryExists(directory))
{
if (isDeleteAll)
{
var files = GetFiles(directory);
foreach (var file in files)
{
DeleteFile(file);
} var directories = GetDirectories(directory);
foreach (var s in directories)
{
DeleteDirectory(s, true);
}
}
sf.DeleteDirectory(directory);
}
}
} public string[] GetFiles(string directory)
{
using (var sf = IsolatedStorageFile.GetUserStoreForApplication())
{
return sf.GetFileNames(directory);
}
} /// <summary>
/// 获取本地文件夹中的文件
/// </summary>
public string[] GetDirectories(string directory)
{
using (var sf = IsolatedStorageFile.GetUserStoreForApplication())
{
return sf.GetDirectoryNames(directory);
}
} #endregion #region 拷贝文件(从安装包到本地) /// <summary>
/// 从安装包拷贝文件到本地
/// </summary>
public async Task CopyPackageFileToLocalAsync(string source, string target = null, bool replace = false)
{
using (var stream = GetResourceStream(source))
{
await CreateFileAsync(stream, target ?? source, replace);
}
} /// <summary>
/// 从安装包拷贝路径到本地
/// </summary>
public async Task CopyPackageFolderToLocalAsync(string source, string target = null, bool replace = false)
{
target = target ?? source; var packagePath = Package.Current.InstalledLocation;
var folder = await GetStorageFolderAsync(packagePath, source); //拷贝文件
var files = await folder.GetFilesAsync();
foreach (var storageFile in files)
{
var fileName = storageFile.Name;
using (var stream = await storageFile.OpenStreamForReadAsync())
{
await CreateFileAsync(stream, target + fileName, replace);
}
} //拷贝子文件夹(递归)
var folders = await folder.GetFoldersAsync();
foreach (var storageFolder in folders)
{
await
CopyPackageFolderToLocalAsync(source + storageFolder.Name + "/", target + storageFolder.Name + "/",
replace);
}
} #endregion #region 从安装包(安装路径)中读取(同步) public Stream GetResourceStream(string file)
{
//引用安装路径的文件的时候不以'/'开头
file = file.TrimStart('/');
return Application.GetResourceStream(new Uri(file, UriKind.Relative)).Stream;
} #endregion #region 序列化 public void Serialize<T>(string fileName, T obj, bool replace)
{
var json = JsonConvert.SerializeObject(obj);
WriteAllText(fileName, json, replace);
} T IStorageHelper.DeSerialize<T>(string fileName)
{
var json = ReadAllText(fileName);
return JsonConvert.DeserializeObject<T>(json);
} public async Task SerializeAsync<T>(string fileName, T obj, bool replace)
{
var json = JsonConvert.SerializeObject(obj);
await WriteAllTextAsync(fileName, json, replace);
} public async Task<T> DeSerializeAsync<T>(string fileName)
{
var json = await ReadAllTextAsync(fileName);
return JsonConvert.DeserializeObject<T>(json);
} #endregion #region 辅助方法 /// <summary>
/// 根据路劲获取StorageFolder
/// </summary>
private async Task<StorageFolder> GetStorageFolderAsync(StorageFolder folder, string directory)
{
var directories = directory.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); foreach (var s in directories)
{
folder = await folder.CreateFolderAsync(s, CreationCollisionOption.OpenIfExists);
}
return folder;
} /// <summary>
/// 根据文件名异步获取本地文件夹StorageFolder(如果路径不存在,则创建路径)
/// </summary>
private async static Task<StorageFolder> GetStorageFolderAsync(string filePath)
{
var localFolder = ApplicationData.Current.LocalFolder;
var directory = Path.GetDirectoryName(filePath); if (!string.IsNullOrEmpty(directory))
{
var directories = directory.Split(new[] {'\\', '/'}, StringSplitOptions.RemoveEmptyEntries); foreach (var s in directories)
{
localFolder = await localFolder.CreateFolderAsync(s, CreationCollisionOption.OpenIfExists);
}
}
return localFolder;
} /// <summary>
/// 根据路径得到StoreageFile
/// </summary>
private async static Task<StorageFile> GetStorageFileAsync(string filePath)
{
var folder = await GetStorageFolderAsync(filePath);
var fileName = Path.GetFileName(filePath);
if (fileName != null)
{
return await folder.CreateFileAsync(fileName, CreationCollisionOption.OpenIfExists);
}
return null;
} #endregion
}
}

图片的写入和读取都使用了线程锁,在最后说明

注意:上面的异步方法是线程不安全的,在多线程的情况下,当文件被一个线程写入的时候,另一个线程调用读的方法会抛出异常 Access Deny,访问被阻止

实现了StorageHelper,下面是CacheableImage的实现,支持占位图片,加载失败图片,配置保存路径

2、自定义可缓存图片控件的实现

<UserControl x:Class="XTuOne.Controls.CacheableImage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="480" d:DesignWidth="480"> <Grid x:Name="LayoutRoot">
<Image x:Name="Image" Stretch="Fill"></Image>
</Grid>
</UserControl>

CacheableImage.xaml

using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using XTuOne.Utility.Helpers; namespace XTuOne.Controls
{
/// <summary>
/// 支持本地缓存的图片空间
/// </summary>
public partial class CacheableImage
{
public CacheableImage()
{
InitializeComponent();
} public static readonly DependencyProperty CachedDirectoryProperty = DependencyProperty.Register(
"CachedDirectory", typeof (string), typeof (CacheableImage), new PropertyMetadata("/ImageCached/")); public static readonly DependencyProperty FaildImageUrlProperty = DependencyProperty.Register(
"FaildImageUrl", typeof(Uri), typeof(CacheableImage), new PropertyMetadata(default(string))); public static readonly DependencyProperty LoadingImageUrlProperty = DependencyProperty.Register(
"LoadingImageUrl", typeof(Uri), typeof(CacheableImage), new PropertyMetadata(default(string))); public static readonly DependencyProperty StretchProperty = DependencyProperty.Register(
"Stretch", typeof (Stretch), typeof (CacheableImage), new PropertyMetadata(default(Stretch), StretchPropertyChangedCallback)); private static void StretchPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var cachedImage = (CacheableImage)dependencyObject;
var stretch = (Stretch)dependencyPropertyChangedEventArgs.NewValue;
if (cachedImage.Image != null)
{
cachedImage.Image.Stretch = stretch;
}
} public Stretch Stretch
{
get { return (Stretch) GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
} /// <summary>
/// 加载失败的图片
/// </summary>
public Uri FaildImageUrl
{
get { return (Uri)GetValue(FaildImageUrlProperty); }
set { SetValue(FaildImageUrlProperty, value); }
} /// <summary>
/// 加载中显示的图片(需要进行网络请求时)
/// </summary>
public Uri LoadingImageUrl
{
get { return (Uri)GetValue(LoadingImageUrlProperty); }
set { SetValue(LoadingImageUrlProperty, value); }
} /// <summary>
/// 缓存到本地的目录
/// </summary>
public string CachedDirectory
{
get { return (string) GetValue(CachedDirectoryProperty); }
set { SetValue(CachedDirectoryProperty, value); }
} public static readonly DependencyProperty ImageUrlProperty = DependencyProperty.Register(
"ImageUrl", typeof (string), typeof (CacheableImage), new PropertyMetadata(default(string), ImageUrlPropertyChangedCallback)); public string ImageUrl
{
get { return (string)GetValue(ImageUrlProperty); }
set { SetValue(ImageUrlProperty, value); }
} private static async void ImageUrlPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var cachedImage = (CacheableImage) dependencyObject;
var imageUrl = (string)dependencyPropertyChangedEventArgs.NewValue; if (string.IsNullOrEmpty(imageUrl))
{
return;
} if (imageUrl.StartsWith("http://") || imageUrl.Equals("https://"))
{
var fileName = cachedImage.CachedDirectory + Uri.EscapeDataString(imageUrl);
//网络图片,判断是否存在
if (!StorageHelper.Instance.FileExist(fileName))
{
try
{
if (cachedImage.LoadingImageUrl != null)
{
cachedImage.Image.Source =
new BitmapImage(cachedImage.LoadingImageUrl);
} //请求
var request = WebRequest.CreateHttp(imageUrl);
request.AllowReadStreamBuffering = true;
var response = await request.GetResponseAsync();
var stream = response.GetResponseStream();
await Task.Delay(); //保存到本地
StorageHelper.Instance.CreateFile(stream, fileName);
}
catch (Exception e)
{
//请求失败
if (cachedImage.FaildImageUrl != null)
{
cachedImage.Image.Source = new BitmapImage(cachedImage.FaildImageUrl);
}
return;
}
}
//读取图片文件
var imageStream = StorageHelper.Instance.ReadFile(fileName);
var bitmapImage = new BitmapImage();
bitmapImage.SetSource(imageStream);
cachedImage.Image.Source = bitmapImage;
}
else
{
//本地图片
var bitmapImage = new BitmapImage(new Uri(imageUrl, UriKind.Relative));
cachedImage.Image.Source = bitmapImage;
}
} public static readonly DependencyProperty ImageStreamProperty = DependencyProperty.Register(
"ImageStream", typeof (Stream), typeof (CacheableImage), new PropertyMetadata(default(Stream), ImageStreamPropertyChangedCallback)); private static void ImageStreamPropertyChangedCallback(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var cachedImage = (CacheableImage) dependencyObject;
var imageStream = (Stream) dependencyPropertyChangedEventArgs.NewValue; var bitmapImage = new BitmapImage();
bitmapImage.SetSource(imageStream);
cachedImage.Image.Source = bitmapImage;
} /// <summary>
/// 支持直接传递流进来
/// </summary>
public Stream ImageStream
{
get { return (Stream) GetValue(ImageStreamProperty); }
set { SetValue(ImageStreamProperty, value); }
}
}
}

为了保证线程安全,这里图片的保存没有用到异步,因为如果有很多图片进行请求的时候,可能会线程请求异常,像上面说的情况

上面的StorageHelper在读取和写入的时候都加了线程锁(其实不应该在读取文件的时候加锁的),是为了保证,在写入的过程中,读取文件出现无权访问的问题

  暂时没有找到方法支持线程安全,如果你有更好的方案,可以给我留言

  

【WP8】图片缓存控件的更多相关文章

  1. 【WPF】【UWP】借鉴 asp.net core 管道处理模型打造图片缓存控件 ImageEx

    在 Web 开发中,img 标签用来呈现图片,而且一般来说,浏览器是会对这些图片进行缓存的. 比如访问百度,我们可以发现,图片.脚本这种都是从缓存(内存缓存/磁盘缓存)中加载的,而不是再去访问一次百度 ...

  2. UI-UIImageView的图片填充方式(contentMode)_图片作为控件背景图的拉伸方式(stretch)介绍

    常用图片填充方式 这里只介绍三个最常用的图片填充方式 UIViewContentModeScaleToFill模式会导致图片变形.例如: UIViewContentModeScaleAspectFit ...

  3. 我写的一个 Qt 显示图片的控件

    Qt 中没有专门显示图片的控件.通常我们会使用QLabel来显示图片.可是QLabel 显示图片的能力还是有点弱.比方不支持图像的缩放一类的功能.使用起来不是非常方便. 因此我就自己写了个简单的类. ...

  4. Android开发技巧——定制仿微信图片裁剪控件

    拍照--裁剪,或者是选择图片--裁剪,是我们设置头像或上传图片时经常需要的一组操作.上篇讲了Camera的使用,这篇讲一下我对图片裁剪的实现. 背景 下面的需求都来自产品. 裁剪图片要像微信那样,拖动 ...

  5. MFC入门(三)-- MFC图片/文字控件(循环显示文字和图片的小程序)

    惯例附上前几个博客的链接: MFC入门(一)简单配置:http://blog.csdn.net/zmdsjtu/article/details/52311107 MFC入门(二)读取输入字符:http ...

  6. 关于IOS某图片添加控件,图片从相册或拍照保存后,再次进入时点击放大图无法显示的问题

    某图片添加控件: https://github.com/XZTLLQ/LQPhotoPickerDemo 问题: 标题已说明 代码块: NSArray *alAssetUrl =(NSMutableA ...

  7. 图片剪裁控件——ClipImageView

    这段时间在做自己的项目时,须要使用到图片剪裁功能,当时大概的思考了一些需求.想到了比較简单的实现方法.因此就抽了点时间做了这个图片剪裁控件--ClipImageView 这里先贴上ClipImageV ...

  8. Android实现图片滚动控件,含页签功能,让你的应用像淘宝一样炫起来

    首先题外话,今天早上起床的时候,手滑一下把我的手机甩了出去,结果陪伴我两年半的摩托罗拉里程碑一代就这么安息了,于是我今天决定怒更一记,纪念我死去的爱机. 如果你是网购达人,你的手机上一定少不了淘宝客户 ...

  9. 用MVVM模式开发中遇到的零散问题总结(5)——将动态加载的可视元素保存为图片的控件,Binding刷新的时机

    原文:用MVVM模式开发中遇到的零散问题总结(5)--将动态加载的可视元素保存为图片的控件,Binding刷新的时机 在项目开发中经常会遇到这样一种情况,就是需要将用户填写的信息排版到一张表单中,供打 ...

随机推荐

  1. mybatis批量更新的两种实现方式

    mapper.xml文件,后台传入一个对象集合,另外如果是mysql数据库,一点在配置文件上加上&allowMultiQueries=true,这样才可以执行多条sql,以下为mysql: & ...

  2. [转]详解Oracle高级分组函数(ROLLUP, CUBE, GROUPING SETS)

    原文地址:http://blog.csdn.net/u014558001/article/details/42387929 本文主要讲解 ROLLUP, CUBE, GROUPING SETS的主要用 ...

  3. Spring框架中InitializingBean执行顺序

    本文转自:https://www.cnblogs.com/yql1986/p/4084888.html package org.test.InitializingBean; 2 3 import or ...

  4. JS自动关闭授权弹窗,并刷新父页面

    echo "<script>window.opener.location.href='index.php'; window.close();</script>&quo ...

  5. 【微信小程序】scroll-view与Page下拉冲突

    需求:主界面是个列表.列表可以纵向滑动,下拉添加新的条目Item.每个条目Item可以横向滑动. 发现做下拉时,用Page的enablePullDownRefresh和scroll-view条目的横向 ...

  6. Maven MyEclipse创建web项目没有src/maim/java

     转载:http://blog.csdn.net/nich002/article/details/43273219 maven项目 错误: 找不到或无法加载主类 分类: java2015-01-29 ...

  7. SpringBoot2 JPA No Identifier specified for entity的解决办法

    No Identifier specified for entity的错误 此类注解都在 import javax.persistence.*;包下     @Id     @GeneratedVal ...

  8. IT系统

      去年11月11日,也就是我们俗称的“双十一”当天,淘宝集市.淘宝商城天猫联手创造了交易额达191亿的销售神话.然而,即便是这种神话也还不足以成为留传至今的佳话,其中最为重要的原因就是支撑电子商务的 ...

  9. Java 应该被记住的8位大人物

    这里列举了 8 个 Java 人物,他们创建了对 Java 社区贡献很大的框架.产品.工具和图书,也因此改变了 Java 的编码方法. 8. Tomcat 和 Ant 创办人 James Duncan ...

  10. JS Window对象操作思维导图