Mobile Zone is brought to you in partnership with:

Den is a DZone Zone Leader and has posted 460 posts at DZone. You can read more from them at their website. View Full User Profile

Building an Imgur Client for Windows Phone - Part 3 - Viewing Image Details

01.21.2013
| 3091 views |
  • submit to reddit

If you missed the previous parts of the series, you can read those here:

Today I am going to talk about a way to view image details, as well as the entire image. In the previous articles I showed you how it is possible to experiment with "lazy loading". However, UI virtualization is already done automatically in the ListBox, so we don't have to worry about that for now. We also do not have to worry about data virtualization, because the initial data set is relatively small.

You already know that we have an internal converter that would allow us to load thumbnail images. Going back to the fact that we already have the deserialized data set, let's use direct binding to the image instead, to give users a better understanding of what they are looking for.

Also, remember that if you are using a custom ItemsPanel (e.g. with a WrapPanel), like I did in the previous example, you need to be really careful with the items that you are loading at the same time, as the automatic virtualization chain will be broken.

In this example, I am modifying the DataTemplate to be bound directly DeserializedHomeImages collection. I am also displaying the image title when it is shown on the main page:

<DataTemplate>
    <Grid Height="240" Width="480">
        <Image Stretch="UniformToFill" 
               Source="{Binding Link}"></Image>
        <Grid Height="80" VerticalAlignment="Bottom">
            <Grid.Background>
                <SolidColorBrush Color="Black" Opacity=".7"></SolidColorBrush>
            </Grid.Background>
            <TextBlock VerticalAlignment="Center" Style="{StaticResource PhoneTextTitle2Style}" Text="{Binding Title}"></TextBlock>
        </Grid>
    </Grid>
</DataTemplate>

This results in something like this:


Do you need to keep the diagnostics counters? Yes. As you are working with different images that are bound to a list, you will see how easy it is to track the performance hits and whether you need to make adjustments to how many images should be bound and displayed at the same time.

Let's now talk about a way one could view the image details, as well as the image itself. Imgur strips all EXIF metadata from the image, so we can't focus on that. Instead, we can get:

  • the date and time the image was uploaded
  • the account that created it
  • how much bandwidth was used by the image
  • number of upvotes
  • number of downvotes
  • total score
Create a new XAML page in your project and name it ImageDetails. This will be the container that will display the details that we need. Some of the metadata will be in plain view, other will be hidden unless the user explicitly asks for it. So how am I going to pass the selected image to the new page? By creating a model I can bind to.

Go to the MainPageViewModel class and add a new property - CurrentImage:

private ImgurImage _currentImage;
public ImgurImage CurrentImage
{
    get
    {
        return _currentImage;
    }
    set
    {
        if (_currentImage != value)
        {
            _currentImage = value;
            NotifyPropertyChanged("CurrentImage");
        }
    }
}

For the ListBox itself, I need to determine when an item is selected - that can be done with the help of the SelectionChanged event handler:

private void mainList_SelectionChanged_1(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
    if (mainList.SelectedItem != null)
    {
        MainPageViewModel.Instance.CurrentImage = (ImgurImage)mainList.SelectedItem;
        mainList.SelectedItem = null;
        NavigationService.Navigate(new System.Uri("/ImageDetails.xaml", System.UriKind.Relative));
    }
}

The idea is simple - when a ListBox is bound to a collection of custom models, you can cast the selected item as an instance of that model - that's why I can easily get the ImgurImage instance instead of referencing it by the name or some other unique identifier. 

Once the instance is selected, I am assigning it to the binding endpoint I created above and am navigating to the page that will display the image metadata.

Here is the default layout that I created for the starting detail viewer:

