Visually Located

XAML and GIS

Creating a Custom Async Dialog for your Win8 Apps Part 1 - LoginDialog

For a recent Windows Store App, I needed to create a way for people to log into it. I could have easily created a simple control or page that allowed the user to enter their login information, but I wanted to work with the new async/await architecture. I thought that creating a login dialog that fit this pattern would be a fun adventure. I wanted to follow the same pattern found in the MessageDialog class so it would be easy to use and understand how it works. This is part one of a two part blog. Part one will cover creating a LoginDialog class that only handles the ability to login. Part two will cover changing that class to be a more generic CustomDialog class that allows for custom content. By the end of the blog we’ll create a LoginDialog that will display a login to the user that looks a lot like the login you get from the People app.

image

This first stab at the dialog will focus on the ability to login, so we need a class that will hold the information the user enters.

public class LoginResult
{
    public string Name { get; set; }
    public string Password { get; set; }
}

Next, let’s stub out what the API will look like. As I said, I wanted this to follow the same pattern as the MessageDialog, so I’ll create a class that closely mirrors that functionality

public class LoginDialog
{
    public LoginDialog(string title) { }
 
    public LoginDialog(string title, string content) : this(title) { }
 
    public IAsyncOperation<LoginResult> ShowAsync() { }
}

Notice that this is just a plain class. It is not a UIControl. This means that you cannot place this into your XAML, you can only create and show it from code. The LoginDialog is not a control, but it does need to display a control. There are two ways you could go about doing this. One is to create a class that is the control to display, the other is to create the control in code within the LoginDialog itself. The first is probably the easiest, but I wanted to accomplish this with only one class. I could have made an internal control that is only used by the LoginDialog (and this is the easier way to go and I’ll show using it later) but I wanted to try doing this with only one class. For now I’ll just stub out the creation of the actual control within the ShowAsync method. This method needs to return an IAsyncOperation of type LoginResult. To create an instance of IAsyncOperation you use the static Run method of the AsyncInfo.

private Popup _popup;
private TaskCompletionSource<LoginResult> _taskCompletionSource;
 
public IAsyncOperation<LoginResult> ShowAsync()
{
    _popup = new Popup { Child = CreateLogin() };
    if (_popup.Child != null)
    {
        _popup.IsOpen = true;
    }
    return AsyncInfo.Run(token => WaitForInput(token));
}
 
private Task<LoginResult> WaitForInput(CancellationToken token)
{
    _taskCompletionSource = new TaskCompletionSource<LoginResult>();
 
    token.Register(OnCanceled);
    
    return _taskCompletionSource.Task;
}

The WaitForInput method allows me to create the TaskCompletionSource that will be used to return the result. TaskCompletionSource is an awesome class that allows you to set the result at a later time. The WaitForInput method returns the Task property of the TaskCompletionSource. This allows for some async goodness. The code will wait until the SetResult method is called on the TaskCompletionSource.

To set the result of TaskCompletionSource and ultimately of the ShowAsync method we need two methods, one for when the dialog is canceled and one for when the user has entered their information. The cancel method will handle if the user clicked the cancel button or, if the application cancels the IAsyncOperation within the code. Note: I tried using the cancelation token for the cancel method but that did not do anything.

private void OnCompleted()
{
    var result = new LoginResult();
    result.Name = _userTextBox.Text;
    result.Password = _passwordTextBox.Password;
 
    _popup.IsOpen = false;
    _taskCompletionSource.SetResult(result);
}
 
private void OnCanceled()
{
    // null to indicate dialog was canceled
    LoginResult result = null;
 
    _popup.IsOpen = false;
    _taskCompletionSource.SetResult(result);
}

This is the basics of what we need, however windows store apps have so many visual states and sizes to be aware of. This means that the dialog needs to be aware of a few different events. If the app is on a small tablet the dialog needs to adjust for the virtual keyboard. If your app supports portrait the dialog needs to resize itself correctly.

