Data binding images with MVVM

The title for this post make it sound like the world’s simplest thing. And to be honest, it sort of is. It is not hard to get images from the ViewModels into the view, but there are several ways of doing it. Each with its own pros and cons.

In my world, my VMs often get urls/uris to images instead of the actual image. The reason for this I guess is sort of the same thing as why you shouldn’t store your images in the database. The models can become huge if they include the images, especially if we add a couple different image sizes and so on. And that is without considering the possibility of us transferring a whole array of these objects across the wire. And in a lot of cases we don’t even show all the images in the UI, so why would we pass the images along if we don’t need them…

[UPDATE]
One of my readers has just enlightened me that you can apparently data bind according to the second example below… I can swear that this has not worked in earlier versions of Silverlight at least… But I might be wrong… Anyhow, it makes part of this post less interesting. But it does not make the later examples with added functionality less useful at all.
So, thank you Sander for the information. Would be very cool if someone could tell if I am completely off the track when I say that it hasn’t worked before. If I am right about that, telling me when it was fixed would be cool…
But as I said, it does not make the entire post useless. It still shows some ideas that can be useful. The ImageViewModel does still give us some abilities that I like to use in my UIs…
[END UPDATE]

So to get the images to show up in the view, we need a way to transform these urls into actual images. And this post is all about looking at a couple of different ways that we can do this.

But let’s start at looking at what we are trying to accomplish…

<Image Source="https://chris.59north.com/myImage.jpg" />

This works like a charm (as long as there is an image at that Url).

But what if I want to get it from a VM? Well, then we obviously we need to start using a data binding syntax…

<Image Source="{Binding MyImageUrl}" />

Unfortunately this fails miserably both if the MyImageUrl property is string or Uri… So why? Well, Source property is actually of type ImageSource. And when we set the value using a string as we do in the previous example, a type converter is used to figure out what we are actually trying to do. It takes the string and assumes it is a url to an image, and then gets the image for us and sets it as the source…

But when we data bind it, the type converter is not in play. So we are responsible for exposing the correct type to the Image object. And there are several ways of doing this as I have mentioned before…

One solution would be to have the ViewModel download the image and expose an ImageSource instead. And even though this is tempting (and will actually be used with some modification a little later), I want to do it a little differently.

On way would be to implement our own type converter that does the same thing as the built in one does…more or les at least… And the way to do this, is by creating a class that implements the IValueConverter interface. And the implementation of it is actually quite simple in this case. Take a string or a Uri and turn it into an ImageSource…the only snag is that ImageSource is an abstract class, so we need to get hold of a class that inherits from it. In this case, BitmapImage will be very useful.

So a value converter for the binding could look something like this

