2009-12-10 4 views
9

Nous développons une application de bureau WPF qui affiche les images qui sont en train d'être récupérées sur HTTP.Comment mettre en cache des images sur le client pour une application WPF?

Les images sont déjà optimisées pour la qualité/taille mais il y a une attente évidente chaque fois que l'image est récupérée.

Existe-t-il un moyen de mettre en cache les images sur le client afin qu'elles ne soient pas téléchargées à chaque fois?

Répondre

4

Pour les personnes qui viennent ici via Google, j'ai emballé l'implémentation originale que Simon Hartcher posté, refacturé par Jeroen van Langen (avec les réglages de Ivan Leonenko pour le rendre lisible), dans un paquet Open Source NuGet.

S'il vous plaît trouver les détails ici - http://floydpink.github.io/CachedImage/

+1

Je ne pense pas que vous pourriez me créditer pour le code original sur lequel Jeroen construit sur? Il dit qu'il a utilisé mon code dans sa réponse. –

+0

Mon diable, Simon. Le post de blog que vous aviez précédemment accepté n'est plus accessible et il ne s'est pas complètement enregistré que Jeroen avait en fait refaçonné votre solution originale. Je vais certainement faire les modifications et inclure votre nom aussi. :-) –

+0

Et merci d'en faire la réponse acceptée! –

2

Si vous essayez simplement de mettre en cache dans le même cycle, un dictionnaire local peut fonctionner comme un cache d'exécution.

Si vous essayez de mettre en cache entre des exécutions d'application, il devient plus délicat.

S'il s'agit d'une application de bureau, enregistrez simplement les images mises en cache localement dans le dossier de données d'application de l'utilisateur.

S'il s'agit d'une application XBAP (WPF dans le navigateur), vous ne pourrez configurer un cache local que dans la zone Isolated Storage de l'utilisateur, en raison de la sécurité.

+0

C'est une application de bureau. Je vais mettre cela dans la question. –

+0

Eh bien, dans ce cas, il suffit de sauvegarder les images dans le dossier de données de l'application pour l'utilisateur ... –

3

J'ai résolu ceci en créant un convertisseur de liaison en utilisant l'interface IValueConverter. Étant donné que j'ai essayé de trouver une solution solide pour cela pendant au moins une semaine, j'ai pensé que je devais partager ma solution pour ceux qui ont ce problème à l'avenir.

Voici mon blog: Image Caching for a WPF Desktop Application

+0

http://simonhartcher.com/2009/12/12/image-caching-for-a-wpf-desktop-application/ – Wouter

+0

@wouter Fixe. J'ai migré des systèmes et ai encore besoin de réparer quelques anciens messages :) –

3

j'ai lu votre blog, et qui m'a amené à ce (je pense beaucoup plus facile) configuration concept:

Comme vous remarqué, je réutilisé une partie de votre code que vous avez partagé, donc je vais partager le mien.

Créez un nouveau contrôle personnalisé appelé 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; 
      } 
     } 
    } 
} 

Ensuite je l'ai fait une classe FileCache (donc j'ai le contrôle sur la mise en cache des images non seulement)

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; 
    } 
} 

également pour le téléchargement de contenu que j'ai fait une classe d'aide:

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 utilise une extension de la classe WebResponse pour lire le résultat dans un tableau

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(); 
     } 
    } 
} 

Maintenant vous l'avez terminé, permet de l'utiliser en XAML

<Grid> 
    <local:CachedImage ImageUrl="http://host/image.png" /> 
</Grid> 

C'est tout, il est réutilisable et robuste. Le seul inconvénient est que l'image n'est jamais téléchargée à nouveau tant que vous n'avez pas nettoyé le répertoire de cache.

La première fois que l'image est téléchargée à partir du Web et enregistrée dans le répertoire de cache. Finalement, l'image est chargée depuis le cache et affectée à la source de la classe parente (Image). Cordialement, Jeroen van Langen.

+0

J'aime vraiment votre mise en œuvre. C'est très bien factorisé.Merci –

+0

Merci de partager la mise en œuvre, Jeroen. Je l'ai emballé comme un paquet NuGet au cas où vous voudriez l'utiliser à nouveau. S'il vous plaît vérifier les détails ici - http://floydpink.github.io/CachedImage/ –

0

Juste une mise à jour de Jeroen van Langen réponse,

Vous pouvez enregistrer un groupe de ligne

supprimer HttpHelper classe et la WebResponse_extension

remplacer

HttpHelper.GetAndSaveToFile(url, localFile); 

par

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

DownloadFile crée le fichier même si la demande de téléchargement a échoué. –

1

Basé sur ce que je fait un contrôle personnalisé qui:

  • peuvent télécharger des images de manière asynchrone et les obtenir de cache si l'image
  • est thread-safe
  • a été téléchargé est propriété de dépendance auquel vous pouvez lier à
  • mise à jour des images, en fournissant de nouveaux noms dans le flux initial (ne pas oublier de maintenir le fonctionnement de nettoyage de cache, par exemple vous pouvez analyser votre flux et de manière asynchrone supprimer des images sans lien dans l'alimentation)

I made a blog post:, et voici le code:

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)); 
    } 
} 

Maintenant, vous pouvez lier à elle:

<Cache:CachedImage ImageUrl="{Binding Icon}"/> 
+0

Merci d'avoir partagé les réglages que vous avez faits pour le rendre lisible, Ivan. J'ai empaqueté l'implémentation de Jeroen avec votre tweak, dans un paquet NuGet. S'il vous plaît vérifier les détails ici - http://floydpink.github.io/CachedImage/ –

11

Je sais que cette question est très vieux, mais je devais utiliser récemment la mise en cache dans une application WPF et a constaté qu'il ya une option beaucoup mieux en .Net 3.5 avec BitmapImage par la mise en UriCachePolicy, qui va utiliser la mise en cache au niveau du système:

<Image.Source> 
    <BitmapImage UriCachePolicy="Revalidate" 
    UriSource="https://farm3.staticflickr.com/2345/2077570455_03891081db.jpg"/> 
</Image.Source> 

Vous pouvez même définir la valeur dans le app.config pour faire toute votre application utilise une valeur par défaut pour la mise en cache:

<system.net> 
    <requestCaching defaultPolicyLevel="CacheIfAvailable"/> 
</system.net> 

Vous trouverez une explication des valeurs de RequestCacheLevel ici: http://msdn.microsoft.com/en-us/library/system.net.cache.requestcachelevel(v=vs.110).aspx

Cette fonctionnalité comprend entêtes HTTP/1.1, donc si vous définissez revalider utilise If-Modified-Since pour éviter de le télécharger à chaque fois, mais toujours vérifier si l'image a été modifiée, vous avez toujours la bonne.

+1

Bonne trouvaille. Il était hors de n'avoir aucune option que la solution originale a été construite. Ce n'était qu'une question de temps avant que Microsoft ne mette en place le leur. –

Questions connexes