// adjust for different view states
private void OnWindowSizeChanged(object sender, WindowSizeChangedEventArgs e)
{
    if (_popup.IsOpen == false) return;
 
    var child = _popup.Child as FrameworkElement;
    if (child == null) return;
 
    child.Width = e.Size.Width;
    child.Height = e.Size.Height;
} 
 
// Adjust the name/password textboxes for the virtual keyuboard
private void OnInputShowing(InputPane sender, InputPaneVisibilityEventArgs args)
{
    var child = _popup.Child as FrameworkElement;
    if (child == null) return;
 
    var transform = _passwordTextBox.TransformToVisual(child);
    var topLeft = transform.TransformPoint(new Point(0, 0));
 
    // Need to be able to view the entire textblock (plus a little more)
    var buffer = _passwordTextBox.ActualHeight + 10; 
    if ((topLeft.Y + buffer) > sender.OccludedRect.Top)
    {
        var margin = topLeft.Y - sender.OccludedRect.Top;
        margin += buffer;
        child.Margin = new Thickness(0, -margin, 0, 0);
    }
}
 
private void OnInputHiding(InputPane sender, InputPaneVisibilityEventArgs args)
{
    var child = _popup.Child as FrameworkElement;
    if (child == null) return;
 
    child.Margin = new Thickness(0);
}

We only want to subscribe to these events when the user shows the dialog (within the ShowAsync method), and we want to unsubscribe from the events when the dialog closes (OnCanceled and OnCompleted methods). Another event that is good to listen to is the KeyDown event of the window. If the user presses the escape key you should cancel the dialog.

private void SubscribeEvents()
{
    Window.Current.SizeChanged += OnWindowSizeChanged;
    Window.Current.Content.KeyDown += OnKeyDown;
 
    var input = InputPane.GetForCurrentView();
    input.Showing += OnInputShowing;
    input.Hiding += OnInputHiding;
}
 
private void UnsubscribeEvents()
{
    Window.Current.SizeChanged -= OnWindowSizeChanged;
    Window.Current.Content.KeyDown -= OnKeyDown;
 
    var input = InputPane.GetForCurrentView();
    input.Showing -= OnInputShowing;
    input.Hiding -= OnInputHiding;
}

The only thing I’ve left out is the creation of the actual dialog. For this I used a cool github project called XAML Conversion by Petr Onderka. I created the XAML in a user control and used this tool to convert it to code. For the most part it worked really well. It did require that you put children of panels into the Child tag.

<Border Background=""Red"" Height=""80"">
    <Border.Child>
        <TextBlock Text="Hello"/>
    </Border.Child>
</Border>

Download the source and sample application today. This application takes advantage of the light styles I blogged about before.

Easily create light themed styles for your Win8 Settings Flyout

One of the things I love about Windows Store apps is their ability to integrate with the system. One of these integration points is the Settings Charm. I’m not going to show you how to create a settings flyout. There are already lots of examples out there that do this. There are even some helpers like the SettingsFlyout available in Callisto and the helper created by Jerry Nixon. Recently Tim Heuer made a change to the SettingsFlyout to set the background to white. This change allows your app to follow the guidelines. He also added a property that allows you to change what you want the background to be if you are not a fan of white.

This change to the background works great if your app has a light requested theme. If you are using the default dark theme then the new background on the flyout becomes a nightmare. It’s a nightmare because now you have to style all of the controls you use for your settings to work properly with a light theme. You could easily start changing the brushes of various properties, but this doesn’t look very polished. You could restyle the controls and start to guess and what colors to use for this or that, but you’ll forget something (at least I did, and it was a lot).

There has to be an easy way to get light themed styles at runtime, right? I couldn’t find one, but I did find a way to easily create them. The resources and styles used by controls within the app are generally not found within the app itself. The app requests a theme from the system and that theme has all of resources and styles. This allows you to create custom controls and use those brushes and have the control be styled properly for both light and dark themed apps. These resources are not found within your app, but they are on your machine.

