Dotnet Enabling Dark or Light Mode

Overview

Trying WPF in a personal project, I wanted to be able to switch to Dark or Light mode easily. While doing some research I discovered the use of Themes. Here is a small description on how I used it to switch from Dark to Light UI and allowing the addition of other themes.

Theme Management

Each Theme can be materialized in a Ressource Dictionary file. Create a Folder named Themes then create Ressource Dictionary files named Dark.xaml and Light.xaml.

1
2
3
4
5
6
7
8
9
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <SolidColorBrush Color="#2E3440" x:Key="BackGround"/>
    <SolidColorBrush Color="#15191d" x:Key="TitleColor"/>
    <SolidColorBrush Color="MidnightBlue" x:Key="ControlText"/>
    <SolidColorBrush Color="LightGray" x:Key="InputText"/>
    <SolidColorBrush Color="DarkGray" x:Key="ToolBox"/>
    <Image Source="Themes/Close_red_16x.png" x:Key="IconClose"/>
</ResourceDictionary>

x:Key will be used to referebce the colour. All themes must contains the same keys.

Theme Handling

To manage different themes, I have isolated some logic in a specific class : ‘Theme’

This class is used to repertoriate the list of available themes, and to change the current theme.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System.Windows;

namespace WpfApi
{
    internal class Theme
    {

        /// <summary>
        /// Change the theme of the application
        /// </summary>
        /// <param name="themeName"> name of the theme. must be consistent with the RessourceDictionary name and the entry in theme list.</param>
        public static void ChangeTheme(string themeName)
        {
            ResourceDictionary theme = new ResourceDictionary() { Source = new Uri($"Themes/{themeName}.xaml", UriKind.Relative) };
            Application.Current.Resources.Clear();
            Application.Current.Resources.MergedDictionaries.Add(theme);
        }

        /// <summary>
        /// List of available themes, must be consistent with Ressource  Dictionary names in Themes folder.
        /// </summary>
        private static List<string> _themes = new List<string> { "Light", "Dark" };
        public static List<string> GetThemes()
        {
            return _themes;
        }

    }
}

Main Window

In the main Window I will load a ComboBox with all the available Themes and I will set up the event to update the theme when the selection changes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
        #region Theme Management
        // Load Theme list into the Theme combobox
        private void LoadThemes()
        {
            foreach (string theme in Theme.GetThemes())
            {
                toolTheme.Items.Add(theme);
            }
            toolTheme.SelectedItem = "Dark";
        }
        private void ThemeSelection_Changed(object sender, SelectionChangedEventArgs e)
        {
            Theme.ChangeTheme((string)toolTheme.SelectedItem);
        }
        #endregion

I will call the LoadThemes after the Initialization is done.

In the Windows layout, I will add the following combo box and add reference to the ressources I have created to setup the color of the controls.

1
2
3
4
5
    ...
    <Grid Background="{DynamicResource BackGround}">
    ...
                <ComboBox Background="{DynamicResource ToolBox}" Foreground="{DynamicResource ControlText}" x:Name="toolTheme" Width="120" HorizontalAlignment="Center" SelectionChanged="ThemeSelection_Changed"/>
    ...

The tedious part is really to setup the colour for the controls in each windows.

Result

Light mode : Light Mode ScreenShot

Dark mode : Dark Mode ScreenShot

Note

One annoying part is the Windows title bar, that part is handled by the system color scheme.