PhoneThemeManager: allow your app to have the Light, Dark, or Inverted theme with 1 line of code

25 January 2012

I’ve just pushed a small library that I created this evening to the world and wanted to share details on the short project in this post. (NuGet assembly, NuGet source, project on GitHub)

One of the most common feature requests that I receive for 4th & Mayor is a setting in the app to force it to always use the “Light” theme, overriding the dark/light theme that the user’s set for their theme.

Having a light background is a nice contrasting design look for the phone, and the Mail application always does this. I personally always submit mostly light theme screen shots for my applications to the Windows Phone Marketplace, it just looks better and nice to me – but I rarely use all-light themed apps other than Mail.

In preparing for the next update of my app, v3.3, I’ve coded up this capability and I’m sharing it ahead of time.

Technical Overview

In 7.0 there were a few ways to do theme overriding, sometimes through a few silly platform bugs that we left in there – but it’s actually not an easy thing to do, and many people end up overriding every single style in their app to get the effect that they are going for.

This helper library is designed for altering the entire application’s running instance, not for providing per-page theme overriding.

In researching possible ways to solve this, I did come across this page on windowsphonegeek that talks about ways to merge in styles or to programmatically override resources that are brushes.

I decided to create a system to walk through the known values and names for all the core theme resources of colors, brushes, plus the light/dark theme visibility and opacity properties.

In your app, you simply call `ThemeManager.ToLightTheme()`, `ThemeManager.ToDarkTheme()`, `ThemeManager.InvertTheme`, to force this. You need to have this call happen inside your App.xaml.cs file, in the constructor, after the Initialize calls.

You cannot change or call this more than once, so if you offer a setting for users to “Force Light”, use a MessageBox to inform them that their setting will be used the next time they start the app. You will have to read their setting right away when they start up the app. Once styles start applying the values, you could start to get inconsistent results.

The code walks and updates the Color instances, then walks the Brush resources and sets their Colors to be the original Color instances, just in case.

I have placed the ThemeManager class within the Microsoft.Phone.Controls namespace for this to be easy to add to your App.xaml.cs file.

You should have an App constructor like this:

/// <summary>
/// Constructor for the Application object.
/// </summary>
public App()
    // Global handler for uncaught exceptions. 
    UnhandledException += Application_UnhandledException;

    // Standard Silverlight initialization

    // Phone-specific initialization


    // Other code that might be here already...

What’s themed

By default this is what happens:

  • The resources for foreground, background, all the contrast/chrome/etc. colors and brushes are updated
  • The light and dark theme visibility and opacity resources are updated
  • The background brush of the Frame is set explicitly to the color (may have a negative performance impact!)
  • The System Tray whenever a page navigation completes
  • The ApplicationBar of a page if set immediately

I’ve added a simple OverrideOptions enum (static) to the ThemeManager that can be used to disable the auto-behaviors I’ve added.

Newing up AppBars

If you have code in your app like “var ab = new ApplicationBar”, beware that that application bar will take on the system’s actual theme colors by default, and not the overridden light/dark coloring that happens with the app.

If you need to new up an ApplicationBar, you should use the convenience method of `ThemeManager.CreateApplicationBar()` or use the extension method on app bar that I added, `MatchOverriddenTheme`, to set the color values.

What’s not themed

Unfortunately this cannot theme MessageBox at all.

Talking about fill rate performance

I’ve designed the system so that resources are only overridden when needed.

If your app uses ToLightTheme to force the light theme, and the user is running the Light theme already, nothing happens – it’s a no-op.

Although updating resources has no negative effect really on the app’s performance, the trouble is setting the Background color of the phone application’s frame.

The frame is always present and may add a fill count of 1.0 to every single page in your app.

Anything above a fill rate of 2-3 is not a good thing, so you may notice a degraded experience. Might want to inform your users of that when providing the option to force the light theme, for example.

A note about your battery

Many Windows Phones use AMOLED or similar technology where bright colors, such as the background color used in the Light theme, will use a lot of power. Please respect your users and realize that long-running apps probably should not force the Light option.

Consider only making such a “Force Light Theme” option as a setting that users opt-in to, as opposed to always overriding the theme.

About custom themes

When I designed this library, I thought about offering a ton of capability in it for using “branding” colors, modifying the accent brush, etc., but instead decided to tackle just one thing. So the name ThemeManager is a little overkill maybe, but it’s where we are for now.

Get the bits

NuGet Binary

The binary is super easy to use. With NuGet just add the PhoneThemeManager package reference.

NuGet Source File

Instead of adding yet another assembly to your project, just add the single source file (or add it to your existing shared library, etc.) by using the PhoneThemeManager.Source package.

GitHub Repo

Fork and enjoy

Hope this helps.

Jeff Wilcox is a Software Engineer at Microsoft in the Open Source Programs Office (OSPO), helping Microsoft engineers use, contribute to and release open source at scale.

comments powered by Disqus