Practical support for custom light/dark theme resources on Windows Phone 7

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.

About these ads

6 thoughts on “Practical support for custom light/dark theme resources on Windows Phone 7

  1. Can you post the source to your project? I am having trouble getting the custom definitions to appear in Cider, and when I try to run the application in the emulator, I get errors:

    “Failed to assign property ‘System.Windows.ResourceDictionary.Source’.”

    • Looks like it’s an issue with Mango.

      I just test this with Mango and it’s fine. The error your describing probably menas you didn’t change “/ThemeManagement;” to reflect your project name (I ran into the same error while reproducing your issue).

    • Oh. My fault. I made Styles.xaml – ThemeResourceDictionary – but in App.xaml declared it as ResourceDictionary Source=”Styles.xaml”
      Probably this will same somebody some hours :) Be careful.

      • haha, I did not read comments. They downloaded and gave me your project “Theme_Problem_Repro”, then told me to apply that to our project. It took me some hours to identify the problem… until I read ur comment @.@ I fixed it =.=

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s