Create a real time switching night/dark mode for your Windows Phone App

Compare Windows Phone and Windows Desktop and you'll notice the amount of functionality available in WPF for the Desktop is much more powerful. One thing that's missing is the DynamicResource that we can use to change resources in the runtime. You can't do that so easily in Windows Phone, here's an implementation I fixed up to solve this issue.

Use Case

I have created an awesome application - a news reader, it works as expected and looks great. But the problem is my eyes strain when using the app at night due to the bright colour scheme I went for. I want to give the user an option to switch to a darker theme when they like and would like to do it in real time rather than having the user restart the app to apply the theme.

Theory

When developing applications for Windows Phone (Even Windows Universal Desktop/Tablet) applications you'll notice there isn't a DynamicResource you can only use StaticResource. A StaticResource will be resolved and assigned to the property during the loading of the XAML which occurs before the application is actually run. It will only be assigned once and any changes to resource dictionary ignored. A DynamicResource assigns an Expression object to the property during loading but does not actually lookup the resource until runtime when the Expression object is asked for the value. This defers looking up the resource until it is needed at runtime. A good example would be a forward reference to a resource defined later on in the XAML. Another example is a resource that will not even exist until runtime. It will update the target if the source resource dictionary is changed. So if we're stuck with using the StaticResource for my colour scheme how are we meant to change them in the same way a DynamicResource can be changed?

Implementation

Binding to a data source, that's how! Let me explain - we can use the great feature of binding controls to the back-end code in WPF. We specify a data source and implement INotifyPropertyChanged to allow the UI to be changed. What we can do is create a class for my theme that contains things like the page's background, header and foreground colour and from there we bind those fields to my controls in the UI. I am going to be creating this example using a Windows Desktop Universal Application, but the same logic can be applied to a Windows Phone application. The first thing you'd want to do is create a class, call it ThemeModel.cs and define what fields you'd like to use. I'll be going for the three generic ones I mentioned above. I have also implemented INotifyPropertyChanged - I won't be talking about this interface as a simple Google search will bring up all you need to know about it.
public class ThemeModel : INotifyPropertyChanged
 {

 private string _backgroundColour;
 private string _foregroundColour;
 private string _headerColour;

 public string BackgroundColour
 {
 get { return _backgroundColour; }
 set { _backgroundColour = value; OnPropertyChanged("BackgroundColour"); }
 }

 public string ForegroundColour
 {
 get { return _foregroundColour; }
 set { _foregroundColour = value; OnPropertyChanged("ForegroundColour"); }
 }

 public string HeaderColour
 {
 get { return _headerColour; }
 set { _headerColour = value; OnPropertyChanged("HeaderColour"); }
 }


 public event PropertyChangedEventHandler PropertyChanged;

 protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
 {
 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
 }
 }

This will work off as my base for any colours or fonts we want to be dynamically set in my application. Now we just need to create a new instance of the ThemeModel to be used - what I'm going to do is create a class called Global.cs that will house global objects that we can use throughout the application.
public static class Global
 {

 private static ThemeModel themeModel;

 public static ThemeModel ThemeModel
 {
 get { return themeModel; }
 set { themeModel = value; }
 }

 }

What I have in Global.cs is reference the ThemeModel class and encapsulated the field so I can use it anywhere in my application. Now I need to create a new instance of ThemeModel and store it in my Global class. To do this I am going to enter some code in my App.xaml.cs file's constructor.
 public App()
 {
 this.InitializeComponent();
 this.Suspending += OnSuspending;

 ThemeModel model = new ThemeModel();
 Global.ThemeModel = model;
 }

I am almost ready to start defining my theme's colours in my application. I am going to go back to Global.cs and add two methods that will be for my themes.
 public static void LightTheme()
 {
 ThemeModel.BackgroundColour = "#EEEEEE";
 ThemeModel.HeaderColour = "#BBBBBB";
 ThemeModel.ForegroundColour = "#000000";
 }

 public static void DarkTheme()
 {
 ThemeModel.BackgroundColour = "#333333";
 ThemeModel.HeaderColour = "#000000";
 ThemeModel.ForegroundColour = "#FFFFFF";
 }

The two methods here are my themes that I want to use in my application, a Light and Dark theme. As you can see the Light theme contains a light background and dark text, the Dark theme contains a dark background and light text. I need to add Global.LightTheme() in my App.xaml.cs just to let the app know what Theme to start off with. My completed App() constructor looks like this
 public App()
 {
 this.InitializeComponent();
 this.Suspending += OnSuspending;

 ThemeModel model = new ThemeModel();
 Global.ThemeModel = model;
 Global.LightTheme();
 }

Now I need to hook everything up to my UI - The code below is a final version of my MainPage.xaml - it contains a header and a main body with some text.
<Page
 x:Class="NightMode.MainPage"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:local="using:NightMode"
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 mc:Ignorable="d" x:Name="Page">

 <Grid Background="{Binding Theme.BackgroundColour, ElementName=Page}">
 
 <Grid.RowDefinitions>
 <RowDefinition Height="0.2*" />
 <RowDefinition Height="*" />
 </Grid.RowDefinitions>
 
 <!-- Header -->
 <Grid Grid.Row="0" Background="{Binding Theme.HeaderColour, ElementName=Page}">
 <Button Margin="20" x:Name="BtnChangeTheme" Content="Change Theme" Click="BtnChangeTheme_Click" />
 </Grid>
 <!-- / Header -->

 <!-- Body -->
 <Grid Grid.Row="1">
 <TextBlock FontSize="18" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis at neque eu nisl semper commodo. In venenatis id velit et malesuada. Quisque finibus condimentum turpis, at tincidunt sem imperdiet et. Nullam sodales augue ante, at cursus mi aliquet vitae. Integer sed turpis ac nisi placerat pharetra. Maecenas fringilla imperdiet leo, at cursus lectus mollis ut. Pellentesque ultricies sapien non convallis tincidunt. Nunc consectetur dui finibus nunc mollis porttitor vel auctor massa. Mauris elementum pharetra fringilla. In pellentesque et diam sed mattis. In at vulputate massa. Vivamus justo felis, faucibus rutrum interdum quis, eleifend euismod risus. Fusce lorem elit, finibus quis lacus sit amet, fermentum tempus nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum pharetra rutrum tristique." TextWrapping="Wrap" Margin="20" Foreground="{Binding Theme.ForegroundColour, ElementName=Page}" />
 </Grid>
 <!-- / Body -->

 </Grid>
</Page>

Here's some of the changes I had made:
  • Added x:Name="Page" to the page
  • Added a Button that will toggle me between the two different themes
  • Added a new field in my ThemeModel called ThemName so I can tell which theme is currently showing.
To see the full changes you can view the full source code on Github.

Running the application

Now that I have added everything in to my UI I can give the application a run and see the changes in action. When I run the app my Window shows a light based theme. Light Mode And when I click the "Change Theme" button on the upper left of the Window I am shown the Dark theme, this is all done at a click of a button thanks to our binding on the UI Element. Dark Mode

Other uses

I have shown you the example of using this method for a night but it can also be used as a middleman for multiple languages. You can define all the string keys in the "ThemeModel" class and have separate methods to inject a French or German language in your app without needing to restart the app.

Download

You can download the demo application used in this article from my Github repository. This application is provided as is with no support.

Sandeep Bansal is a Software Engineer focusing on Microsoft and Web technologies. He looks to develop both well designed and functional desktop/phone applications.

Comments