Hello again! It is time to go through the next phase in the development of the flickrVIEWR that I started a couple of days ago. The viewmodels. As usual I do build my Silverlight application using the MVVM pattern. I like this pattern a lot. Coming from an ASP.NET background, I think it offers a lot of nice features that I whish where available in general web development. I guess you do get some of the benefits and features by using MVC, but I have yet to try out doing that. And I will, I promise.
Anyhow…the viewmodels for the flickrVIEWR is not that complicated. Not at all. THhey have some small tricks up their sleeves, but nothing really cool. But before we get to the viewmodels I’m going to take a small detour via a service locator…
To make my code a little more flexible I have decided to introduce a service locator in my project. I was considering to use Unity or Ninject, but for this single service it felt like serious overkill. So instead I built my a simple class to handle the location of my service. It looks like this
public static class ServiceLocator
{
public static IPhotoService GetPhotoService()
{
return (IPhotoService)Activator.CreateInstance(PhotoServiceType, PhotoServiceParams);
}
public static Type PhotoServiceType
{
get;
set;
}
public static object[] PhotoServiceParams
{
get;
set;
}
}
It makes it possible for me to create my viewmodels without having to pass in my service in the constructor or something else. But still being able to replace the service type for another type whenever I feel like it.
But let’s carry on to my Photoset viewmodel. This is the main model for the application. It is responsible for getting all the image information and create the Photo viewmodels and so on.
First of all I have created a new class that implements the INotifyPropertyChanged interface. All viewmodels need this to make good use of the binding support in Silverlight. Next off I have decided that it needs at least 2 properties. One called Photos and one called SelectedPhoto. The Photos one will be an ObservableCollection. This is a nice little generic collection that implements ICollectionChanged or something like that. Basically it will notify any bound objects if the collection changes. The SelectedPhoto will be the currently selected Photo.
public class Photoset : INotifyPropertyChanged
{
ObservableCollection<Photo> _photos = new ObservableCollection<Photo>();
Photo _selectedPhoto = null;
public ObservableCollection<Photo> Photos
{
get { return _photos;}
}
public Photo SelectedPhoto
{
get { return _selectedPhoto; }
set
{
_selectedPhoto = value;
OnPropertyChanged("SelectedPhoto");
}
}
protected void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
But the Photoset needs to perform some work as well. It needs to run off to the service and get the photos… I have decided to do this in the constructor. Why? Well, it should be done as soon as possible, the call is asynchronous so it wont hold up the caller…so why not… There are also a few other things going on in there. I will get back to that in a minute…
public Photoset(PhotoSearchType searchType, string searchParameter)
{
GetPhotos(searchType, searchParameter);
// more code here in a minute...
}
private void GetPhotos(PhotoSearchType searchType, string searchParameter)
{
IPhotoService photoService = ServiceLocator.GetPhotoService();
photoService.DownloadProgressChanged += delegate(object sender, DownloadProgressEventArgs e)
{
DownloadProgress = e.DownloadProgress;
OnPropertyChanged("DownloadProgress");
};
photoService.DownloadFailed += delegate(object sender, ExceptionEventArgs e)
{
Error = e.Exception;
OnPropertyChanged("ErrorMessage");
};
photoService.PhotoDownloaded += delegate(object sender, PhotoInfoEventArgs e)
{
Photo p = new Photo(e.PhotoInfo);
p.Selected += delegate { SelectedPhoto = p; };
_photos.Add(p);
};
photoService.GetPhotos(searchType, searchParameter);
}
As you can see, it is not that complicated. I get a hold of a new photo service through my service locator. Then I add handlers for the different events. And finally I call GetPhotos(). Calling OnPropertyChanged after setting the values in the handlers is possibly not the best looking thing. It should probably be in the actual property. But I have implemented those as simple as possible, and can therefore not do it there…
As you have probably noticed, I have a few more properties than I have shown this far. I have added properties for download progress, exceptions, and also two helpers to tell the view. The little helpers are called NextAvailable and PreviousAvailable. These properties help the view to enable or disable next and previous buttons. They rely on an internal property that I have created that gets the index of the currently selected image…
private int SelectedIndex
{
get
{
if (SelectedPhoto == null)
return -1;
return Photos.IndexOf(SelectedPhoto);
}
}
public int DownloadProgress
{
get;
private set;
}
public Exception Error
{
get;
private set;
}
public bool NextAvailable
{
get
{
if (SelectedPhoto == null)
return Photos.Count > 0;
return SelectedIndex < Photos.Count - 1;
}
}
public bool PreviousAvailable
{
get
{
if (SelectedPhoto == null)
return false;
return SelectedIndex > 0;
}
}
However, some of the properties are as you can see depending on other things than themselves being changed. That is why I have added some extra calls to OnPropertyChanged. The Next- and PreviousAvailable are dependent on the collection of photos. That’s why I have added a little code in the constructor
Photos.CollectionChanged += delegate
{
OnPropertyChanged("NextAvailable");
OnPropertyChanged("PreviousAvailable");
};
This code will notify the view of changes to those properties as soon as the photos collection changes. They are however also dependent on the selected photo. So in the setter for selected photo I have added this
set
{
_selectedPhoto = value;
OnPropertyChanged("SelectedPhoto");
OnPropertyChanged("NextAvailable");
OnPropertyChanged("PreviousAvailable");
}
That’s actually it for the Photoset object. Except for one thing. I need support to do actions. I need to be able to select photos, go to next and previous as well as “unselect” a photo. This functionality should be in my viewmodel and not in code behind. That’s why I use the CommandManager I created in previous posts. This uses ICommand objects. In this case I created an ICommand called DelegateCommand. It is, as you probably understand by the name, a ICommand object that uses delegates. This is pretty unimportant, but it looks like this
public class DelegateCommand : ICommand
{
CommandDelegate _delg;
public DelegateCommand(CommandDelegate delg)
{
_delg = delg;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_delg.Invoke(parameter);
}
public event EventHandler CanExecuteChanged;
}
public delegate void CommandDelegate(object parameter);
So, after having built this, I added 4 ICommand properties to my viewmodel
public ICommand SelectPhoto
{
get;
private set;
}
public ICommand UnselectPhoto
{
get;
private set;
}
public ICommand Next
{
get;
private set;
}
public ICommand Previous
{
get;
private set;
}
These properties are then set from the constructor. Setting them to relevant delegates. In this case, the delegates are so simple that I have created them using lambda expressions and anonymous methods…
SelectPhoto = new DelegateCommand(param => SelectedPhoto = param as Photo);
UnselectPhoto = new DelegateCommand(param => SelectedPhoto = null);
Next = new DelegateCommand(delegate(object param) { if (NextAvailable) SelectedPhoto = Photos[SelectedIndex + 1]; });
Previous = new DelegateCommand(delegate(object param) { if (PreviousAvailable) SelectedPhoto = Photos[SelectedIndex - 1]; });
That’s it. Not that hard was it. But wait, there’s more… Not in this class though… Let’s carry on to the Photo class.
Once again I start of with a new class that implements INotifyPropertyChanged… This class will also expose a single ICommand as well as one event. The ICommand is called Select and is used to set the Photo as the SelectedPhoto of the Photoset. It does this by exposing a Select ICommand that raises the Selected event that the Photoset listens to (which you should know if you read my code above…).
public class Photo : INotifyPropertyChanged
{
internal Photo(IPhotoInfo photo)
{
PhotoInfo = photo;
Select = new DelegateCommand(o => OnSelected());
}
private IPhotoInfo PhotoInfo
{
get;
set;
}
public ICommand Select
{
get;
private set;
}
protected void OnSelected()
{
if (Selected != null)
Selected(this, new EventArgs());
}
public event EventHandler Selected;
protected void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
The code is long because of all the line breaks, but very simple in its functionality. The Photo of course needs to expose some information about the photo. It needs to expose a title, a description as well as two bitmaps representing the thumbnail and the image in larger scale. But first off, here are the title and description properties…
public string Title
{
get { return PhotoInfo.Title; }
}
public string Description
{
get { return PhotoInfo.Description; }
}
The images are a little more complicated though. First of all, they are lazy loaded. So, on the first request, they need to run off to the service and get the bytes that represent the image. The other interesting thing is that the properties can’t return the same image to all callers. If you bind the image to several Image controls, they freak out if the image is being used already. (Actually I haven’t verified this this time, but I have at least had that problem in the past)
Let’s start by looking at the Thumbnail property. It includes 2 private members and three properties. Sweet! What are the members for? Well, one will contain the byte array, that’s kind of obvious. The other is a boolean that indicates if the image is being downloaded. This keeps the system from starting several downloads if several elements are bound to it. It looks like this
byte[] _thumbnail = null;
bool _isGettingThumbnail;
public BitmapImage Thumbnail
{
get
{
if (_thumbnail == null && !_isGettingThumbnail)
{
// more code...
}
return GenerateImage(_thumbnail);
}
}
public int ThumbnailDownloadProgress
{
get;
private set;
}
public Exception ThumbnailDownloadException
{
get;
private set;
}
As you can see, there is a bit of code that I have cut out. These cut out part and the GenerateImage method needs a little extra explanation… First off the cut out part that does the actual download. That code gets a new photo service using the service locator. Then adds handlers to the service’s events and finally calls GetImage(). The handlers don’t really do that much. They make sure that the relevant properties are updated as they should and that OnPropertyChanged is called. The interesting part is how this works in the real world. The call to the photo service is asynchronous. So the bound element will start the download and then get a null.
IPhotoService photoService = ServiceLocator.GetPhotoService();
photoService.ImageDownloadProgressChanged += delegate(object sender, flickVIEWR.Interfaces.DownloadProgressEventArgs e)
{
ThumbnailDownloadProgress = e.DownloadProgress;
OnPropertyChanged("ThumbnailDownloadProgress");
};
photoService.ImageDownloadFailed += delegate(object sender, ExceptionEventArgs e)
{
ThumbnailDownloadException = e.Exception;
OnPropertyChanged("ThumbnailDownloadException");
};
photoService.ImageDownloadCompleted += delegate(object sender, ImageEventArgs e)
{
_thumbnail = e.Image;
_isGettingThumbnail = false;
OnPropertyChanged("Thumbnail");
};
photoService.GetImage(PhotoInfo, ImageSize.Thumbnail);
The cool part is in the “completed” handler. It stores the byte array in the private member, and then calls OnPropertyChanged. This causes all the elements that are bound to it to run back and check it again. This time there are bytes in the byte array which causes a Bitmap to be returned.
So, what does the GenerateImage do? Well, not that much. It creates a new BitmapImage object. Then creates a new MemoryStream. The BitmapImage can initialize itself from a stream. So somehow I need to get my byte array into a stream and then pass the stream to the BitmapImage. This is where the MemoryStream comes in to play. I write the bytes to the stream, sets the position back to the beginning and then pass it to the BitmapImage. The moving of the position is really important. If you don’t reset it to 0, it will be at the last place you were working in the stream. After writing to the stream, the “pointer” is at the end of the stream…
private BitmapImage GenerateImage(byte[] bytes)
{
if (bytes == null)
return null;
BitmapImage img = new BitmapImage();
MemoryStream ms = new MemoryStream(new byte[bytes.Length]);
ms.Write(bytes,0,bytes.Length);
ms.Seek(0,SeekOrigin.Begin);
img.SetSource(ms);
return img;
}
The code for the Image properties are more or less identical, so I wont post it. It does exactly the same things, but writes to other members…
That’s it! The viewmodels are not more complicated than that. I will try to post the UI post as soon as possible. After that we will see if I get my act together and add some nice layout to it…
Thanks for reading all the way to the end. If there is anything you wonder or think I do wrong or anything, send me an e-mail or add a comment…