Reputation: 20764
I use the following code to show images from a webserver:
<Image Source="{Binding Url}" />
The image gets automatically downloaded, and I assume there is also some caching based on the Url.
My problem is, that when the app is offline, the assumably cached images are not shown.
Is there any way to change the caching behavior, so that images are also loaded when there is no network available? Pointers to documentation regarding the caching would be very helpful as well.
Upvotes: 13
Views: 19538
Reputation: 7454
You could also use FFImageLoading (https://github.com/molinch/FFImageLoading/)
It's just as simple as:
<ff:MvxCachedImage Name="image"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
LoadingPlaceholder="loading.png"
ErrorPlaceholder="error.png"
RetryCount="3"
RetryDelay="250"
DownsampleHeight="300"
ImagePath="http://lorempixel.com/output/city-q-c-600-600-5.jpg">
</ff: MvxCachedImage >
Sample projects here: https://github.com/molinch/FFImageLoading/tree/master/samples/
Upvotes: 1
Reputation: 11
My solution: (save image from web to local storage and bind saved image to page)
XAML
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<!--Some code removed-->
<Image Source="{Binding Img_Thumb.Result}" />
</DataTemplate>
</ListView.ItemTemplate>
DataModel
public class DataModel_ListOfEvents
{
public DataModel_ListOfEvents(String img_thumb)
{
this.Img_Thumb = new NotifyTaskCompletion<string>(JsonCached.ImageFromCache2(img_thumb));
}
public NotifyTaskCompletion<string> Img_Thumb { get; private set; }
}
public sealed class SampleData_ListOfEvents
{
private static SampleData_ListOfEvents _sampleDataSource = new SampleData_ListOfEvents();
private ObservableCollection<DataModel_ListOfEvents> _items = new ObservableCollection<DataModel_ListOfEvents>();
public ObservableCollection<DataModel_ListOfEvents> Items { get { return this._items; } }
}
Magic
public class JsonCached
{
public static async Task<string> ImageFromCache2(string path)
{
int ru = path.IndexOf(".ru") + 4;// TODO: .com .net .org
string new_path = path.Substring(ru).Replace("/", "\\");
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
try
{
Stream p = await localFolder.OpenStreamForReadAsync(new_path);
p.Dispose();
System.Diagnostics.Debug.WriteLine("From cache");
return localFolder.Path + "\\" + new_path;
}
catch (FileNotFoundException)
{
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine("{0}", e.Message);
}
StorageFile storageFile = await localFolder.CreateFileAsync(new_path, CreationCollisionOption.OpenIfExists);
Uri Website = new Uri(path);
HttpClient http = new HttpClient();
// TODO: Check connection. Return message on fail.
System.Diagnostics.Debug.WriteLine("Downloading started");
byte[] image_from_web_as_bytes = await http.GetByteArrayAsync(Website);
MakeFolders(localFolder, path.Substring(ru));
Stream outputStream = await storageFile.OpenStreamForWriteAsync();
outputStream.Write(image_from_web_as_bytes, 0, image_from_web_as_bytes.Length);
outputStream.Position = 0;
System.Diagnostics.Debug.WriteLine("Write file done {0}", outputStream.Length);
outputStream.Dispose();
return localFolder.Path + "\\" + new_path;
}
private static async void MakeFolders(StorageFolder localFolder, string path)
{
//pics/thumbnail/050/197/50197442.jpg
int slash = path.IndexOf("/");
if (slash <= 0) // -1 Not found
return;
string new_path = path.Substring(0, slash);
StorageFolder opened_folder = await localFolder.CreateFolderAsync(new_path, CreationCollisionOption.OpenIfExists);
string very_new_path = path.Remove(0, new_path.Length + 1);
MakeFolders(opened_folder, very_new_path);
}
}
NotifyTaskCompletion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
namespace App2.NotifyTask
{
public sealed class NotifyTaskCompletion<TResult> : INotifyPropertyChanged
{
public NotifyTaskCompletion(Task<TResult> task)
{
Task = task;
if (!task.IsCompleted)
{
var _ = WatchTaskAsync(task);
}
}
private async Task WatchTaskAsync(Task task)
{
try
{
await task;
}
catch
{
}
var propertyChanged = PropertyChanged;
if (propertyChanged == null)
return;
propertyChanged(this, new PropertyChangedEventArgs("Status"));
propertyChanged(this, new PropertyChangedEventArgs("IsCompleted"));
propertyChanged(this, new PropertyChangedEventArgs("IsNotCompleted"));
if (task.IsCanceled)
{
propertyChanged(this, new PropertyChangedEventArgs("IsCanceled"));
}
else if (task.IsFaulted)
{
propertyChanged(this, new PropertyChangedEventArgs("IsFaulted"));
propertyChanged(this, new PropertyChangedEventArgs("Exception"));
propertyChanged(this,
new PropertyChangedEventArgs("InnerException"));
propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage"));
}
else
{
propertyChanged(this,
new PropertyChangedEventArgs("IsSuccessfullyCompleted"));
propertyChanged(this, new PropertyChangedEventArgs("Result"));
}
}
public Task<TResult> Task { get; private set; }
public TResult Result { get { return (Task.Status == TaskStatus.RanToCompletion) ? Task.Result : default(TResult); } }
public TaskStatus Status { get { return Task.Status; } }
public bool IsCompleted { get { return Task.IsCompleted; } }
public bool IsNotCompleted { get { return !Task.IsCompleted; } }
public bool IsSuccessfullyCompleted { get { return Task.Status == TaskStatus.RanToCompletion; } }
public bool IsCanceled { get { return Task.IsCanceled; } }
public bool IsFaulted { get { return Task.IsFaulted; } }
public AggregateException Exception { get { return Task.Exception; } }
public Exception InnerException { get { return (Exception == null) ? null : Exception.InnerException; } }
public string ErrorMessage { get { return (InnerException == null) ? null : InnerException.Message; } }
public event PropertyChangedEventHandler PropertyChanged;
}
}
Upvotes: 1
Reputation: 4447
I have got a solution for you. It is JetImageLoader, I created it for application, where we need to load, cache and show big amount of logos, icons and so on.
It can be used as binding converter, so you should not even change your code! Just update your XAMLs!
Please, check out samples in repository, you'll love it ;)
Features:
Here is the example:
<Image Source="{Binding ImageUrl, Converter={StaticResource MyAppJetImageLoaderConverter}}"/>
Upvotes: 5
Reputation: 2727
BitmapImage
automatically caches remote images by default. It's best used in conjunction with CreateOptions="BackgroundCreation"
for the best performance.
<Image Height="100" Width="100" Margin="12,0,9,0">
<Image.Source>
<BitmapImage UriSource="{Binding ImgURL}" CreateOptions="BackgroundCreation"/>
</Image.Source>
</Image>
This MSDN blog post, old but still relevant, lists and explains all the CreationOptions
and that caching is automatic in most modes.
I use these options to display many news items with images and it works well. I can load the list of articles, exit the app and turn Flight Mode to On, then start a new instance of the app and the images still load up.
Manual Approach
If you'd like to control the caching yourself and cache HTTPS resources then there are few good examples...
Upvotes: 25
Reputation: 1154
I don't think there is a build in way to do it, but you could save the images in IsolatedStorage and use a Converter that checks the internet availability and either returns the online or offline url.
A quick search yielded this which might be exactly what you are looking for (it's compatible to Windows Phone 7 and might not be the best solution for Windows Phone 8)
Upvotes: 1