<phone:PhoneApplicationPage
    x:Class="Imagine.ImageDetails"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="PortraitOrLandscape" Orientation="Portrait"
    mc:Ignorable="d"
    shell:SystemTray.IsVisible="False"
    DataContext="{Binding Path=Instance.CurrentImage, Source={StaticResource MainPageViewModel}}">

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        
        <Grid x:Name="ContentPanel" Grid.Row="0">
            <StackPanel Margin="0,0,0,24">
                <TextBlock Style="{StaticResource PhoneTextTitle3Style}" TextWrapping="Wrap" Text="{Binding Title}"></TextBlock>
                <TextBlock Style="{StaticResource PhoneTextSmallStyle}" TextWrapping="Wrap" Text="{Binding DateTime, Converter={StaticResource CompositeToDateConverter}}"></TextBlock>
                <StackPanel Margin="8,0,0,0" Orientation="Horizontal">
                    <Image VerticalAlignment="Center" Source="{Binding Converter={StaticResource ThemeDetector}, ConverterParameter=score}" Height="48"></Image>
                    <TextBlock VerticalAlignment="Center" Text="{Binding Score}"></TextBlock>

                    <Image Margin="12,0,0,0" VerticalAlignment="Center" Source="{Binding Converter={StaticResource ThemeDetector}, ConverterParameter=user}" Height="48"></Image>
                    <TextBlock VerticalAlignment="Center" Text="{Binding AccountUrl, Converter={StaticResource UserNameChecker}}"></TextBlock>
                </StackPanel>
            </StackPanel>
        </Grid>

        <ScrollViewer VerticalAlignment="Top" x:Name="scvImage" 
                      HorizontalScrollBarVisibility="Disabled" 
                      Grid.Row="1">
            <Image 
                x:Name="imgPreview" 
                Stretch="Uniform" 
                Source="{Binding Link}"></Image>
        </ScrollViewer>
    </Grid>
</phone:PhoneApplicationPage>

There are several things that I should mention here. First of all, take a look at the DataContext that is set for the page. Since I only need the currently selected image, I am binding it to the CurrentImage property in the main page viewmodel (MainPageViewModel.cs).

Second, notice that two images inside the header are not bound to a direct URI but rather to a converter. This is there for a reason - if the user switches the theme from light to dark and vice-versa, I need to keep two versions of the same image. To give you a better idea, take a look at the two screenshots below:



At this point, the converter responsible for detecting the proper theme should just fetch a different image location depending on two factors - the parameter passed to it, that will determine the image type, and the current theme. The implementation looks like this:

using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media.Imaging;

namespace Imagine.Core
{
    public class ThemeDetector : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            var theme = Application.Current.Resources["PhoneDarkThemeVisibility"];
            var visibility = (Visibility)theme;

            switch ((string)parameter)
            {
                case "score":
                    {
                        if (visibility == Visibility.Visible)
                            return new BitmapImage(new Uri("/Images/appbar.gauge.75.png", UriKind.Relative));
                        else
                            return new BitmapImage(new Uri("/Images/appbar.gauge.75.light.png", UriKind.Relative));
                    }
                case "user":
                    {
                        if (visibility == Visibility.Visible)
                            return new BitmapImage(new Uri("/Images/appbar.people.png", UriKind.Relative));
                        else
                            return new BitmapImage(new Uri("/Images/appbar.people.light.png", UriKind.Relative));
                    }
                default:
                    return null;
            }
        }

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

It relies on the well-known DarkThemeVisibility resource trick. You can easily modify this to fetch any image in the application itself, as well as external ones.

Next comes the date. The default DateTime property in the ImgurImage class is a UNIX timestamp   C# does not have a default class to handle that, but knowing the way it is generated, we can easily implement a converter that will give the necessary date in the standard format:

using System;
using System.Windows.Data;

namespace Imagine.Core
{
    public class CompositeToDateConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            DateTime current = new DateTime(1970, 1, 1).AddSeconds(System.Convert.ToDouble(value));
            return current;
        }

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

With the current XAML layout for the page, it will be correctly rendered (and the image re-sized) in both landscape and portrait mode. The Image control is placed inside a ScrollViewer to ensure that disproportionally large images (e.g. height has a much higher value than width) are viewable.

You can download the current source code here.