.NET 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

Porting Visual Studio Achievements for WP to Windows 8 - Viewing and Sharing

03.25.2012
| 2500 views |
  • submit to reddit

We are getting close to the end of the series, this being the last technical article before a general review and summary. That being said, I highly recommend you follow along and read the previous parts before going any further:

Today I am talking about reading individual achievements and sharing data via the Share charm.

Viewing individual achievements

In VSA for Windows Phone, there is a UserDetail.xaml page that allows me to view individual achievements. It is displayed whenever the user taps on a tracked niner in the main list. Its structure looks like this:

It is the perfect layout for the task, but with Windows 8 I am dealing with a different screen format, so I will have to make some visual adjustments. In my Metro project, I created a blank page named UserDetail.xaml (in an attempt to preserve general naming conventions). The XAML for the content grid used here is imported in its entirety from the Windows Phone project, with minor adjustments to styles and controls - notice that, for example, the Description property is bound to a TextBlock instead of a WebBrowser:

    <Grid DataContext="{Binding Path=Instance.CurrentNiner,Source={StaticResource LocalBindingPoint}}" Background="{StaticResource ApplicationPageBackgroundBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="240"></RowDefinition>
            <RowDefinition Height="60"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>

        <Grid Grid.Row="1" Margin="0,0,0,10">
            <Image HorizontalAlignment="Left" Source="Images/achievements.png"></Image>
        </Grid>

        <StackPanel x:Name="stkUserPanel" Orientation="Horizontal">
            <Image Margin="20,0,0,0" Height="200" Width="200" Source="{Binding Avatar}"></Image>
            <StackPanel VerticalAlignment="Center" Margin="20,20,0,20">
                <TextBlock Foreground="Black" Text="{Binding Alias}" TextWrapping="Wrap"></TextBlock>
                <TextBlock Foreground="Gray" Text="{Binding Name}" TextWrapping="Wrap"></TextBlock>
                <TextBlock Foreground="Gray" Text="{Binding Caption}" TextWrapping="Wrap"></TextBlock>
                <TextBlock Foreground="Gray" Text="{Binding Points}" TextWrapping="Wrap"></TextBlock>
            </StackPanel>
        </StackPanel>

        <ListBox Margin="20,20,20,20" Grid.Row="2" ItemsSource="{Binding Achievements}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid Margin="0,0,0,30">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="10"></ColumnDefinition>
                            <ColumnDefinition Width="*"></ColumnDefinition>
                        </Grid.ColumnDefinitions>

                        <StackPanel Grid.Column="1" Orientation="Vertical">
                            <Grid>
                                <StackPanel HorizontalAlignment="Left" Margin="20,0,0,0" Orientation="Horizontal">
                                    <Image Height="64" Source="{Binding Icon}"></Image>
                                    <TextBlock FontWeight="Bold" Margin="20,10,0,0" HorizontalAlignment="Center" Text="{Binding Points}" TextWrapping="Wrap" Foreground="Gray"></TextBlock>
                                </StackPanel>
                                <TextBlock HorizontalAlignment="Right" VerticalAlignment="Center" Text="{Binding DateEarned}" TextWrapping="Wrap" Foreground="White"></TextBlock>
                            </Grid>

                            <StackPanel Margin="0,5,0,0">
                                <TextBlock HorizontalAlignment="Left" Text="{Binding FriendlyName}" TextWrapping="Wrap" Foreground="White"></TextBlock>
                                <StackPanel Margin="0,10,0,0">
                                    <TextBlock Margin="10,0,0,0" Height="120" Text="{Binding Description}"></TextBlock>
                                </StackPanel>
                            </StackPanel>
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>

One problem with this page is also the fact that images for individual achievements, as well as the profile image are not displayed. The reason behind this issue is the fact that the Niner model exposes a Avatar property, and the Achievement model - the Icon property. Both are of type Uri, and in Metro applications an Image control fails to bind its source to an Uri instance.

Fixing this is a matter of building a custom converter that will return a string value instead of Uri:

using System;
using Windows.UI.Xaml.Data;

namespace VSAW8.Converters
{
    class UriToString : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            Uri uri = (Uri)value;
            return uri.ToString();
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            string uri = (string)value;
            return new Uri(uri);
        }
    }
}

This converter is exposed through a XAML declaration in App.xaml:

<Application
    x:Class="VSAW8.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:VSAW8"
    xmlns:converters="using:VSAW8.Converters">

    <Application.Resources>      
        <ResourceDictionary>
            <!-- More resources here --!>
            <converters:UriToString x:Key="UriToString"></converters:UriToString>
        </ResourceDictionary>
    </Application.Resources>
</Application>

For every Image control in UserDetail.xaml I used a binding pattern like this:

Source="{Binding URI_PROPERTY, Converter={StaticResource UriToString}}"

To show the page listing detailed achievements for a user, I am using the SelectionChanged event handler for the main list. It is bound to an ObservableCollection<Niner>, so whenever a new item is selected, it is a separate Niner instance. This makes it really easy to get the data for the selected Channel9 member:

private void lstNiners_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    BindingPoint.Instance.CurrentNiner = (Niner)e.AddedItems.First();

    Frame.Navigate(typeof(UserDetail));
}

The very ugly raw achievement viewer now looks like this:

Focusing on the functional side, I managed to make the achievement reading process work - all data is displayed correctly and there are no broken bindings. Now it is time to implement some basic sharing.

Using the Share charm

The sharing process in Metro applications relies on DataTransferManager - a class that might be considered as the connecting link between your application and other applications that expose sharing capabilities. VSA for Windows 8 by itself does not expose sharing endpoints, but rather consumes them. Whenever the user page is loaded, I can share the following data - the user alias and the number of points he earned. To do this, I first need to acquire the DataTransferManager.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    var dataTM = Windows.ApplicationModel.DataTransfer.DataTransferManager.GetForCurrentView();
    dataTM.DataRequested += dataTM_DataRequested;
}

The DataRequested event handler determines what data to share. In my case, it is going to be plain text with:

void dataTM_DataRequested(Windows.ApplicationModel.DataTransfer.DataTransferManager sender, Windows.ApplicationModel.DataTransfer.DataRequestedEventArgs args)
{
    var request = args.Request;
    request.Data.Properties.Title = "VSA for WP8";
    request.Data.Properties.Description = "Achievement Report";
    request.Data.SetText(string.Format("{0} has earned {1} Visual Studio Achievement points.",
        BindingPoint.Instance.CurrentNiner.Alias, BindingPoint.Instance.CurrentNiner.Points.ToString()));
}

Whenever the Share charm is invoked, the user will be prompted with a list of applications that are capable of sharing data to a third-party service:

 

As you can see, the sharing engine is as easy to use as the one on Windows Phone. As long as the developer is familiar with ShareStatusTask and the likes, he will be able to easily use the Windows 8 sharing hooks. And since we're talking about sharing, I highly recommend reading this: