【发布时间】:2010-12-25 02:01:54
【问题描述】:
我们正在开发一个 WPF 桌面应用程序,它显示当前通过 HTTP 获取的图像。
图像已经针对质量/尺寸进行了优化,但每次获取图像时都会有明显的等待。
有没有办法在客户端缓存图片,以免每次都下载?
【问题讨论】:
我们正在开发一个 WPF 桌面应用程序,它显示当前通过 HTTP 获取的图像。
图像已经针对质量/尺寸进行了优化,但每次获取图像时都会有明显的等待。
有没有办法在客户端缓存图片,以免每次都下载?
【问题讨论】:
这个 cachedImage 效果很好,但是..
关于我如何为 ImageBrush 使用 ImageSource 的相同缓存图像类型功能的任何建议?
<Rectangle
Width="32"
Height="32"
Margin="2,1"
RadiusX="16"
RadiusY="16"
RenderOptions.BitmapScalingMode="HighQuality">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding Image}" />
</Rectangle.Fill>
</Rectangle>
请注意,可能有更好的方法来制作圆形图像(例如个人资料图像)
【讨论】:
我知道这个问题已经很老了,但是我最近不得不在 WPF 应用程序中使用缓存,并发现通过设置UriCachePolicy 在带有 BitmapImage 的 .Net 3.5 中有一个更好的选择,它将使用系统级缓存:
<Image.Source>
<BitmapImage UriCachePolicy="Revalidate"
UriSource="https://farm3.staticflickr.com/2345/2077570455_03891081db.jpg"/>
</Image.Source>
您甚至可以在 app.config 中设置该值,以使您的所有应用都使用默认值进行缓存:
<system.net>
<requestCaching defaultPolicyLevel="CacheIfAvailable"/>
</system.net>
您将在此处找到对 RequestCacheLevel 值的说明:http://msdn.microsoft.com/en-us/library/system.net.cache.requestcachelevel(v=vs.110).aspx
此功能理解 HTTP/1.1 标头,因此如果您设置 Revalidate 它使用 If-Modified-Since 标头以避免每次都下载它,但仍会检查图像是否已更改,因此您始终拥有正确的。
【讨论】:
对于通过 Google 来到这里的人,我已将 Simon Hartcher 发布的原始实现打包,由 Jeroen van Langen 重构(以及 Ivan Leonenko 的调整以使其可绑定),进入开源 NuGet 包。
请在此处找到详细信息 - http://floydpink.github.io/CachedImage/
【讨论】:
我通过使用IValueConverter 接口创建绑定转换器解决了这个问题。考虑到我至少花了一周的时间试图找到一个可靠的解决方案,我想我应该与将来有这个问题的人分享我的解决方案。
【讨论】:
基于此,我制作了自定义控件:
I made a blog post:,这是代码:
public class CachedImage : Image
{
static CachedImage()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CachedImage), new FrameworkPropertyMetadata(typeof(CachedImage)));
}
public readonly static DependencyProperty ImageUrlProperty = DependencyProperty.Register("ImageUrl", typeof(string), typeof(CachedImage), new PropertyMetadata("", ImageUrlPropertyChanged));
public string ImageUrl
{
get
{
return (string)GetValue(ImageUrlProperty);
}
set
{
SetValue(ImageUrlProperty, value);
}
}
private static readonly object SafeCopy = new object();
private static void ImageUrlPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var url = (String)e.NewValue;
if (String.IsNullOrEmpty(url))
return;
var uri = new Uri(url);
var localFile = String.Format(Path.Combine(Globals.CacheFolder, uri.Segments[uri.Segments.Length - 1]));
var tempFile = String.Format(Path.Combine(Globals.CacheFolder, Guid.NewGuid().ToString()));
if (File.Exists(localFile))
{
SetSource((CachedImage)obj, localFile);
}
else
{
var webClient = new WebClient();
webClient.DownloadFileCompleted += (sender, args) =>
{
if (args.Error != null)
{
File.Delete(tempFile);
return;
}
if (File.Exists(localFile))
return;
lock (SafeCopy)
{
File.Move(tempFile, localFile);
}
SetSource((CachedImage)obj, localFile);
};
webClient.DownloadFileAsync(uri, tempFile);
}
}
private static void SetSource(Image inst, String path)
{
inst.Source = new BitmapImage(new Uri(path));
}
}
现在你可以绑定它了:
<Cache:CachedImage ImageUrl="{Binding Icon}"/>
【讨论】:
只是 Jeroen van Langen 回复的更新,
你可以节省一堆线
删除 HttpHelper 类和 WebResponse_extension
替换
HttpHelper.GetAndSaveToFile(url, localFile);
通过
WebClient webClient = new WebClient();
webClient.DownloadFile(url, localFile);
【讨论】:
我读过你的博客,这让我想到了这个(我认为更容易)概念设置:
您会注意到,我重复使用了您分享的一些代码,所以我会分享我的。
创建一个名为 CachedImage 的新自定义控件。
public class CachedImage : Image
{
private string _imageUrl;
static CachedImage()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CachedImage), new FrameworkPropertyMetadata(typeof(CachedImage)));
}
public string ImageUrl
{
get
{
return _imageUrl;
}
set
{
if (value != _imageUrl)
{
Source = new BitmapImage(new Uri(FileCache.FromUrl(value)));
_imageUrl = value;
}
}
}
}
接下来我创建了一个 FileCache 类(所以我不仅可以控制图像,还可以控制所有缓存)
public class FileCache
{
public static string AppCacheDirectory { get; set; }
static FileCache()
{
// default cache directory, can be changed in de app.xaml.
AppCacheDirectory = String.Format("{0}/Cache/", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData));
}
public static string FromUrl(string url)
{
//Check to see if the directory in AppData has been created
if (!Directory.Exists(AppCacheDirectory))
{
//Create it
Directory.CreateDirectory(AppCacheDirectory);
}
//Cast the string into a Uri so we can access the image name without regex
var uri = new Uri(url);
var localFile = String.Format("{0}{1}", AppCacheDirectory, uri.Segments[uri.Segments.Length - 1]);
if (!File.Exists(localFile))
{
HttpHelper.GetAndSaveToFile(url, localFile);
}
//The full path of the image on the local computer
return localFile;
}
}
我还为下载内容创建了一个辅助类:
public class HttpHelper
{
public static byte[] Get(string url)
{
WebRequest request = HttpWebRequest.Create(url);
WebResponse response = request.GetResponse();
return response.ReadToEnd();
}
public static void GetAndSaveToFile(string url, string filename)
{
using (FileStream stream = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
byte[] data = Get(url);
stream.Write(data, 0, data.Length);
}
}
}
HttpHelper 使用 WebResponse 类的扩展来将结果读取到数组中
public static class WebResponse_extension
{
public static byte[] ReadToEnd(this WebResponse webresponse)
{
Stream responseStream = webresponse.GetResponseStream();
using (MemoryStream memoryStream = new MemoryStream((int)webresponse.ContentLength))
{
responseStream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
}
现在你已经完成了,让我们在 xaml 中使用它
<Grid>
<local:CachedImage ImageUrl="http://host/image.png" />
</Grid>
就是这样,它是可重用且健壮的。
唯一的缺点是,在您清理缓存目录之前,永远不会再次下载图像。
第一次从网上下载图片并保存在缓存目录中。 最终从缓存中加载图片,并分配给父类的源(Image)。
亲切的问候, 杰伦·范·朗根。
【讨论】:
如果您只是尝试在同一运行中进行缓存,则本地字典可以用作运行时缓存。
如果您尝试在应用程序运行之间进行缓存,它会变得更加棘手。
如果这是一个桌面应用程序,只需将缓存的图像保存在本地用户的应用程序数据文件夹中。
如果是 XBAP 应用程序(浏览器中的 WPF),出于安全考虑,您将只能在用户的 Isolated Storage 中设置本地缓存。
【讨论】: