Custom Styling in Windows Store Apps

Sign in to queue

Description

 

Now that we have the data properly formatted and bound to the controls, this video will focus on custom styling and interface elements in our app.

 

Setting up Custom Styles

As we've discussed in the previous Quickstarts, by basing our migration effort on one of the Windows 8 Templates in Visual Studio 2012, our project came with a Common and Data folder filled with classes and resources ready to use. The Common\StandardStyles.xaml file provides the default styling for all the elements in the template and is a great place to start when customizing your own interface.

However, to keep things organized, we created a new file and also placed it in the Common folder called CustomStyles.xaml. By using inheritance, we are able to keep most of our custom edits in CustomStyles.xaml and leave StandardStyles.xaml relatively unchanged.

clip_image001

It may be designer preference, but one exception to this separation between CustomStyles.xaml and StandardStyles.xaml was the inclusion of custom of global colors, which we put at the very top of the StandardStyles.xaml file since we would want them to affect all subsequent styles, including the defaults.

StandardStyles.xaml

<ResourceDictionary
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vsm="using:Windows.UI.Xaml"
    >
<!-- Non-brush values that vary across themes –>
<ResourceDictionary.ThemeDictionaries  […]  >

<!-- System Wide Overrides -->
<SolidColorBrush x:Key="ApplicationForegroundThemeBrush"
    Color="#FF222E3D" />
<SolidColorBrush x:Key="ApplicationPageBackgroundThemeBrush"
    Color="#FFD4DEB7" />
<SolidColorBrush x:Key="ApplicationPointerOverForegroundThemeBrush"
    Color="#FFFFFFFF" />
<SolidColorBrush x:Key="ApplicationPressedForegroundThemeBrush"
    Color="#FFFFFFFF" />
<SolidColorBrush x:Key="ButtonDisabledForegroundThemeBrush"
    Color="#99222E3D" />
<SolidColorBrush x:Key="ListViewItemPlaceholderBackgroundThemeBrush"
    Color="#FF222E3D" />
<SolidColorBrush x:Key="ListViewItemOverlayBackgroundThemeBrush"
    Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="ListViewItemBackgroundThemeBrush"
    Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="BackButtonForegroundThemeBrush"
    Color="#FF222E3D"/>
<SolidColorBrush x:Key="BackButtonPressedForegroundThemeBrush"
    Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="BackButtonPointerOverBackgroundThemeBrush"
    Color="#00000000"/>

However, all other custom style definitions are kept grouped together in a single file called CustomStyles.xaml which in turn references StandardStyles.xaml as a merged dictionary:

CustomStyles.xaml

<ResourceDictionary
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vsm="using:Windows.UI.Xaml"
    xmlns:local="using:KhanAcademy.Common">
    
    <vsm:ResourceDictionary.MergedDictionaries>
        <vsm:ResourceDictionary Source="StandardStyles.xaml"/>
    </vsm:ResourceDictionary.MergedDictionaries>

In this way, custom styles we define in CustomStyles.xaml can be BasedOn template styles already defined in StandardStyles.xaml with only minimal change, if we only intend to customize a few properties of an existing template:

CustomStyles.xaml

<vsm:Style x:Key="TopicItemTextStyleTitle" 
           TargetType="TextBlock" 
           BasedOn="{StaticResource BaselineTextStyle}">
    <vsm:Setter Property="FontSize" Value="12"/>
    <vsm:Setter Property="LineHeight" Value="16"/>
    <vsm:Setter Property="FontWeight" Value="SemiBold"/>
    <vsm:Setter Property="Foreground" 
                Value="{StaticResource TopicItemForegroundTextBrush}"/>
    <vsm:Setter Property="VerticalAlignment" 
                Value="Center"/>
</vsm:Style>

Of course, we don't need to base our new elements on existing elements. We can always create new styles and templates from scratch as needed:

CustomStyles.xaml

<vsm:DataTemplate x:Key="TopicItemTemplate">
    <Grid HorizontalAlignment="Left" 
            Width="220" 
            Height="110" 
            Background="White" 
            Margin="0,0,2,2">
        <Grid.RowDefinitions>
            <RowDefinition Height="2"/>
            <RowDefinition Height="38"/>
            <RowDefinition Height="69"/>
        </Grid.RowDefinitions>
        <Rectangle Grid.Row="0" 
                    Width="220" 
                    Height="2" 
                    Fill="{Binding Color}"/>
        <TextBlock Grid.Row="1" 
                    Text="{Binding Name}" 
                    Style="{StaticResource TopicItemTextStyleTitle}" 
                    VerticalAlignment="Center" 
                    Padding="10,0,10,0"/>
        <TextBlock Grid.Row="2" 
                    Text="{Binding Description}" 
                    Style="{StaticResource TopicItemTextStyleBody}" 
                    TextWrapping="Wrap" 
                    VerticalAlignment="Top" 
                    Margin="10,5,10,5"/>
    </Grid>
</vsm:DataTemplate>
   

Notice that the DataTemplate shown above is assuming bindable properties of the elements it will be formatting, which we already reviewed in the Data Binding Quickstart.

Since CustomStyles.xaml is already merging in StandardStyles.xaml as a Resource Dictionary, we only need to reference CustomStyles.xaml directly in App.xaml to get access to the entire collection of custom and standard styles.

