flickrVIEWR – A flickr viewer in Silverlight – Part 3

I guess it is time to tie it all together and get the UI up and running for the flickrVIEWR. As you will notice, having built the viewmodels, the code for the UI is actually very simple. And the cool part is that there is no code in code behind at all.

But before I start creating the Xaml for the application, I’m going to hook up the viewmodel to the page and do a few little bits and pieces needed to get it all working.

As you might remember, I decided to use a service locator to handle the location of the photo service. The simple little service locator I created needs a type to work. So before we can go anywhere I must tell the service locator what type of service to use. I do this in a handler for the Application’s Startup event.

In this handler, I also need to decide what type of “search” I want to use . This decision is made flexible by passing variables to the application from HTML. That way, I can either set the variables server side in my ASP.NET page or by using for example jQuery. The passing of the variables, is easily done using the init parameters. And all I have to do in the handler is to parse the init parameters and pass them along to the Photoset viewmodel.

And last but not least, the handler needs to instantiate a new usercontrol, set its data context and then set it to be the applications RootVisual. This step is actually more or less provided to us when creating a new Silverlight application in Visual Studio. The only difference is that I am setting the Page’s data context before using it to set the RootVisual of the application.

private void Application_Startup(object sender, StartupEventArgs e)
{
string apiKey = "xxx"; // api key from flickr
string nsid = "xxx"; // the users flickr id

ServiceLocator.PhotoServiceType = typeof(flickrPhotoService);
ServiceLocator.PhotoServiceParams = new object[] { apiKey, nsid };

PhotoSearchType searchType = (PhotoSearchType)Enum.Parse(typeof(PhotoSearchType),
e.InitParams["searchMode"],
true);
string identifier = e.InitParams["identifier"];

Photoset photoset = new Photoset(searchType, identifier);

this.RootVisual = new Page() { DataContext = photoset };
}

As you can see, you will need to get a flickr account and a flickr api key, but that is a simple task. You can do it here.

If you were wondering how to set the init params, it is not harder than setting them using a comma separated list of values like this

<asp:Silverlight ID="Xaml1" runat="server" Source="~/ClientBin/flickrVIEWR.Blog.xap"
MinimumVersion="2.0.31005.0" Width="100%" Height="100%"
InitParameters="searchMode=Tag,identifier=blog" />

Finally it is time to get started on the UI…yeah…

The first step is to create a “Loading” text. This text will be shown while the application loads up the necessary data from flickr. For this demo, this is going to be a simple text that says “Downloading” and then the percentage downloaded. In a real world scenario, this might be a lot more interesting… Since the loading information is a string, I’ll create a TextBlock in my Xaml. And since it should be visible in the center of the application, I set its VerticalAlignment as well as HorizontalAlignment to Center. This positions it smack in the middle of the application.

<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" />

This text in the TextBlock should of course come from my viewmodel’s DownloadProgress property. There is just a tiny problem. That property is of type integer, and the TextBlock’s Text property is of type string. This has to be sorted before I can move on. I will also take this opportunity to add the “Downloading” string in front of the download progress while I’m at it. So how do I go from integer to string. Well, that is the job of a converter. So  I have to create a new converter. I’ll call it ProgressToTextConverter. It is a simple little class that implements the IValueConverter interface. But since I’m only doing unidirectional conversion, I won’t even implement one of the interfaces 2 methods. The implementation looks like this

public class ProgressToTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is int)
return string.Format("Downloading {0}%",(int)progress);

return string.Empty;
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

To be able to use this converter class, it has to be added to my controls resources. Or at least to some controls resources. Unfortunately I have created the converter in a separate namespace, which is currently not available in my Xaml. So to be able to add the converter to my resources, I have to add the namespace to my Xaml like this

<UserControl x:Class="flickrVIEWR.Blog.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:flickrVIEWR.Blog.Converters">

 

And now I can add my converter to my resources and make it available to my TextBlock binding. Adding a class to the resources basically means that you create a named instance of that class, which you can then use throughout your Xaml.