The styles and resources for WinRT are found at C:\Program Files (x86)\Windows Kits\8.0\Include\WinRT\Xaml\Design. This folder has two files in it. The themeresource.xaml file has all of the brushes, and other resources like font size and opacity. The generic.xaml file has all of these resources as well as all of the styles for each control. We can use these files to build our light themed styles.

To start with, open the project you need the light styles in, and add a new resource dictionary to store all of our light styles. In the Solution Explorer window, right click the Common folder and select Add –> New Item…

image

In the Add New Item dialog, select Resource Dictionary and name it LightStandardStyles.xaml

image

We then need to add this xaml to our App.xaml. Open the App.xaml and copy the line that adds the StandardStyles.xaml, paste it directly under that line and change the name to LightStandardStyles.

<ResourceDictionary Source="Common/StandardStyles.xaml"/>
<ResourceDictionary Source="Common/LightStandardStyles.xaml"/>

Let’s say you need to have a light themed button. Open LightStandardStyles.xaml and also open C:\Program Files (x86)\Windows Kits\8.0\Include\WinRT\Xaml\Design\generic.xaml.  Search for TargetType="Button" in the file. Copy the style and paste the style into LightStandardStyles. Give the style the key LightButtonStyle. Next open themeresource.xaml and collapse the “Default” and “HighContrast” ResourceDictionary. Now it’s as simple as searching for the brushes used within the button style and copy/paste them from the themeresource file and paste them into LightStandardStyles.

