MyPhotoApp: Sample project from my Windows Phone development series at Øredev

16 November 2010

Last week I gave a multi-part series on Windows Phone development at the Øredev conference in Malmö, Sweden. Here is the sample app that was built throughout the talk. It’s a simple app that lets you pick a photoset from Flickr for a given user, then browse the photos in a nice slideshow format (swipe  to move between the photos). It’s all setup with sweet data binding and goodness.

Download the zip and make sure that you have the Windows Phone Development Tools and also the Silverlight for Windows Phone Toolkit.

This app has a number of things inside it:

  • Simple view models
  • Simple Flickr web service layer
  • Integration of the service into the view model
  • Navigation service passing parameters to pages
  • Makes use of the JSON library on CodePlex
  • Makes use of the Silverlight Toolkit for page transitions and the list picker control
  • Various examples of templating and styling
  • The Pivot control for the phone is used as a photo slide show with built-in flick and pan manipulations by not using the header or title properties, and binding the actual items to the photos to be shown
  • PerformanceProgressBar

Things you need to do to get this working:

  • You may need to add the references to the JSON library as well as the toolkit before it will build for you.
  • You need a Flickr API key, get one here.
  • You need to put your API key inside the FlickrService.cs file on line 23.
  • Unless you want to browse all my photosets, you’ll want to put your own Flickr user ID string inside of MainPageViewModel.cs line 74.

The talks in the series will be uploaded in the coming weeks I’ve been told, but they are not yet available.

Pivot as a slide show component for photos

Since pivot has the nice manipulations support built into it, it was super simple to use as a slide show-style photo viewer. Here’s the XAML I used for the slide show page:

<controls:Pivot
    ItemsSource="{Binding Photos}">
    <controls:Pivot.HeaderTemplate>
        <DataTemplate>
            <Grid Height="1" Width="1"/>
        </DataTemplate>
    </controls:Pivot.HeaderTemplate>
    <controls:Pivot.ItemTemplate>
        <DataTemplate>
            <Image Source="{Binding ImageUrl}"
                    />
        </DataTemplate>
    </controls:Pivot.ItemTemplate>
    <controls:Pivot.ItemContainerStyle>
        <Style TargetType="controls:PivotItem">
            <Setter Property="Margin" Value="0"/>
            <Setter Property="Padding" Value="0"/>
        </Style>
    </controls:Pivot.ItemContainerStyle>
</controls:Pivot>

Walking through the elements, here is what they do.

ItemsSource binding

The view model for the slide show page includes a property that exposes the photo objects. This binds the pivot to that list. This means that without any of the other elements and templates being set, the pivot control will show the type name (Photo) instead of the actual photos.

Here’s the slide show view model file btw:

public class SlideshowViewModel : ViewModel
{
    private readonly IFlickrService _flickr;
    private bool _isLoading;
    private IEnumerable<Photo> _photos;

    public SlideshowViewModel()
        : this(new FlickrService())
    {
    }

    public SlideshowViewModel(IFlickrService flickrService)
    {
        _flickr = flickrService;
    }

    public bool IsLoading
    {
        get { return _isLoading; }
        private set
        {
            _isLoading = value;
            RaisePropertyChanged("IsLoading");
        }
    }

    public IEnumerable<Photo> Photos
    {
        get { return _photos; }
        set
        {
            _photos = value;
            RaisePropertyChanged("Photos");
        }
    }

    public void LoadPhotos(string photosetId)
    {
        IsLoading = true;
        _flickr.LoadPhotoset(photosetId, photos => Dispatch(() =>
            {
                if (photos != null)
                {
                    Photos = photos;
                }
                IsLoading = false;
            }));
    }
}

HeaderTemplate data template

This is a little bit of a Pivot control hack. You actually can set the header to x:Null, but then when you run this under the debugger, you may get a first-chance exception. The pivot control catches and continues, but it’s annoying as a developer.

This workaround, a 1x1 grid, enables a “screen capture” to be taken of the header, avoiding the exception when it isn’t of size:

<controls:Pivot.HeaderTemplate>
    <DataTemplate>
        <Grid Height="1" Width="1"/>
    </DataTemplate>
