Overriding ThemeResources in Windows 8.1 Apps
Windows 8.1 offers a new markup extension called ThemeResource, this allows for dynamic theming of your app at runtime. There is also a new RequestedTheme property on FrameworkElement, which allows for retargeting specific islands of content. I was working on a sample today and wanted to write a short post to show how to put this all together with the combination of ThemeDictionaries to create a dynamic app.
ThemeResource
When you create a new Windows 8.1 app it behooves you use the built-in brushes and styles. This keeps your app consistent with the Windows experience, and also manages cases where users change their underlying system preferences for things like accessibility. Microsoft provides a reference of the XAML theme resources. To use them in Windows 8 you would just use a StaticResource.
<Border Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> </Border>
While StaticResource still works, with Windows 8.1, you can use the new ThemeResource markup extension for these references.
<Border Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> </Border>
This allows the resources to be updated dynamically whenever changes to the theme occur, previously this required an application restart. With the dynamic themes comes a broader reach for theme changing. To allow this, Microsoft added a RequestedTheme property to the FrameworkElement object. This allows you to change themes within the context of any element, even nested.
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <TextBlock Text="Default Theme" Foreground="{ThemeResource ApplicationForegroundThemeBrush}" /> <StackPanel RequestedTheme="Light" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <TextBlock Text="Light Theme" Foreground="{ThemeResource ApplicationForegroundThemeBrush}" /> <StackPanel x:Name="DarkContainer" RequestedTheme="Dark" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <TextBlock Text="DarkTheme" Foreground="{ThemeResource ApplicationForegroundThemeBrush}" /> </StackPanel> </StackPanel> </StackPanel>
You can also change these values at runtime in code.
DarkContainer.RequestedTheme = ElementTheme.Dark;
Overrides with ThemeDictionaries
You can override any of these existing styles by specifying a ResourceDictionary as part of the ThemeDictionaries. Each dictionary must specify a key with one of the following names:
- Default
- Dark
- Light
- HighContrast
- HighContrastBlack
- HighContrastWhite
- HighContrastCustom
The ThemeDictionaries documentation provides all the rules, but the HighContrast key is the default for all high contrast options and the Default key is the fallback when a theme name that cannot be located. Inside the dictionary you can specify any resource override from the full list of theme resource keys.
<ResourceDictionary.ThemeDictionaries> <ResourceDictionary x:Key="Default"> <SolidColorBrush x:Key="ApplicationPageBackgroundThemeBrush" Color="Magenta" /> </ResourceDictionary> </ResourceDictionary.ThemeDictionaries>
There is one caveat that I notice in my samples, if you specify an ApplicationPageBackgroundThemeBrush, it’s best to specify the value for all themes, in my case Dark and Light. Until I made this change the Light theme wasn’t using the default white background brush I was looking for. This only seems to be an issue with the page background key, but this code fixed it
<ResourceDictionary.ThemeDictionaries> <ResourceDictionary x:Key="Dark"> <SolidColorBrush x:Key="ApplicationPageBackgroundThemeBrush" Color="Magenta" /> </ResourceDictionary> <ResourceDictionary x:Key="Light"> <SolidColorBrush x:Key="ApplicationPageBackgroundThemeBrush" Color="#FFFFFFFF" /> </ResourceDictionary> </ResourceDictionary.ThemeDictionaries>