After doing this, the file should look like this.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 
    <SolidColorBrush x:Key="ButtonBackgroundThemeBrush" Color="#B3B6B6B6" />
    <SolidColorBrush x:Key="ButtonBorderThemeBrush" Color="#33000000" />
    <SolidColorBrush x:Key="ButtonDisabledBackgroundThemeBrush" Color="#66CACACA" />
    <SolidColorBrush x:Key="ButtonDisabledBorderThemeBrush" Color="#1A000000" />
    <SolidColorBrush x:Key="ButtonDisabledForegroundThemeBrush" Color="#66000000" />
    <SolidColorBrush x:Key="ButtonForegroundThemeBrush" Color="#FF000000" />
    <SolidColorBrush x:Key="ButtonPointerOverBackgroundThemeBrush" Color="#D1CDCDCD" />
    <SolidColorBrush x:Key="ButtonPointerOverForegroundThemeBrush" Color="#FF000000" />
    <SolidColorBrush x:Key="ButtonPressedBackgroundThemeBrush" Color="#FF000000" />
    <SolidColorBrush x:Key="ButtonPressedForegroundThemeBrush" Color="#FFFFFFFF" />
    <SolidColorBrush x:Key="FocusVisualBlackStrokeThemeBrush" Color="Black" />
    <SolidColorBrush x:Key="FocusVisualWhiteStrokeThemeBrush" Color="White" />
 
    <Style x:Key="LightButtonStyle" TargetType="Button">
        <Setter Property="Background" Value="{StaticResource ButtonBackgroundThemeBrush}" />
        <Setter Property="Foreground" Value="{StaticResource ButtonForegroundThemeBrush}"/>
        <Setter Property="BorderBrush" Value="{StaticResource ButtonBorderThemeBrush}" />
        <Setter Property="BorderThickness" Value="{StaticResource ButtonBorderThemeThickness}" />
        <Setter Property="Padding" Value="12,4,12,4" />
        <Setter Property="HorizontalAlignment" Value="Left" />
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="FontFamily" Value="{StaticResource ContentControlThemeFontFamily}" />
        <Setter Property="FontWeight" Value="SemiBold" />
        <Setter Property="FontSize" Value="{StaticResource ControlContentThemeFontSize}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Grid>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal" />
                                <VisualState x:Name="PointerOver">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                                       Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ButtonPointerOverBackgroundThemeBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                                       Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ButtonPointerOverForegroundThemeBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                                       Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ButtonPressedBackgroundThemeBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                                       Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ButtonPressedForegroundThemeBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                                       Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ButtonDisabledBackgroundThemeBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                                       Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ButtonDisabledBorderThemeBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                                       Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ButtonDisabledForegroundThemeBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="FocusStates">
                                <VisualState x:Name="Focused">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="FocusVisualWhite"
                                                         Storyboard.TargetProperty="Opacity"
                                                         To="1"
                                                         Duration="0" />
                                        <DoubleAnimation Storyboard.TargetName="FocusVisualBlack"
                                                         Storyboard.TargetProperty="Opacity"
                                                         To="1"
                                                         Duration="0" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Unfocused" />
                                <VisualState x:Name="PointerFocused" />
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Border x:Name="Border"
                                Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Margin="3">
                            <ContentPresenter x:Name="ContentPresenter"
                                              Content="{TemplateBinding Content}"
                                              ContentTransitions="{TemplateBinding ContentTransitions}"
                                              ContentTemplate="{TemplateBinding ContentTemplate}"
                                              Margin="{TemplateBinding Padding}"
                                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                        </Border>
                        <Rectangle x:Name="FocusVisualWhite"
                                   IsHitTestVisible="False"
                                   Stroke="{StaticResource FocusVisualWhiteStrokeThemeBrush}"
                                   StrokeEndLineCap="Square"
                                   StrokeDashArray="1,1"
                                   Opacity="0"
                                   StrokeDashOffset="1.5" />
                        <Rectangle x:Name="FocusVisualBlack"
                                   IsHitTestVisible="False"
                                   Stroke="{StaticResource FocusVisualBlackStrokeThemeBrush}"
                                   StrokeEndLineCap="Square"
                                   StrokeDashArray="1,1"
                                   Opacity="0"
                                   StrokeDashOffset="0.5" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Next it’s as simple as prefixing each of these resources with ‘Light’. Doing this results in the following file

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 
    <SolidColorBrush x:Key="LightButtonBackgroundThemeBrush" Color="#B3B6B6B6" />
    <SolidColorBrush x:Key="LightButtonBorderThemeBrush" Color="#33000000" />
    <SolidColorBrush x:Key="LightButtonDisabledBackgroundThemeBrush" Color="#66CACACA" />
    <SolidColorBrush x:Key="LightButtonDisabledBorderThemeBrush" Color="#1A000000" />
    <SolidColorBrush x:Key="LightButtonDisabledForegroundThemeBrush" Color="#66000000" />
    <SolidColorBrush x:Key="LightButtonForegroundThemeBrush" Color="#FF000000" />
    <SolidColorBrush x:Key="LightButtonPointerOverBackgroundThemeBrush" Color="#D1CDCDCD" />
    <SolidColorBrush x:Key="LightButtonPointerOverForegroundThemeBrush" Color="#FF000000" />
    <SolidColorBrush x:Key="LightButtonPressedBackgroundThemeBrush" Color="#FF000000" />
    <SolidColorBrush x:Key="LightButtonPressedForegroundThemeBrush" Color="#FFFFFFFF" />
    <SolidColorBrush x:Key="LightFocusVisualBlackStrokeThemeBrush" Color="Black" />
    <SolidColorBrush x:Key="LightFocusVisualWhiteStrokeThemeBrush" Color="White" />
 
    <Style x:Key="LightButtonStyle" TargetType="Button">
        <Setter Property="Background" Value="{StaticResource LightButtonBackgroundThemeBrush}" />
        <Setter Property="Foreground" Value="{StaticResource LightButtonForegroundThemeBrush}"/>
        <Setter Property="BorderBrush" Value="{StaticResource LightButtonBorderThemeBrush}" />
        <Setter Property="BorderThickness" Value="{StaticResource ButtonBorderThemeThickness}" />
        <Setter Property="Padding" Value="12,4,12,4" />
        <Setter Property="HorizontalAlignment" Value="Left" />
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="FontFamily" Value="{StaticResource ContentControlThemeFontFamily}" />
        <Setter Property="FontWeight" Value="SemiBold" />
        <Setter Property="FontSize" Value="{StaticResource ControlContentThemeFontSize}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Grid>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal" />
                                <VisualState x:Name="PointerOver">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                                       Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource LightButtonPointerOverBackgroundThemeBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                                       Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource LightButtonPointerOverForegroundThemeBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                                       Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource LightButtonPressedBackgroundThemeBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                                       Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource LightButtonPressedForegroundThemeBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                                       Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource LightButtonDisabledBackgroundThemeBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                                       Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource LightButtonDisabledBorderThemeBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                                       Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource LightButtonDisabledForegroundThemeBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="FocusStates">
                                <VisualState x:Name="Focused">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="FocusVisualWhite"
                                                         Storyboard.TargetProperty="Opacity"
                                                         To="1"
                                                         Duration="0" />
                                        <DoubleAnimation Storyboard.TargetName="FocusVisualBlack"
                                                         Storyboard.TargetProperty="Opacity"
                                                         To="1"
                                                         Duration="0" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Unfocused" />
                                <VisualState x:Name="PointerFocused" />
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Border x:Name="Border"
                                Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Margin="3">
                            <ContentPresenter x:Name="ContentPresenter"
                                              Content="{TemplateBinding Content}"
                                              ContentTransitions="{TemplateBinding ContentTransitions}"
                                              ContentTemplate="{TemplateBinding ContentTemplate}"
                                              Margin="{TemplateBinding Padding}"
                                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                        </Border>
                        <Rectangle x:Name="FocusVisualWhite"
                                   IsHitTestVisible="False"
                                   Stroke="{StaticResource LightFocusVisualWhiteStrokeThemeBrush}"
                                   StrokeEndLineCap="Square"
                                   StrokeDashArray="1,1"
                                   Opacity="0"
                                   StrokeDashOffset="1.5" />
                        <Rectangle x:Name="FocusVisualBlack"
                                   IsHitTestVisible="False"
                                   Stroke="{StaticResource LightFocusVisualBlackStrokeThemeBrush}"
                                   StrokeEndLineCap="Square"
                                   StrokeDashArray="1,1"
                                   Opacity="0"
                                   StrokeDashOffset="0.5" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