<Grid x:Name="LayoutRoot" Background="White">
<Grid.Resources>
<converters:ProgressToTextConverter x:Key="ProgressConverter" />
</Grid.Resources>

<!-- more Xaml -->

And after this huge sidestep I can finally bind my TextBlock’s Text property to the DownloadProgress property using the following Xaml

<TextBlock Text="{Binding DownloadProgress, Converter={StaticResource ProgressConverter}}"
VerticalAlignment="Center" HorizontalAlignment="Center" />

 

There is just one big problem with this snippet. This text will always be visible. I need it to disappear as soon as the download is completed. I can do this by binding the Visible property to something useful. In this case, since the viewmodel doesn’t have a “Loading” property, or something similar, I will have to use the DownloadProgress property and a smart converter instead. I could of course rewrite my viewmodel, which in this case might be smarter, but for some reason I have opted for the smart converter.

Since I know that I probably will be using a converter that converts different object types to visibility, I have named it ObjectToVisibilityConverter. It looks just like the ProgressToTextConverter, except that the implementation of Convert is different. The implementation supports converting from bool and integer. The boolean one is simple. If it is true, it is visible. The integer however, is a bit different. It will make it visible if it is 100. So basically if the download progress is 100% it is visible. This is the complete opposite of what I want right now. So why the heck would I implement it like that? Well, frankly because it makes a whole lot more sense than saying that something is visible if it is not complete. And also because I need that logic later on. But to make it possible to use it in the current situation, I have decided to use the ConverterParameter attribute. So if I pass in “inverse” to the method, it will inverse the visibility. It looks like this

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Visibility ret = (value == null) ? Visibility.Collapsed : Visibility.Visible;
if (value is bool)
ret = ((bool)value) ? Visibility.Visible : Visibility.Collapsed;

if (value is int)
ret = ((int)value >= 100) ? Visibility.Visible : Visibility.Collapsed;

bool inverse = false;
if (parameter is string)
inverse = ((string)parameter).Equals("inverse",StringComparison.InvariantCultureIgnoreCase);

if (!inverse)
return ret;

return (ret == Visibility.Visible) ? Visibility.Collapsed : Visibility.Visible;
}

And after yet another sidestep, I can go back to my Xaml and complete my binding. As you hopefully remember, I was trying to show my “downloading” text only when actually downloading. And yes, I’ll have to add the new converter to the resource dictionary just like I did with the other converter. However, since I have already shown this, I will just skip it…

<TextBlock Text="{Binding DownloadProgress, Converter={StaticResource ProgressConverter}}"
Visibility="{Binding DownloadProgress,
Converter={StaticResource VisibilityConverter},
ConverterParameter=inverse}"

VerticalAlignment="Center" HorizontalAlignment="Center" />

 

Finally…that is my loading text completed. I know that there was a bunch of code just to get that working, but the cool thing is that there will be no more code involved. From now on, it is only Xaml.

So lets go on with the Xaml. My application will be laid out using a Grid. So I create a new Grid inside the Grid that makes up the usercontrol’s layout root. This Grid will be shown only when the download is complete. And this is when all that hard work with the visibility converter gives back over and over again. I can just reuse that converter to get the Grid to be visible at the right times

<Grid Visibility="{Binding DownloadProgress, Converter={StaticResource VisibilityConverter}}">
</Grid>

Simple right…? As soon as you have hooked up all the plumbing code, it can be reused over and over and make your Xaml fast and easy to write. General converters could of course even be compiled in to a separate assembly and then reused between projects.

My Grid needs 2 columns and 2 rows. The first column should be 103 pixels wide, and the second should take up the rest of the space available. The rows are the opposite. The first row should take up all extra space, while the second row should be 30 pixels. The Xaml looks like this

<Grid.ColumnDefinitions>
<ColumnDefinition Width="103" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>

 

