Adding Bindable Theme Support to your Windows Phone App

While developing the latest version of Instance, I built two different UI mockups that I liked. One was a light background with dark text, the other was inverse. I went back and forth on them and finally decided to just allow the user to pick. What at first seemed like a simple idea would actually require some work to make it as seamless as I wanted it to be. What I ended up with was a clever way to allow your apps to present different UI configurations depending on user preferences, device configurations, and OS options.

With the advent of 1080p screens for Windows Phone, this theme could also allow you to customize font sizes, provide higher resolution images when needed, and scale objects according to screen size.

For our purposes I’m going to keep our theme simple. We will define a name, a foreground color, and a background color.

    public class Theme
    {
        public String ThemeName { get; set; }
        public Color BackgroundColor { get; set; }
        public Color ForegroundColor { get; set; }

        public SolidColorBrush Foreground
        {
            get
            {    return new SolidColorBrush(ForegroundColor);    }
        }
        public SolidColorBrush Background
        {
            get
            {    return new SolidColorBrush(BackgroundColor);    }
        }
    }

It’s important to note that the Color class we are using here is System.Windows.Media.Color as there is a TypeConverter that allows us to define the value as a String in our XAML. Also, you’ll note the SolidColorBrush properties, Foreground and Background. We will actually be binding to these properties in the XAML.

The other thing we are going to do is create a ThemeCollection class that looks like this:

public class ThemeCollection : List<Theme> { }

One of the things that was important to me was to have the different themes defined in the XAML. As they are technically still part of the markup, I didn’t want them being defined in code. Here’s what a theme looks like in XAML. These will be defined in your App.Xaml file under Application.Resources.

To start, make sure at the top of your XAML you define a “local” namespace that looks like this:

xmlns:local="clr-namespace:ThemeControllerTest"

Instead of ThemeControllerTest, yours should be the namespace for your ThemeCollection class.

And here are the themes defined in XAML:


        <local:ThemeCollection x:Key="Themes">
            <local:Theme ThemeName="Default">
                <local:Theme.ForegroundColor>#DEDEDE</local:Theme.ForegroundColor><!--Light Gray-->
                <local:Theme.BackgroundColor>#333333</local:Theme.BackgroundColor><!--Dark Gray-->
            </local:Theme>
            <local:Theme ThemeName="Inverted">
                <local:Theme.ForegroundColor>#333333</local:Theme.ForegroundColor><!--Dark Gray-->
                <local:Theme.BackgroundColor>#DEDEDE</local:Theme.BackgroundColor><!--Light Gray-->
            </local:Theme>
        </local:ThemeCollection>

This XAML can be a bit confusing, so let me elaborate if you’re confused. What we’ve created here is an application resource. It is available anywhere in the application by calling App.Current.Resources["Themes"]. That would return you the ThemeCollection object with the two themes we defined as its items: Default and Inverted.

With our class created and our themes defined, it’s time to take a look at our ThemeController class.

    public class ThemeController : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public ThemeCollection Themes
        {
            get
            {
                return (ThemeCollection)App.Current.Resources["Themes"];
            }
        }

        [DefaultValue("Default")]
        public String DefaultTheme { get; set; }

        private Theme activeTheme;

        public Theme ActiveTheme
        {
            get
            {
                if (activeTheme == null)
                {
                    activeTheme = Themes.FirstOrDefault(t => t.ThemeName == DefaultTheme);
                }

                return activeTheme;
            }
            set
            {
                if (activeTheme != value)
                {
                    activeTheme = value;
                    NotifyPropertyChanged("ActiveTheme");
                }
            }
        }
    }

This is a pretty large chunk of code, so let’s take it one bite at a time.

    public class ThemeController : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

If you’re not familiar with INotifyPropertyChanged, here’s what it does. It provides an event that will execute when a property changes. Shocking, I know. More importantly, it allows the UI to update itself in the event the theme is changed.

public ThemeCollection Themes
    {
        get
        {
            return (ThemeCollection)App.Current.Resources["Themes"];
        }
    }

    [DefaultValue("Default")]
    public String DefaultTheme { get; set; }

Here we are creating a property that returns the ThemeCollection object we created in the Application Resources and a DefaultTheme value that we are defaulting to, handily enough, “Default”.

private Theme activeTheme;

    public Theme ActiveTheme
    {
        get
        {
            if (activeTheme == null)
            {
                activeTheme = Themes.FirstOrDefault(t => t.ThemeName == DefaultTheme);
            }

            return activeTheme;
        }
        set
        {
            if (activeTheme != value)
            {
                activeTheme = value;
                NotifyPropertyChanged("ActiveTheme");
            }
        }
    }

Edit
As Robert McLaws(@robertmclaws) points out, you don’t have to specify the property name in NotifyPropertyChanged() as we declared it with CallerMemberName:

private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")

So instead of NotifyPropertyChanged(“ActiveTheme”), we can just call NotifyPropertyChanged(). Thanks Robert!

Finally, we come to the ActiveTheme property. This is what we will bind to in our XAML. It returns either the Default theme if one hasn’t been selected, or the currently selected active theme.

We’ve got one more step before we can bind to our ThemeController: Create one in our Application Resources.

        <local:ThemeController x:Key="ThemeController" DefaultTheme="Default" />

Once that’s added in your App.xaml, either above or below our ThemeCollection, you are ready to start binding to it. Let’s see what that looks like.

<Grid x:Name="LayoutRoot" Background="{Binding ActiveTheme.Background, Source={StaticResource ThemeController}}">

As you can see, it does require a fair amount of markup. However, because we declared a default theme(remember?), our theme does show up in the Designer and we can see what it looks like without having to deploy the app.

Let’s put a ListBox on our page and bind it to our ThemeCollection.

            <ListBox ItemsSource="{StaticResource Themes}" SelectionChanged="ListBox_SelectionChanged">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding ThemeName}" Margin="6" />
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>

Add that to your page. You’ll notice the “SelectionChanged=’ListBox_SelectionChanged’” event handler. Let’s define the method now.

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.AddedItems.Count > 0)
            {
                ((App.Current.Resources["ThemeController"]) as ThemeController).ActiveTheme = (Theme)e.AddedItems[0];
            }
        }

Run it and toggle between the themes. Here’s what you get:

default inverted

Congratulations! You just themed a Windows Phone App!

Ideas to consider:

  • You could use the device’s resolution to determine what images to display
  • You could use the device’s theme to automatically determine which theme to display
  • You could use the time of day to change the theme.  A day theme/night theme.

There are countless possibilities once you start playing with it.

Leave a Reply