With this you can create a light style pretty quickly. But today, and today only, you can download the light styles that I created. This resource file has light themed styles for the ToggleSwitch, TextBox, ComboBox, Button (including a LightAppBarButtonStyle) and all of the standard TextBlock styles.

Easily create light themed styles for your Win8 Settings pane

One of the things I love about Windows Store apps is their ability to integrate with the system. One of these integration points is the Settings Charm. I’m not going to show you how to create a settings flyout. There are already lots of examples out there that do this. There are even some helpers like the SettingsFlyout available in Callisto and the helper created by Jerry Nixon. Recently Tim Heuer made a change to the SettingsFlyout to set the background to white. This change allows your app to follow the guidelines. He also added a property that allows you to change what you want the background to be if you are not a fan of white.

This change to the background works great if your app has a light requested theme. If you are using the default dark theme then the new background on the flyout becomes a nightmare. It’s a nightmare because now you have to style all of the controls you use for your settings to work properly with a light theme. You could easily start changing the brushes of various properties, but this doesn’t look very polished. You could restyle the controls and start to guess and what colors to use for this or that, but you’ll forget something (at least I did, and it was a lot).

There has to be an easy way to get light themed styles, right? I couldn’t find one, but I did find a way to easily create them. The resources and styles used by controls within the app are generally not found within the app itself. The app requests a theme from the system and that theme has all of resources and styles This allows you to create custom controls and use those brushes and have the control be styled properly for both light and dark themed apps. Because these resources are not available to us, we’ll need to create our own.

