Windows Phone 7's dark/light themes, which allow the user to choose between white-on-black or black-on-white, expose default resource keys for your application to consume. However, since this support doesn't extend to declaring your own light/dark resources, I thought I'd look into a practical solution.

I had a few requirements for the implementation:

  • It not should require user code to select between specific resources
  • It should support standard resource dictionary files and not require specific naming
  • It should not have a runtime performance penalty
  • It should work in the Visual Studio "cider" designer
  • It should work in Expression Blend

The result is a solution that manages 4 (or 4.5, depending on how you look at it) of those 5, and involves a subclassed ResourceDictionary

namespace ThemeManagement
{
    /// <summary>
    /// Provides automatic selection of resources based on the current theme
    /// </summary>
    public class ThemeResourceDictionary : ResourceDictionary
    {
        private ResourceDictionary lightResources;
        private ResourceDictionary darkResources;
        /// <summary>
        /// Gets or sets the <see cref="ResourceDictioary"/> to use when in the "light" theme
        /// </summary>
        public ResourceDictionary LightResources
        {
            get { return lightResources; }
            set
            {
                lightResources = value;
                if (!IsDarkTheme && value != null)
                {
                    MergedDictionaries.Add(value);
                }
            }
        }
        /// <summary>
        /// Gets or sets the <see cref="ResourceDictioary"/> to use when in the "dark" theme
        /// </summary>
        public ResourceDictionary DarkResources
        {
            get { return darkResources; }
            set
            {
                darkResources = value;
                if (IsDarkTheme && value != null)
                {
                    MergedDictionaries.Add(value);
                }
            }
        }
        /// <summary>
        /// Determines if the application is running in the dark theme
        /// </summary>
        private bool IsDarkTheme
        {
            get
            {
                if (IsDesignMode)
                {
                    return true;
                }
                else
                {
                    return (Visibility)Application.Current.Resources["PhoneDarkThemeVisibility"]
                        == Visibility.Visible;
                }
            }
        }
        /// <summary>
        /// Determines if the application is being run by a design tool
        /// </summary>
        private bool IsDesignMode
        {
            get
            {
                // VisualStudio sometimes returns false for DesignMode, DesignTool is our backup
                return DesignerProperties.GetIsInDesignMode(this) ||
                    DesignerProperties.IsInDesignTool;
            }
        }
    }
}

To test it out, I will create a page that contains an Image wrapped in a Border with the intention of using resources for the ImageSource and BorderBrush, respectively:

<Border BorderThickness="5" BorderBrush="{StaticResource ImageBorderBrush}">
    <Image Source="{StaticResource ThemedImage}" />
</Border>

(Notice that it's still using StaticResource)

Next, I created two resource dictionaries in /Resources/Light.xaml and /Resources/Dark.xaml, which looked like this:

<!-- Light.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <SolidColorBrush x:Key="ImageBorderBrush" Color="Red" />
    <BitmapImage x:Key="ThemedImage"
                 UriSource="/ThemeManagement;component/Content/ImageLight.png" />
</ResourceDictionary>

 

<!-- Dark.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <SolidColorBrush x:Key="ImageBorderBrush" Color="Green" />
    <BitmapImage x:Key="ThemedImage"
                 UriSource="/ThemeManagement;component/Content/ImageDark.png" />
</ResourceDictionary>

NOTE: ThemeManagement is the name of the project

Finally, I added the custom resource dictionary to the App.xaml:

<Application.Resources>
    <custom:ThemeResourceDictionary>
        <custom:ThemeResourceDictionary.LightResources>
            <ResourceDictionary Source="/ThemeManagement;component/Resources/Light.xaml" />
        </custom:ThemeResourceDictionary.LightResources>
        <custom:ThemeResourceDictionary.DarkResources>
            <ResourceDictionary Source="/ThemeManagement;component/Resources/Dark.xaml" />
        </custom:ThemeResourceDictionary.DarkResources>
    </custom:ThemeResourceDictionary>
</Application.Resources>

NOTE: ThemeManagement is the name of the project

Now we can take it for a test toast:

  1. Run the app
  2. Push the start button on the phone/emulator
  3. Go to Settings and change the theme to light
  4. Push Back a few times until you're in the app again

UPDATE: The above test will not work in Mango due to a unfortunate side effect of multitasking. The correct theme will be applied if the application is relaunched from Start.

Dark Theme Light Theme

We can also confirm that it works in Cider by switching to the design view:

However, since Cider cannot change themes, we can only see the dark theme.

Limitations

Unfortunately, this solution does not work in Expression Blend. For some reason, Blend parses the Resource BAML (compiled XAML) itself and manually supports Source and MergedDictionary attributes. Because of this, the custom ResourceDictionary code never runs and we Blend can't find the resources and you'll receive a warning when the project is opened.

The workaround is to select a theme file for design time resources (you can do so from the Blend warning dialog). I recommend choosing Light, since Cider forces us to use Dark. It is unfortunate that both themes can't be supported, though, as Blend is the one environment that supports swapping between them on the fly.