public class ImageSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType == typeof(ImageSource))
{
if (value is string)
{
string str = (string)value;
return new BitmapImage(new Uri(str, UriKind.RelativeOrAbsolute));
}
else if (value is Uri)
{
Uri uri = (Uri)value;
return new BitmapImage(uri);
}
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

As you can see, it will basically do exactly what I said. And using it is not that hard either

...
<UserControl.Resources>
<converters:ImageSourceConverter x:Key="ImageSourceConverter" />
</UserControl.Resources>
...
<Image Source="{Binding ImageUrl, Converter={StaticResource ImageSourceConverter}}" />

Voila! That’s all we need to get an image to be shown in the view based on a Uri in the VM.

But I am kidding right? There has to be a better way…doesn’t it? Well, yes. There is definitely a way to do this without having to build a converter, and thus saving us some work. It does however mean adding a lot more XAML…

The Source property of Image is as I said of type ImageSource, and BitmapImage is an ImageSource. Luckily, we can simply use BitmapImage from XAML like this

<Image Height="50" Width="50" Stretch="Uniform">
<Image.Source>
<BitmapImage UriSource="{Binding ImageUri}" />
</Image.Source>
</Image>

 

 

This will make sure that the Source is set to an ImageSource, in this case a BitmapImage, and that the BitmapImage will get the correct image for us.

So with a bit more XAML, we can avoid the value converter… But what if I want to give the user an indication of how far the download has come? Well…then you need to do some more work. Luckily, it is re-usable work that can even offer some more advantages.

The basic idea is that since we need more functionality, we need to role that functionality into another VM. Basically we create a VM for each of the images that we are exposing, and we expose that VM instead of the Uri from out “main” VM.

The new VM, is responsible for doing exactly what the value converter and type converter is doing, but with a little bit more flexibility and “transparency”.

So let’s see what we can do with this idea… Let’s start with a simple class and build on it… And since all VMs look more or les the same in the foundation, a base class is definitely recommendable. But this time, for simplicity, I will ignore that…

public class ImageViewModel : INotifyPropertyChanged
{
protected virtual void OnPropertyChanged(string propertyname)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
public event PropertyChangedEventHandler PropertyChanged;
}

While working with transforming my url into an image, I want to expose a little bit of what is happening so that my view can offer some feedback to the user. In this case, I want to know whether or not I am actually still loading the image and how far along I am in that process. So I add 2 properties, one bool and one int

public class ImageViewModel : INotifyPropertyChanged
{
bool _isLoading;
int _progress;

...

public bool IsLoading
{
get { return _isLoading; }
private set
{
if (value == _isLoading)
return;
_isLoading = value;
OnPropertyChanged("IsLoading");
}
}
public int DownloadProgress
{
get { return _progress; }
private set
{
if (value == _progress)
return;
_progress = value;
OnPropertyChanged("DownloadProgress");
}
}
}

Once again, very basic stuff. And to be completely honest, there is nothing complicated in this VM at all. Not even when is all completed and useful…

Next I obviously need to expose some form of ImageSource…so I add a property of type BitmapImage

public class ImageViewModel : INotifyPropertyChanged
{
...
BitmapImage _img;

...

public BitmapImage Image
{
get { return _img; }
}
}

That’s it! It is now time to start adding some functionality. And it is all in the constructor. I know…it shouldn’t be there, but hey, it is a very simple little thing…

So I add a constructor that takes a string, which is the url to the image. In the constructor, I start off by setting the IsLoading property to true. I then create a new BitmapImage and hook up a handler for the ImageOpened event as well as the DownloadProgress. In the handlers I basically just update the properties in the class. When the image has been opened, I set the IsLoading to false, and whenever the download progress changes, I set the DownloadProgress property.

public ImageViewModel(string imageUrl)
{
IsLoading = true;
_img = new BitmapImage();
_img.ImageOpened += (s, e) => { IsLoading = false; };
_img.DownloadProgress += (s, e) => { DownloadProgress = e.Progress; };
_img.UriSource = new Uri(imageUrl, UriKind.RelativeOrAbsolute);
}

That’s it! This now means that we can track the image download in the view using something like the following (very simple example)

<Grid DataContext="{Binding MyImage}" Height="50" Width="50">
<Image Source="{Binding Image}" />
<Grid Visibility="{Binding IsLoading, Converter={StaticResource BoolConverter}}" Background="White">
<TextBlock Text="{Binding DownloadProgress}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Grid>

This will cover the image with a white Grid that contains a TextBlock presenting the user with the current download progress. When the image is done, the IsLoading is set to false, which causes the Grid to disappear due to the bound Visibility property.

The converter used for the Visibility binding, is very simple. It just converts a “true” value to Visibility.Visible and “false” to Visibility.Collapsed.

But what if the download fails? Well, that is just not going to be a good user experience at the moment. It will actually leave the DownloadProgress at 0 and IsLoading will just be true all the time. So this needs to be handled. And the best way to handle that in my mind, would be a fallback image. And image that is shown in case the image that is supposed to be shown doesn’t exist or causes problems. It can also be shown if the url happens to be empty…

To do this, I overload the constructor with an extra constructor that takes 2 parameters. A string url, and a BitmapImage. And for simplicity and so on, I move all the logic into that constructor and call it from the overloaded constructor.

public ImageViewModel(string imageUrl) : this(imageUrl, null)
{
}
public ImageViewModel(string imageUrl, BitmapImage fallbackImage)
{
if (string.IsNullOrEmpty(imageUrl))
{
_img = fallbackImage;
return;
}

IsLoading = true;
_img = new BitmapImage();
_img.ImageOpened += (s, e) => { IsLoading = false; };
_img.DownloadProgress += (s, e) => { DownloadProgress = e.Progress; };
_img.ImageFailed += (s, e) =>
{
IsLoading = false;
_img = FallbackImage;
OnPropertyChanged("Image");
};
_img.UriSource = new Uri(imageUrl, UriKind.RelativeOrAbsolute);

FallbackImage = fallbackImage;
}

 

As you can see from the code (I hope), if the url to the image is null or empty, the Image is set to the fallback image and the constructor returns. If there is a url, I use the same code as before, but with a small modification, if the image download fails, I set the Image to the fallback image.

There is actually one more modifications as you might have noticed. The fallback image is stored in a new public BitmapImage property called FallbackImage. This makes it possible to use the fallback image while the real image is being downloaded…

This makes it possible to change the XAML to something like this

<Grid DataContext="{Binding ImageWithFallback}">
<Image Source="{Binding Image}" />
<Grid Visibility="{Binding IsLoading, Converter={StaticResource BoolConverter}}" Background="White">
<Image Source="{Binding FallbackImage}" />
<TextBlock Text="{Binding DownloadProgress}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Grid>

 

So, how do I actually set the fallback image. That needs to come from somewhere as well… And it does. This can be done in several ways, but I have opted for a simple way for this demo. I have added a fallback image to the project and set it’s “Built Action” to “Embedded Resource”. By embedding it like that, I can get access to it using the current assembly. So when I create my ImageViewModel, I do the following

BitmapImage img = new BitmapImage();
img.SetSource(this.GetType().Assembly.GetManifestResourceStream("ImageBindingDemo.Images.chucken.jpg"));
ImageWithFallback = new ImageViewModel("http://www.somenonexistenturl.com/", img);

 

The syntax for the manifest resource name is Namespace.Path.Filename. So in this case ImageBindingDemo is the default namespace for the project, and the image called “chucken.jpg” is in a directory called “Images”. Remember that this name is case-sensitive…

That’s it! Some words of wisdom about data binding images. Well…words of wisdom might be a bit inflated to say…but at least some tips on what can be accomplished by some simple lines of code…

Demo code is available here: DOWNLOAD!

And as usual, questions are more than welcome, either as comments or as e-mails…

Cheers!

Comments (4) -

Great tutorial, nice to read and insightful thoughts, which Im eager to implement (even if I not understand it completely as Im a newbie to XAML&Csharp)
Keep on and thanks Chris

Uhh... have you actually tested the problem statement? Because I am binding string-properties with URLs everywhere and it works just fine out of the box.

<Image Source="{Binding MyImageUrl}" />

Maybe this is some special feature only in Silverlight? I have not tested with WPF or WP7.

Hi Sander!
That's really awesome...and weird. I know that I have had issues with this before. I don't know if it is a feature that has been added in later versions and that I have just missed out on that. Thanks for enlightening me...
Cheers,
Chris

Hi ZeroKoll!

Thanks for this tutorial with "under the hood" information: I'm fresh new to WPF and this kind of article is great to learn lots of things in one shot !
I've read this article because I couldn't bind an image from VM so you're definitely not off the track !
(I'm creating WPF app (.Net 3.5) with VS2K10 and Blend 4)
I think I'll stick to the first solution though: once again, I'm fresh new on the matter !

Thanks again ZeroKoll

Comments are closed