This trick can be done using either Visual Studio or Blend. For this blog I’m going to show examples using Visual Studio. The first step is to create a new Windows Store project. It’s best just to use the Blank App project as you will not need any of the extra stuff installed with the other ones. Open the App.xaml file (not App.xaml.cs) and set the RequestedTheme to be Light

<Application x:Class="LightStyles.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="using:LightStyles"
             RequestedTheme="Light">

Next, we want to add a new resource dictionary to store all of our styles. In the Solution Explorer window, right click the Common folder and select Add –> New Item…

image

In the Add New Item dialog, select Resource Dictionary and name it LightStandardStyles.xaml

image

We then need to add this xaml to our App.xaml. Open the App.xaml and copy the line that adds the StandardStyles.xaml, paste it directly under that line and change the name to LightStandardStyles.

<ResourceDictionary Source="Common/StandardStyles.xaml"/>
<ResourceDictionary Source="Common/LightStandardStyles.xaml"/>

To start us off, I’ll create a light theme style for a Button. Open the MainPage.xaml file. and place a Button within the Grid. In the designer or the document outline window, right click the button and select Edit Template –> Edit a Copy…

image

Name the style LightThemeButtonStyle and place it in the current document. For some of the steps it will be easier to have the style in the age and then cut/paste the style into LightStandardStyles.xaml. Now for the tedious part. We need to create new brushes that will replace the existing brushes. This is really easy to do using Visual Studio or Blend. While viewing the xaml, scroll through and find a brush. The first one is going to be the background of the button itself.

Note: To make naming these styles easier, copy the name of the style. eg: ButtonBackgroundThemeBrush

Open the Properties window and place your cursor on the line for the background. In the Properties windows, click on the green square next to the value of Value. In the popup, click on Convert to New Resource…

image

This allows you to create a local copy of the resource. Name the style LightButtonBackgroundThemeBrush and this time do place it in LightStandardStyles.xaml.

Note: The ButtonBackgroundThemeBrush is only used once, but it’s a good idea to do a find/replace of the name once you have created the new resource. When you do the replace, make sure that you match case and match whole word

Now you are on a rinse/repeat cycle. Follow the following steps for each of the brushes in the style. You can use these steps for every brush except for the brushes used in the states. Do not use these steps for brushes that use TemplateBinding, only the ones that use a StaticResource.

  1. Find brush
  2. Copy brush name
  3. In Properties window, click green square and select Convert to New Resource…
  4. Prefix ‘Light’ for the brush name (eg: ButtonBorderThemeThickness becomes LightButtonBorderThemeThickness)
  5. Place the resource into LightStandardStyles.xaml
  6. Replace (match case and whole word) any other uses of the resources with your new resource

During this process, you may encounter a message stating that changing the style will remove a visual state style. I never confirmed whether this actually did remove the state style, but to be safe I cut the style out of the template and pasted it into another file. Now we need to get the brushes changed for the different visual states. This is where having the style in the page will pay off. If you removed the states, now is the time to paste them back in. In your page add a Rectangle.

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <Button Style="{StaticResource LightThemeButtonStyle}"  />
    <Rectangle />
</Grid>

Now go to your visual states and find a state that changes a brush (eg: PointerOver changes the Background property of the border to ButtonPointerOverBackgroundThemeBrush). Copy the resource and place that as the Fill for the new Rectangle.

<Rectangle Fill="{StaticResource ButtonPointerOverBackgroundThemeBrush}" />

Now that you have the brush being used, you can follow the follow the six steps above to change all other resources in the file. Rinse/repeat this cycle until all of the brush resources have been updated. Now you can cut the resource out of the page and paste it into LightStandardStyles.xaml. This does sound tedious, but you can create a light style pretty quickly. But today, and today only, you can download the light styles that I created. This resource file has light themed styles for the ToggleSwitch, TextBox, ComboBox, Button (including a LightAppBarButtonStyle) and all of the standard TextBlock styles.