Properly cancel an image download in Silverlight - silverlight

I have a set of Image elements that I use to download pictures. All the pictures have to be downloaded, but I wish to download the picture the user is looking at in the first place. If the user changes the viewed picture, I wish to cancel the downloads in progress to get the viewed picture as fast as possible.
To start a download I write: myImage.Source = new BitmapImage(theUri);.
How should I cancel it?
myImage.Source = null; ?
act on the BitmapImage ?
a better solution ?
I don't wish to download the picture by code to keep the benefit of the browser cache.

This is definitely doable -- I just tested it to make sure. Here is a quick class you can try:
public partial class Page : UserControl
{
private WebClient m_oWC;
public Page()
{
InitializeComponent();
m_oWC = new WebClient();
m_oWC.OpenReadCompleted += new OpenReadCompletedEventHandler(m_oWC_OpenReadCompleted);
}
void StartDownload(string sImageURL)
{
if (m_oWC.IsBusy)
{
m_oWC.CancelAsync();
}
m_oWC.OpenReadAsync(new Uri(sImageURL));
}
void m_oWC_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
BitmapImage oBMI = new BitmapImage();
oBMI.SetSource(e.Result);
imgMain.Source = oBMI;
}
}
This works just like you wanted (I tested it). Everytime you call StartDownload with the URL of an image (presumably whenever a user clicks to the next image) if there is a current download in progress it is canceled. The broswer cache is also definitely being used (I verified with fiddler), so cached images are loaded ~ instantly.

Related

Codename One: dealing with server/connection unavailability when loading images

It seems that if at the time when URLImage.createCachedImage is called to load and the image is not available (no connection, etc.), it won't be called again to reload when the connection comes back.
Description:
When the connection is unavailable, it shows a blank image. And it still shows the same blank image when the Display.getInstance().callSerially(...) is called again. It does not seem like it is invoking the connection to try to load the image but the blank image has apparently become the cache.
I'm not sure if I'm describing my problem properly, but here's the simplified question: how to deal with loading of image and handle events when the connection/server is unavailable? (I thought URLImage.createCachedImage knows if the image is not loaded and will try again.)
I have this piece of code to load Image:
protected static final Image loadImage(String imageAccessLocation) {
int filenameIndex = imageAccessLocation.lastIndexOf("/");
String filename = imageAccessLocation.substring(filenameIndex + 1);
String imageAccessBaseLocation = Application.getInstance().getImagesAccessBaseLocation();
String imageAccessURL = imageAccessBaseLocation + imageAccessLocation;
int displayWidth = Display.getInstance().getDisplayWidth();
EncodedImage imagePlaceholder = EncodedImage.createFromImage(Image.createImage(displayWidth, displayWidth / 5, 0xffff0000), true);
Image image = URLImage.createCachedImage(filename, imageAccessURL, imagePlaceholder, FLAG_RESIZE_SCALE);
return image;
}
protected static final Container prepareImageContainer(Item item) {
Image image = item.getImage();
if(image == null) {
return null;
}
Image scaledImage = image.scaled(50, 50);
Container imageContainer = new Container();
imageContainer.add(scaledImage);
return imageContainer;
}
private final void prepare() {
Container imageContainer = prepareImageContainer(lookslike);
Container textContainer = prepareTextDescription(lookslike);
add(imageContainer);
add(textContainer);
}
You're right. If there is a network problem, URLImage will just silently fail.
If the URLImage hasn't finished downloading the image for whatever reason, its isAnimation() method will return true. This is a way to detect if it hasn't finished downloading. It doesn't tell you whether the download failed or just hasn't completed, but you could combine that with some sort of timeout to check if the image is downloaded, and, if not, replace it with a new URLImage for the same URL.
Not ideal, I know. You can file an RFE in the issue tracker and we'll evaluate the request.

Listbox default image

