.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 - Layout and basic bindings

03.11.2012
| 3781 views |
  • submit to reddit

One of the great features that is currently highlighted for Windows 8 and specifically WinRT is the low barrier of entry for existing Windows Phone and Silverlight developers in general. There was already a case of an existing Windows Phone application being ported to the new OS, but I decided to actually document the entire porting process in its core.

Let's start with the very basics and assume that I have no code whatsoever on my local machine. I am heading over to the CodePlex page and download the source ZIP. OI course, you might want to choose a version control system and pull the source that way, but I will leave that at your own discretion. The default solution is of course opened in Visual Studio 2010. Your solution contents should resemble this:

 

Now let's working on the actual new project. Launch Visual Studio 11 Beta and click on New Project. Select Visual C# > Windows Metro Style and finally - Blank Application. Give a unique name to the project. I personally used VSAW8. Open BlankPage.xaml - this is the main work page that will display all necessary user information. For convenience purposes, I renamed it to MainPage.xaml.

NOTE: Don't forget to also rename the class references. Renaming the file in Solution Explorer alone won't do much to help this. 

Let's take a look at what we have in the MainPage.xaml for the Windows Phone project.

<phone:PhoneApplicationPage 
    x:Class="VisualStudioAchievements.MainPage"
    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"
    xmlns:local="clr-namespace:VisualStudioAchievements"
    xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="728"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="False">
    
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <toolkit:PerformanceProgressBar VerticalAlignment="Top" x:Name="perfBar" IsIndeterminate="True" Visibility="{Binding Path=Instance.HidePerfBar,Source={StaticResource LocalBindingPoint},Converter={StaticResource PerfBarConverter}, ConverterParameter=p}"></toolkit:PerformanceProgressBar>

        <Grid Margin="0,20,0,20" Width="440" Background="{StaticResource PhoneAccentBrush}">
            <Image HorizontalAlignment="Left" Source="Images/pagelogo.png"></Image>
        </Grid>
        

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="20,0,20,20">
            <TextBlock Visibility="{Binding Path=Instance.Niners.Count,Source={StaticResource LocalBindingPoint},Converter={StaticResource CountToVisibility}}" TextAlignment="Left" FontFamily="{StaticResource PhoneFontFamilySemiLight}" FontSize="{StaticResource PhoneFontSizeLarge}" TextWrapping="Wrap" Text="add users to track their visual studio achievements." Foreground="Gray"></TextBlock>
            <ListBox Foreground="Black" ItemsSource="{Binding Path=Instance.Niners,Source={StaticResource LocalBindingPoint}}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid Width="440" HorizontalAlignment="Center" Margin="0,0,0,20" Tap="StackPanel_Tap" Tag="{Binding Alias}" Height="180">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="80"></RowDefinition>
                                <RowDefinition Height="100"></RowDefinition>
                            </Grid.RowDefinitions>
                            
                            <toolkit:ContextMenuService.ContextMenu>
                                <toolkit:ContextMenu>
                                    <toolkit:MenuItem Tag="{Binding Alias}" Click="mnuPin_Click" Header="pin to start" x:Name="mnuPin"></toolkit:MenuItem>
                                    <toolkit:MenuItem Tag="{Binding Alias}" Click="mnuDelete_Click" Header="delete" x:Name="mnuDelete"></toolkit:MenuItem>
                                </toolkit:ContextMenu>
                            </toolkit:ContextMenuService.ContextMenu>
                            
                            <StackPanel>
                                <TextBlock Foreground="Black" Grid.Row="0" FontFamily="Segoe WP Black" FontSize="25" Text="{Binding Alias,Converter={StaticResource BoldNameConverter}}"></TextBlock>
                                <TextBlock Foreground="Black" Grid.Row="0" Text="{Binding Name}"></TextBlock>
                            </StackPanel>

                            <Grid Grid.Row="1">
                                <Grid.Background>
                                    <ImageBrush ImageSource="Images/niner_back.png"></ImageBrush>
                                </Grid.Background>
                                <StackPanel Orientation="Horizontal">
                                    <Image Source="{Binding Avatar}" Height="78" Width="78" HorizontalAlignment="Left" Margin="10,0,0,0"></Image>
                                    <TextBlock Margin="20,0,0,0" FontSize="40" Text="{Binding Points}" Grid.Column="1" TextAlignment="Right" Foreground="White" VerticalAlignment="Center"></TextBlock>
                                </StackPanel>
                            </Grid>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Grid>
 
    <phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton x:Name="btnAddUser" Click="btnAddUser_Click" IconUri="/Images/appbar.add.png" Text="add"/>
            <shell:ApplicationBarIconButton x:Name="btnCompare" Click="btnCompare_Click"  IconUri="/Images/appbar.arrow.down.up.png" Text="compare"/>
            <shell:ApplicationBar.MenuItems>
                <!--<shell:ApplicationBarMenuItem Text="settings"/>-->
                <shell:ApplicationBarMenuItem x:Name="btnAbout" Click="btnAbout_Click"  Text="about"/>
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>
</phone:PhoneApplicationPage>  