App.xaml

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Common/CustomStyles.xaml"/>
        </ResourceDictionary.MergedDictionaries>
        <x:String x:Key="AppName">KhanAcademy</x:String>
    </ResourceDictionary>
</Application.Resources>

 

Working with Template Selectors

As discussed in our Data Binding Quickstart, we wanted to implement a Hub type experience for the Khan Academy landing page. This meant that the interface would be displaying both collections of Playlists and individual playlist collections of Videos. These two types of objects would look different from each other, since Videos have a thumbnail graphic and Playlists only display their title and description.

The challenge was that our single Grid View would need to display both types of objects, but using different styling for each.So we needed a solution to switch templates based on object type.

If we look at the way the default Grid View Template Project works, we see that its ItemTemplate property references a specific StaticResource. In this case, the Standard250x250ItemTemplate description found in Common\StandardStyles.xaml:

GroupedItemsPage.xaml

<GridView
    x:Name="itemGridView"
    AutomationProperties.AutomationId="ItemGridView"
    AutomationProperties.Name="Grouped Items"
    Grid.RowSpan="2"
    Padding="116,137,40,46"
    ItemsSource=
        "{Binding Source={StaticResource groupedItemsViewSource}}"
    ItemTemplate="{StaticResource Standard250x250ItemTemplate}"
    SelectionMode="None"
    IsSwipeEnabled="false"
    IsItemClickEnabled="True"
    ItemClick="ItemView_ItemClick">

That is the way it often works. You use a specific, StaticResource reference on the ItemTemplate property of the container you have bound to your data.

But in the Khan Academy project, if you look at the GridView setup on Hub.xaml, you'll see that we are not using a StaticResource on the ItemTemplate property, but setting a value for the GridView’s ItemTemplateSelector property instead:

HubPage.xaml

<GridView
    x:Name="itemGridView"
    AutomationProperties.AutomationId="ItemGridView"
    AutomationProperties.Name="Grouped Items"
    Margin="0,-3,0,0"
    Padding="120,0,40,60"
    VerticalAlignment="Top"
    ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
    ItemTemplateSelector="{StaticResource TemplateSelector}"
    SelectionMode="None"
    IsItemClickEnabled="True"
    ItemClick="ItemView_ItemClick">

ItemTemplateSelector is referencing a local resource we added to the page resources:

HubPage.xaml

<Page.Resources>
<local:HubItemSelector x:Key="TemplateSelector"
    Topic="{StaticResource TopicItemTemplate}"
    Playlist="{StaticResource PlaylistItemTemplate}"
    Video="{StaticResource FeaturedVideoItemTemplate}" >
</local:HubItemSelector>   

Based on the return type of HubItemSelector, this ItemTemplateSelector will redirect the template to whatever template you define in the look up. So what does the class HubItemSelector actually look like?

HubItemSelector.cs

public class HubItemSelector : DataTemplateSelector
    {
        public DataTemplate Video { get; set; }
        public DataTemplate Playlist { get; set; }
        public DataTemplate Topic { get; set; }
        protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
        {
            DataItem dataItem = item as DataItem;
            if (dataItem.GetType() == typeof(VideoItem))
            {
                return Playlist;
            }
            else
            {
                return Topic;
            }
        }
    }

 

This technique can definitely come in handy if you are experimenting with Hub style interfaces and find yourself in a similar situation with your own app.

 

Semantic Zoom

Like Windows Phone, modern Window 8 apps are Touch ready. As such, Semantic Zoom is a powerful new control we can leverage in our apps. Semantic Zoom allows the user to switch views on a large body of data with a simple pinch gesture.

 image

This gives us a great high level, low level metaphor we can leverage in our apps, and is very effective when we want to provide the experience of "zooming out" on a data set and simplifying or aggregating the smaller pieces of content. Here is how it works.

If we look at the default landing page of a Grid App template project, we see the following structure:

clip_image012

After the Page is declared and has its Resources defined, the LayoutRootStyle grid is created with two main rows: a row for the header that meets the Windows 8 app styling guidelines, and a row for our content.

The template defines two elements that want to occupy Grid.Row="1". A GridView that is visible when the app is at normal size, and a ListView that is visible when the app is snapped. We will look at snapping in the next quickstart, but for now just realize that this collapsing of visibility is taken care of by the VisualStateManager also on the page.

If we look at HubPage.xaml in the Khan Academy app, though, we can see where SemanticZoom fits into the entire page structure.

clip_image013

With this change, we make SemanticZoom is the primary element in Grid.Row="1" as long as the app isn't snapped. The GridView element that used to be there can now be nested under the SemanticZoom.ZoomedInView property and will appear as expected. However, you now have a SemanticZoom.ZoomedOutView element you can fill with whatever you want.

The control is setup to support gestures automatically. By pinching the screen closed, the user will zoom out and the control will transition between the two containers automatically. While in a zoomed out state, if the user pinches outward, they will return to the default Zoomed In view.

But what about all that business with snapping we quickly brushed over? Snapping and other OS features will be the subject of our next quickstart.

If you have any questions, comments, or feedback feel free to join in the discussion.

Twitter: @rickbarraza, @joelmartinez

Embed

Download

Download this episode

The Discussion

Add Your 2 Cents