In an windows phone 7 application I'm populating one listbox with remote images .. since the images are not downloaded instantly I want to load a default image until the remote image are ready. What is the best way to do this?
Until now, I have the following code skelton:
public partial class RemoteImage : PhoneApplicationPage
{
ObservableCollection<Image> images = new ObservableCollection<Image> { };
public RemoteImage()
{
InitializeComponent();
listImage.ItemsSource = GetAllImages();
}
private ImageSource GetImageSource(string fileName)
{
return new BitmapImage(new Uri(fileName, UriKind.Absolute));
}
private ObservableCollection<Image> GetAllImages()
{
WebClient restClient = new WebClient();
restClient.OpenReadAsync(new Uri(#"http://www.my-api.com"));
restClient.OpenReadCompleted += new OpenReadCompletedEventHandler(onReadComplete);
return images;
}
private void onReadComplete(object sender, OpenReadCompletedEventArgs args)
{
Stream stm = args.Result;
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(RootObject));
RootObject ro = (RootObject)ser.ReadObject(stm);
foreach (var item in ro.items)
{
images.Add(new Image{ PhotoSource = GetImageSource(item.image.link) });
}
}
}
If you know, how many images you would need, you should create first the number of default images. Load some image file directly at your project and use it as imageSource for default images. Then, when you'll finish downloading remote images, you should set the new image source for each.
When I got the similar issue, I had some problems with defining which exactly downloaded image refers to which object on page. (As you remember the WebClient objects work asynchronously, so if you have 10 images on page and download 10 remote images at once you can't say that the first downloaded image is the first on page) To solve this you could create more complicated download method (I used a delegate to transfer the id/name of image) or use recursion (Start download method for first image, download it, set source for one on page, download next one...).

Save Silverlight webcam image (ImageSource) to Server

This is as far as I've gotten. What code would I write to save a captured image to the server? Without any dialog prompting for a save location. Similar to the way Facebook does it. (I've been unable to find examples online)
void CaptureSource_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
{
capturedImage.ImageSource = e.Result;
stopCapture(); // turns off webcam
}
It is not that simple.
Create a WCF service on the server.
Consume it on the silverlight client.
Call service method to send an image to the server.
Save it on the server with custom logic.
Or, if this is too complicated - follow this tutorial. It is rather compact RESTful approach demo.
I did i before,
Firstly
ImageTools is good library not must but a good library, you may use.
Beside this you shoul check for permission for camera access. Then here is the code,
Hope helps,
/Capture image part/
_captureSource.CaptureImageCompleted += ((s, args) =>
{
//some other stuffs
domainServiceObject.PR_PATIENTPHOTOs.Clear();
photo = new PR_PATIENTPHOTO();
ImageTools.ExtendedImage eimg=args.Result.ToImage();
var encoder=new ImageTools.IO.Png.PngEncoder();
Stream stream= eimg.ToStreamByExtension("png");
if (stream.Length > 512000)
{
eimg= ExtendedImage.Resize(eimg, 240, new NearestNeighborResizer());
stream = eimg.ToStreamByExtension("png");
}
/Reload Image Part/
//note photo.photo is byte[]
photo = domainServerObject.PR_PATIENTPHOTOs.FirstOrDefault();
if (photo != null)
{
using (MemoryStream ms = new MemoryStream(photo.PHOTO, 0, photo.PHOTO.Length))
{
ms.Write(photo.PHOTO, 0, photo.PHOTO.Length);
BitmapImage img = new BitmapImage();
img.SetSource(ms);
imagePatientPhoto.Source = img;
}
}

Loading image from URL in Windows Phone 7

I use below code to load image from URL in my winodws phone 7 application.
Uri uri = new Uri("http://dilbert.com/dyn/str_strip/000000000/00000000/0000000/000000/80000/5000/100/85108/85108.strip.print.gif", UriKind.Absolute)
image1.Source = new BitmapImage(uri);
It is working fine for me. But image is loading asynchronously and by the time I want to show some kind of busy indicator there and if image does not exist on such URL then I want to show some default image. How can I achieve that?
I think if you are subscribed to the Image.ImageFailed Event you should be able to show to default image in case of a non-existing image.
Conditions in which this event can occur include the following:
File not found.
Invalid (unrecognized or unsupported) file format.
Unknown file format decoding error after upload.
So something like this might work for you:
image1.ImageFailed += new EventHandler<ExceptionRoutedEventArgs>(handlerImageFailed);
Uri uri = new Uri("http://dilbert.com/dyn/str_strip/000000000/00000000/0000000/000000/80000/5000/100/85108/85108.strip.print.gif", UriKind.Absolute)
image1.Source = new BitmapImage(uri);
void handlerImageFailed(object sender, ExceptionRoutedEventArgs e)
{
// Show the default image
}

How do I cache images on the client for a WPF application?

We are developing a WPF desktop application that is displaying images that are currently being fetched over HTTP.
The images are already optimised for quality/size but there is an obvious wait each time that the image is fetched.
Is there a way to cache images on the client so that they aren't downloaded each time?
I know this question is very old, but I had to use caching recently in a WPF application and found that there is a much better option in .Net 3.5 with BitmapImage by setting UriCachePolicy, that will use system-level caching:
<Image.Source>
<BitmapImage UriCachePolicy="Revalidate"
UriSource="https://farm3.staticflickr.com/2345/2077570455_03891081db.jpg"/>
</Image.Source>
You can even set the value in the app.config to make all your app use a default value for caching:
<system.net>
<requestCaching defaultPolicyLevel="CacheIfAvailable"/>
</system.net>
You will find an explanation of the RequestCacheLevel values here: http://msdn.microsoft.com/en-us/library/system.net.cache.requestcachelevel(v=vs.110).aspx
This functionality understands HTTP/1.1 headers, so if you set Revalidate it uses If-Modified-Since header to avoid downloading it each time, but still checking if the image has been changed so you always have the correct one.
For people coming here via Google, I have packaged the original implementation that Simon Hartcher posted, refactored by Jeroen van Langen (along with the tweaks from Ivan Leonenko to make it bindable), into an Open Source NuGet package.
Please find the details here - http://floydpink.github.io/CachedImage/
i've read your blog, and that brought me to this (i think much easier) concept setup:
As you will noticed, i reused some of your code you shared, so i'll share mine back.
Create a new custom control called 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;
}
}
}
}
Next i've made a FileCache class (so i have control on all caching not only images)
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;
}
}
Also for downloading content I made a helper class:
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);
}
}
}
The HttpHelper uses an extension on the WebResponse class for reading the result to an array
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();
}
}
}
Now you got it complete, lets use it in xaml
<Grid>
<local:CachedImage ImageUrl="http://host/image.png" />
</Grid>
That's all, it's reusable and robust.
The only disadvance is, that the image is never downloaded again until you cleanup the cache directory.
The first time the image is downloaded from the web and saved in the cache directory.
Eventually the image is loaded from the cache and assign to the source of the parent class (Image).
Kind regards,
Jeroen van Langen.
I have solved this by creating a Binding Converter using the IValueConverter interface. Given that I tried to find a solid solution for this for at least a week, I figured I should share my solution for those with this problem in the future.
Here is my blog post: Image Caching for a WPF Desktop Application
If you're just trying to cache within the same run, then a local dictionary could function as a runtime cache.
If you're trying to cache between application runs, it gets trickier.
If this is a desktop application, just save the cached images locally in the user's application data folder.
If it's an XBAP application (WPF in Browser), you'll only be able to setup a local cache in the user's Isolated Storage, due to security.
Based on this I made custom control which:
can download images asynchronously and get them from cache if image
is thread safe
was downloaded has dependency property to which you can bind to
update images, providing new names in initial feed (don’t forget to maintain cache clean operation, e.g. you can parse your feed
and asynchronously delete images with no links in feed)
I made a blog post:, and here's the 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));
}
}
Now you can bind to it:
<Cache:CachedImage ImageUrl="{Binding Icon}"/>
Just a update from Jeroen van Langen reply,
You can save a bunch of line
remove HttpHelper class and the WebResponse_extension
replace
HttpHelper.GetAndSaveToFile(url, localFile);
by
WebClient webClient = new WebClient();
webClient.DownloadFile(url, localFile);
This cachedImage works great, but..
Any advice on how I could use the same cached image type capability for ImageSource for an ImageBrush?
<Rectangle
Width="32"
Height="32"
Margin="2,1"
RadiusX="16"
RadiusY="16"
RenderOptions.BitmapScalingMode="HighQuality">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding Image}" />
</Rectangle.Fill>
</Rectangle>
Note there may be a better way to do rounded images (for say profile images)

Resources