The foundation here is obviously the main grid. I am using the Silverlight Toolkit here as well - something I will have to avoid in the WinRT project since it has no support for it whatsoever. There is also a fragment that builds the Application Bar. In Windows 8, that part of the application itself is handled in a different manner, so I simply copied and pasted the existing grid layout in my Metro project without the extra user components.

First thing that you might notice is the fact that I was using the Tap event handler to track when a user taps on a user entry. In Metro applications, there is no Tap event.

 

Once this part is removed, it is time to fix the binding. For VSA WP, I had a class named BindingPoint.cs. I copied it over to the Metro project as is - all I had to fix is adding references to System.ComponentModel in order to be able to access INotifyPropertyChanged. Also, I needed proper references to System.Collections.ObjectModel (for ObservableCollection<T>) and my self-designed Niner model.

NOTE: Generally, there should not be any problems directly importing classes that represent your own data models. as long as you are correctly referencing any cross-addressed classes. For example, if you use a custom model that references another model, remember to add both to the project.

One thing that I find interesting in particular is the fact that I am no longer using the Dispatcher class to perform cross-thread calls. Instead, inside BindingPoint, for the NotifyPropertyChanged method, I am using Task.Run that is invoked with await.

async private void NotifyPropertyChanged(String info)
{
    if (PropertyChanged != null)
    {
         await Task.Run(() => { PropertyChanged(this, new PropertyChangedEventArgs(info)); });
    }
}

I also need to add some converters, that are bound inside MainPage.xaml. The first one is CountToVisibilityConverter - a class that helps me decide whether a descriptive TextBlock is shown or not, depending on whether there are users in the set of tracked Channel9 members. The class implements IValueConverter, and there is one change that you have to be aware - the signature for the Convert method has changed. Here is what you had in the Windows Phone project:

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 

And here is what you have in a Metro project:

public object Convert(object value, Type targetType, object parameter, string language)

Notice the subtle change of the CultureInfo parameter to string. Other than that, the value conversion idea remains the same. Also, don't forget that XAML references to both binding and conversion classes require prior declaration. I chose to have those inside 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">

    <Application.Resources>      
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Common/StandardStyles.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <local:BindingPoint x:Key="LocalBindingPoint"></local:BindingPoint>
            <local:CountToVisibilityConverter x:Key="CountToVisibility"></local:CountToVisibilityConverter>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Once all adjustments were made, I ended up with a grid layout that lacks all phone references:

    <Grid Background="{StaticResource ApplicationPageBackgroundBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Grid Margin="0,20,0,20" Width="440">
            <Image HorizontalAlignment="Left" Source="Images/pagelogo.png"></Image>
        </Grid>


        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="20,0,20,20">
            <TextBlock Visibility="{Binding Path=Instance.Niners.Count,Source={StaticResource LocalBindingPoint},Converter={StaticResource CountToVisibility}}" TextAlignment="Left" TextWrapping="Wrap" Text="add users to track their visual studio achievements." Foreground="Gray"></TextBlock>
            <ListBox Foreground="Black" ItemsSource="{Binding Path=Instance.Niners,Source={StaticResource LocalBindingPoint}}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid Width="440" HorizontalAlignment="Center" Margin="0,0,0,20" Tag="{Binding Alias}" Height="180">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="80"></RowDefinition>
                                <RowDefinition Height="100"></RowDefinition>
                            </Grid.RowDefinitions>

                            <StackPanel>
                                <TextBlock Foreground="Black" Grid.Row="0" FontFamily="Segoe WP Black" FontSize="25" Text="{Binding Alias}"></TextBlock>
                                <TextBlock Foreground="Black" Grid.Row="0" Text="{Binding Name}"></TextBlock>
                            </StackPanel>

                            <Grid Grid.Row="1">
                                <Grid.Background>
                                    <ImageBrush ImageSource="Images/niner_back.png"></ImageBrush>
                                </Grid.Background>
                                <StackPanel Orientation="Horizontal">
                                    <Image Source="{Binding Avatar}" Height="78" Width="78" HorizontalAlignment="Left" Margin="10,0,0,0"></Image>
                                    <TextBlock Margin="20,0,0,0" FontSize="40" Text="{Binding Points}" Grid.Column="1" TextAlignment="Right" Foreground="White" VerticalAlignment="Center"></TextBlock>
                                </StackPanel>
                            </Grid>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Grid>

To test how well it works, I created a simple loop that adds a bunch of users to to the Niners collection inside BindingPoint.

for (int i = 100; i < 120; i++)
{
    Niner niner = new Niner();
    niner.Alias = "Den";
    niner.Avatar = new Uri("http://www.blogymate.com/Exclusive/E1711201121246.jpg");
    niner.Caption = "TEST";
    niner.Name = "Den Delimarsky";
    niner.Points = i;

    BindingPoint.Instance.Niners.Add(niner);
}

The layout that I ended up with is this:

 

Obviously, major parts are still missing and I will be working on general adjustments, but I managed to demonstrate how the basic XAML and binding structures remained almost unchanged and the effort to port this part of the project is minimal.