Getting used to working with the Grid will make your life a lot easier. Using relative sizes, auto sizes and fixed sizes can create a really complex layout that will still flow well if the control is resized. Not that my ridiculously simple Grid shows this very well, but trust me, it can…

Inside the Grid’s first column I need a way to display the thumbnails. I need something like the ASP.NET repeater. Well…how about we rethink that and see what we can find in the Xaml toolbox. Hmm…the ListBox looks promising. It makes it possible to show a collection of objects and also supports selecting an object…just the functionality I need. If I only had time to style it, it would even look good. The secret with Xaml is to find controls that supports the functionality that you need, ignoring the layout. The layout can be change however you want it to using templates…but in this demo it will stay “ugly”…

So, let’s place a ListBox in the first column. Let’s also have it span both rows, taking up the applications full height… And since it is supposed to be in the Grid’s first column, I don’t even have to tell it that… So the Xaml looks like this

<ListBox Grid.RowSpan="2"></ListBox>

 

But this isn’t really enough. I need it to display something as well. It needs to display my photos. This is easily done by binding the ListBox’s ItemsSource property to the Photos property of the data context. But besides this, I want it to work with the SelectedPhoto property. I want the selected photo in the ListBox to correspond to the SelectedPhoto in the viewmodel. And I want this to go in both directions. So if I change the viewmodel, the selection in the ListBox is changed, and if the selection in the ListBox changes, the selected photo in the viewmodel should change as well. This is exactly what will happen if the binding we use has it’s mode set to TwoWay. Finally, I have also decided to have the vertical scrollbar visible at all time…

<ListBox Grid.RowSpan="2" ItemsSource="{Binding Photos}" 
SelectedItem="{Binding SelectedPhoto,Mode=TwoWay}"
ScrollViewer.VerticalScrollBarVisibility="Visible">
</ListBox>

There is just one little downside to this. It won’t work. Well…it will actually work. Just as with all code you write, it will do just what it tells you. And in this case, I have told it to show all the photos. And it does…it takes all the photo objects and display them in the only way it knows how to. By converting them to strings…by calling ToString. So basically, what you get is a list of strings containing “flickrVIEWR.ViewModels.Photo”… Not really what I wanted. I wanted to see the thumbnails…

To get the ListBox to that, I need to modify its ItemTemplate. The ItemTemplate is basically a template that defines how each item is displayed… It is of type DataTemplate and must contain exactly one root element. In my case I’ve created a simple template that just displays the thumbnail for each item

<ListBox Grid.RowSpan="2" ItemsSource="{Binding Photos}" 
SelectedItem="{Binding SelectedPhoto,Mode=TwoWay}"
ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Image Source="{Binding Thumbnail}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

If the application is run now, it will display a list of thumbnails. The next step is to make sure that the application can also show the image in large scale. This is even easier than anything I have done so far. All I have to do, is to add a new Image and bind it’s Source to the SelectedPhoto’s Image property. The Image should be in the Grid’s first row and second column. I also want it to have a little margin to the edges. I know I said it would be ugly and contain no layout, but there is a limit to how far I will go with that…

<Image Grid.Column="1" Margin="10" Source="{Binding SelectedPhoto.Image}" />

 

As you can see, I bind the Source to the data context’s SelectedPhoto property’s Image property. So when selecting a thumbnail in the ListBox, this sets the SelectedPhoto, which in turn changes the image shown. Sweet right! We are getting functionality without even having to code it up…declarative coding is cool and simple as long as you have created the plumbing correctly.

But I want to give the user some feedback about what is happening as well. When you select and image in the ListBox, it might take a couple of seconds before it is downloaded and visible and during this time, the image will actually be empty. So I have decided to replicate the previous “loading functionality”. Basically having a string telling the user how far along the download is. But instead of binding to the data context’s DownloadProgress property, I bind it to the SelectedPhoto’s DownloadProgress. Other than that change, I can use the famous “copy-paste” approach

