在做图片相关的应用的时候,经常需要用大图片的缓存,默认的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. 遍历目录下的所有文件-os.walk

    #coding:utf-8 import os for root,dirs,files in os.walk("D:"): for fileItem in files: print ...

  2. 1. K近邻算法(KNN)

    1. K近邻算法(KNN) 2. KNN和KdTree算法实现 1. 前言 K近邻法(k-nearest neighbors,KNN)是一种很基本的机器学习方法了,在我们平常的生活中也会不自主的应用, ...

  3. 结合order by 解CTF某题

    真tmd不容易 <?php error_reporting(0); if (!isset($_POST['uname']) || !isset($_POST['pwd'])) { echo '& ...

  4. linux下mysql重置密码并且远程可以访问

    .重置mysql密码: 杀死所有的yum myql进程: pkill mysql; 查看端口 netstat 端口号杀死) 修改my.cnf文件,在[mysqld]下加入skip-grant-tabl ...

  5. 快速排序,一个爱情故事-java版

    public static void myquicksort(int[] ages,int girl,int boy){ //这是一个站在数组两端,追求完美爱情的故事 //年龄不匹配的不要 //第0步 ...

  6. python 文件 IO 操作

    Python 的底层操作 * 其实Python的文件IO操作方法,和Linux底层的差不多 打开 f = open(filename , "r") 后面的 "r" ...

  7. BMP位图文件格式详解及编程建议

    BMP文件渊源流长,虽然对JPG.PNG等格式图像文件来说,确实有点土,但是毕竟BMP文件格式相对简单,容易理解,至于BMP众多的位图格式也不能责怪微软,主要是早期谁也没料到图片技术会发展的这么快,而 ...

  8. ubuntu linux下建立stm32开发环境: GCC安装以及工程Makefile建立

    http://blog.csdn.net/embbnux/article/details/17616809

  9. Hadoop与Spark之间的比较

    Hadoop与Spark之间的比较 Hadoop框架的主要模块包括如下: Hadoop Common Hadoop分布式文件系统(HDFS) Hadoop YARN Hadoop MapReduce ...

  10. http免费升级https 攻略(超简单)

    1.注册沃通SSL免费证书 https://buy.wosign.com/FreeSSL.html 2.验证邮件域名,并下载证书 3.打开IIS,找到服务器证书选择导入,选择下载下来的证书 4.设置网 ...