【问题标题】:How do I cache images on the client for a WPF application?如何在客户端为 WPF 应用程序缓存图像?
【发布时间】:2010-12-25 02:01:54
【问题描述】:

我们正在开发一个 WPF 桌面应用程序,它显示当前通过 HTTP 获取的图像。

图像已经针对质量/尺寸进行了优化,但每次获取图像时都会有明显的等待。

有没有办法在客户端缓存图片,以免每次都下载?

【问题讨论】:

    标签: wpf http image


    【解决方案1】:

    这个 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>
    

    请注意,可能有更好的方法来制作圆形图像(例如个人资料图像)

    【讨论】:

      【解决方案2】:

      我知道这个问题已经很老了,但是我最近不得不在 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 标头以避免每次都下载它,但仍会检查图像是否已更改,因此您始终拥有正确的。

      【讨论】:

      • 很好的发现。最初的解决方案是在没有选择的情况下构建的。微软实施自己的计划只是时间问题。
      【解决方案3】:

      对于通过 Google 来到这里的人,我已将 Simon Hartcher 发布的原始实现打包,由 Jeroen van Langen 重构(以及 Ivan Leonenko 的调整以使其可绑定),进入开源 NuGet 包。

      请在此处找到详细信息 - http://floydpink.github.io/CachedImage/

      【讨论】:

      • 我不认为你可以相信我为 Jeroen 构建的原始代码?他说他在回答中使用了我的代码。
      • 我的错,西蒙。您之前接受的博文不再可访问,并且没有完全表明 Jeroen 实际上重构了您的原始解决方案。我一定会进行编辑并包括你的名字。 :-)
      • 更新了此处的答案以及 GitHub 存储库/项目页面以归功于您的原始解决方案 @SimonHartcher - floydpink.github.io/CachedImage :-)
      • 没问题。我仍然在某个地方有原始帖子,但我认为它不再相关。感谢您对我的信任。
      【解决方案4】:

      我通过使用IValueConverter 接口创建绑定转换器解决了这个问题。考虑到我至少花了一周的时间试图找到一个可靠的解决方案,我想我应该与将来有这个问题的人分享我的解决方案。

      这是我的博文:Image Caching for a WPF Desktop Application

      【讨论】:

      • @wouter 已修复。我已经迁移了系统,但仍然需要修复一些旧帖子:)
      【解决方案5】:

      基于此,我制作了自定义控件:

      • 可以异步下载图片,如果有图片则从缓存中获取
      • 是线程安全的
      • 已下载具有可以绑定到的依赖属性
      • 更新图像,在初始提要中提供新名称(不要忘记保持缓存清理操作,例如,您可以解析提要 并异步删除 Feed 中没有链接的图片)

      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}"/>
      

      【讨论】:

      • 感谢您分享您为使其可绑定所做的调整,Ivan。我已将 Jeroen 的实现与您的调整一起打包到 NuGet 包中。请在此处查看详细信息 - floydpink.github.io/CachedImage
      【解决方案6】:

      只是 Jeroen van Langen 回复的更新,

      你可以节省一堆线

      删除 HttpHelper 类和 WebResponse_extension

      替换

      HttpHelper.GetAndSaveToFile(url, localFile);
      

      通过

      WebClient webClient = new WebClient();
          webClient.DownloadFile(url, localFile);
      

      【讨论】:

      • 即使下载请求失败,DownloadFile 也会创建文件。
      【解决方案7】:

      我读过你的博客,这让我想到了这个(我认为更容易)概念设置:

      您会注意到,我重复使用了您分享的一些代码,所以我会分享我的。

      创建一个名为 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)。

      亲切的问候, 杰伦·范·朗根。

      【讨论】:

      • 我真的很喜欢你的实现。这是很好的因素。谢谢
      • 感谢您分享实施,Jeroen。我已将其打包为 NuGet 包,以防您想再次使用它。请在此处查看详细信息 - floydpink.github.io/CachedImage
      【解决方案8】:

      如果您只是尝试在同一运行中进行缓存,则本地字典可以用作运行时缓存。

      如果您尝试在应用程序运行之间进行缓存,它会变得更加棘手。

      如果这是一个桌面应用程序,只需将缓存的图像保存在本地用户的应用程序数据文件夹中。

      如果是 XBAP 应用程序(浏览器中的 WPF),出于安全考虑,您将只能在用户的 Isolated Storage 中设置本地缓存。

      【讨论】:

      • 它是一个桌面应用程序。我会把它放在问题中。
      猜你喜欢
      • 2014-03-26
      • 1970-01-01
      • 1970-01-01
      • 2010-10-18
      • 1970-01-01
      • 2010-10-04
      • 2010-09-20
      • 2010-10-06
      • 1970-01-01
      相关资源
      最近更新 更多