<TextBlock Text="{Binding SelectedPhoto.ImageDownloadProgress, 
Converter={StaticResource ProgressConverter}}"

Visibility="{Binding SelectedPhoto.ImageDownloadProgress,
Converter={StaticResource VisibilityConverter},
ConverterParameter=inverse}"

VerticalAlignment="Center" HorizontalAlignment="Center"
Grid.Column="1" />

The application is actually up and running now. If you want to, you could of course add a TextBlock to display the image’s Title or Description. But since it didn’t fit in to my simple design, I will save that part to the blog post where I demo the fully fledged, fully designed application that will be on my fiancées blog.

I just have one more thing I want in this application. I want to give the user the ability to flick through the images using next and previous buttons.

These buttons wont be visible when there is no selected image. So let’s start off by implementing that functionality. I’m placing the buttons inside a Grid. The Grid will be 200 pixels wide, with one button in each end. It will be placed in the parent Grid’s second row and second column, placing it right below the big image. To hide it when there is no selected image, I just bind it’s visibility to the SelectedPhoto property. This will set it to Collapsed if the SelectedPhoto is null…

<Grid Width="200" Grid.Row="1" Grid.Column="1"
Visibility="{Binding SelectedPhoto,
Converter={StaticResource VisibilityConverter}}"

/>

Inside that Grid, I create 2 new buttons. One with the text “<< Previous” aligned to the left, and one with the text “Next >>” aligned to the right. Since there might not be a next/previous photo available, I have decided to disable the buttons if they are not usable. This is simply done by binding the button’s IsEnabled property to the corresponding property…

<Grid Width="200" Grid.Row="1" Grid.Column="1"
Visibility="{Binding SelectedPhoto,
Converter={StaticResource VisibilityConverter}}">
<Button Content="&lt;&lt; Previous" Width="80" Height="20"
HorizontalAlignment="Left"
IsEnabled="{Binding PreviousAvailable}" />
<Button Content="Next &gt;&gt;" Width="80" Height="20"
HorizontalAlignment="Right"
IsEnabled="{Binding NextAvailable}" />
</Grid>

Oh, yeah…just remember to convert the “<” and “>” characters. Adding them unconverted, will cause the Xaml parser to fail..

To add the functionality that they will offer, I need to somehow wire them up to my viewmodel’s Next and Previous commands. This is very simple to do by using the CommandManager I created in a couple of previous posts, mainly here and here.

To be able to use the CommandManager, I need to add the CommandManager’s namespace to the Xaml

<UserControl x:Class="flickrVIEWR.Blog.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cmd="clr-namespace:CommandManager;assembly=CommandManager"
xmlns:converters="clr-namespace:flickrVIEWR.Blog.Converters">

 

After this is done, I can add hook up the functionality by simply setting the CommandManager’s properties on each of the buttons

<Button Content="&lt;&lt; Previous" Width="80" Height="20" 
HorizontalAlignment="Left"
IsEnabled="{Binding PreviousAvailable}"
cmd:CommandManager.CommandEventName="Click"
cmd:CommandManager.Command="{Binding Previous}" />
<Button Content="Next &gt;&gt;" Width="80" Height="20"
HorizontalAlignment="Right"
IsEnabled="{Binding NextAvailable}"
cmd:CommandManager.CommandEventName="Click"
cmd:CommandManager.Command="{Binding Next}" />

 

That’s it! The UI is done and fully functional. All you need to do now is to tear out all that I have just shown you how to make, and then create some cool design. And if you do, don’t forget to send me a message and show me what you have created…

And yes…to make it simpler for you, I have zipped the code for you so you can just download it and work on the design. flickrVIEWR.zip (1.49 mb)

If you have any questions, comments or possible complaints, don’t hesitate to send a message or leave a comment…

Cheers!

Comments (1) -

Thanks so much for the fantastic post (no need to apologize for its length as you have)! I found it useful for learning a bit about Silverlight, as well as the MVVM pattern.

Comments are closed