</controls:Pivot.HeaderTemplate>

This is all because for this slide show page, we don’t even want to show any headers, we aren’t using that visual part of the pivot ux – we’re just using the slick built-in manipulations logic and animations plus data binding.

ItemTemplate data template

This is what translates the data object (Photo) into an actual image that can be loaded.

<controls:Pivot.ItemTemplate>
    <DataTemplate>
        <Image Source="{Binding ImageUrl}"
                />
    </DataTemplate>
</controls:Pivot.ItemTemplate>

The binding to ImageUrl is for the property on the Photo data object. Sorry it was a string instead of a Uri, cheap and easy on my side I suppose.

namespace MyPhotoApp.Flickr
{
    public class Photo
    {
        public string Id { get; set; }
        public string Title { get; set; }
        public string ImageUrl { get; set; }
    }
}

So this way the image will automatically load. A nice improvement I should have made was write some better code to make sure the images are ready to go and maybe even fade in as they’re downloaded, but for a two-minute slide show, this was all pretty easy.

ItemContainerStyle for PivotItems

By default, the user experience guidelines for the pivot control and pivot items are nicely set in the default styles and templates. You’ll note without using the item container style that there is spacing between the edge of the photos and the edge of the phone’s screen.

This is the 12 pixel margin on the left and right of the pivot items; when using the standard phone styles for things like buttons and textblocks, which include another 12px margin, this means there should be 24px on the left and right of the actual visual things inside the pivot. Things align nicely in that situation.

But this is a photo viewer, so we actually want to show as much of the photos as possible – so this container style says “for each new item we are creating, let’s just set the margin and padding properties to 0!”.

<controls:Pivot.ItemContainerStyle>
    <Style TargetType="controls:PivotItem">
        <Setter Property="Margin" Value="0"/>
        <Setter Property="Padding" Value="0"/>
    </Style>
</controls:Pivot.ItemContainerStyle>

Navigation service usage

Inside this app, the navigation service is used to go to the slideshow page. Since the slideshow page has its nice view model data bound into the DataContext, I need to use the navigation service to get the photoset ID into the page. By using the URI inside the nav service instead of using a global variable or app-level call or property set, this means that the tombstoning for the app just works.

If you press the Start key while in a photoset, then hit back, you’ll come back to that photoset instead of the homepage for the app.

Now being a sample/demo app, I didn’t fully implement the right tombstoning behavior: I also should have actually stored the pivot selected index and restored that, so they would be on the same photo, etc. I also don’t using isolated storage in this app for simplicity sake, but that must be done for a real production app of course.

I also don’t properly do null/empty string checks in the calls, so if no photosets are returned and you try navigating, you’ll get a null ref exception. It’s demo code!

Moving to the sub-page

private void OnButtonClick(object sender, RoutedEventArgs e)
{
    NavigationService.Navigate(new Uri(
        string.Format(
        "/Views/SlideshowPage.xaml?id={0}",
        ((Photoset)SetList.SelectedItem).Id),
        UriKind.Relative));
}

Getting the photoset from the URI in the slideshow page

In the page’s OnNavigatedTo event handler, I read the property from the query string if I can. I only load photos if I find a photoset ID. This does have the trouble that you’ll see a blank bit of nothing if there are no photos returned.

Also if you look at the implementation, you’ll note that the loading automatically happens in the view model – so I don’t have to have the code in the slideshow file worry at all about whether to show the loading indicator or not. That’s all declaratively setup in XAML. Nice.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    var context = DataContext as SlideshowViewModel;
    if (context != null)
    {
        string id;
        if (NavigationContext.QueryString.TryGetValue("id", out id))
        {
            context.LoadPhotos(id);
        }
    }
}

And since all IDs in Flickr are to be treated as UTF8 strings, I don’t have to parse or convert the string value from the query string into an integer or anything like that.

General memory note

Note that you’ll want to watch your memory consumption if you go this route.

MyPhotoApp.zip download. Hope this helps.

Jeff Wilcox is a Software Engineer at Microsoft in the Open Source Programs Office (OSPO), helping Microsoft engineers use, contribute to and release open source at scale.

comments powered by Disqus