Window Forms Dark mode
Karen Payne
Posted on November 16, 2024
Introduction
Learn how to support dark mode for Windows Forms projects targeting .NET 9 Core Framework.
Although this is an experimental feature, dark mode appears to work fine.
Why dark mode?
There are customers who simply prefer dark mode while others it may be that light mode is difficult to work with, an accessibility issue. Either way having the ability to offer dark mode is a plus.
How to implement
Add the following PropertyGroup to your project file:
<PropertyGroup>
<NoWarn>$(NoWarn);WFO5001</NoWarn>
</PropertyGroup>
Or use #pragma warning disable WFO5001
directive to suppress the error where it occurs instead of disabling it for the entire project.
Next add to Program.cs Application.SetColorMode(SystemColorMode.Dark);
before Application.Run(new Form1());
.
Once the above is in place, build and run the project.
Dynamic mode
Rather than hard code dark mode, a better idea is to start off with light mode and provide an option for light, system or dark mode.
Example Window child form (included with source code) that provides a way to change and persist color mode to appsettings.json file.
In the provide source code this done using the following singleton class.
SetModeText
method accepts the selected color mode from the settings form and saves to appsettings.json.
/// <summary>
/// Represents the configuration settings for the application.
/// This class is implemented as a singleton to ensure a single instance of the configuration is used throughout the application.
/// </summary>
public sealed class Configuration
{
private static readonly Lazy<Configuration> Lazy = new(() => new Configuration());
public static Configuration Instance => Lazy.Value;
/// <summary>
/// Gets or sets the color mode for the application.
/// </summary>
/// <value>
/// A <see cref="SystemColorMode"/> value that indicates the current color mode of the application.
/// </value>
public SystemColorMode ColorMode { get; set; }
/// <summary>
/// Gets a value indicating whether the application is in dark mode.
/// </summary>
/// <value>
/// <c>true</c> if the application is in dark mode; otherwise, <c>false</c>.
/// </value>
public bool IsDarkMode => ColorMode == SystemColorMode.Dark;
/// <summary>
/// Gets or sets the text representation of the current color mode.
/// </summary>
/// <value>
/// A string that represents the current color mode. Possible values are "Dark mode", "Light mode", and "System mode".
/// </value>
/// <remarks>
/// This property is set based on the <see cref="ColorMode"/> property during the initialization of the <see cref="Configuration"/> class.
/// </remarks>
public string ModeText { get; set; }
/// <summary>
/// Gets or sets an array of mode names representing the different system color modes available.
/// </summary>
/// <value>
/// An array of strings where each string is the name of a system color mode.
/// </value>
/// <remarks>
/// This property is initialized with the names of the <see cref="SystemColorMode"/> enumeration values.
/// </remarks>
public string[] ModesArray { get; set; }
/// <summary>
/// Sets the mode text based on the specified <paramref name="mode"/> and updates the configuration file.
/// </summary>
/// <param name="mode">The system color mode to set.</param>
/// <remarks>
/// This method reads the current configuration from "appsettings.json", updates the visual configuration's system color mode,
/// and writes the updated configuration back to the file.
/// </remarks>
public void SetModeText(SystemColorMode mode)
{
var json = File.ReadAllText("appsettings.json");
ApplicationSetting? settings = JsonSerializer.Deserialize<ApplicationSetting>(json);
settings!.VisualConfiguration.SystemColorMode = mode.ToString();
File.WriteAllText("appsettings.json", JsonSerializer.Serialize(settings, Indented));
}
/// <summary>
/// Initializes a new instance of the <see cref="Configuration"/> class.
/// This constructor reads the configuration settings from the "appsettings.json" file and sets the <see cref="ColorMode"/> property.
/// </summary>
/// <remarks>
/// The constructor is private to enforce the singleton pattern. It reads the JSON configuration file, deserializes it to retrieve the
/// visual configuration settings, and sets the <see cref="ColorMode"/> property based on the deserialized value.
/// </remarks>
private Configuration()
{
// for use in SettingsForm ComboBox
ModesArray = Enum.GetValues(typeof(SystemColorMode))
.Cast<SystemColorMode>()
.Select(e => e.ToString())
.ToArray();
var json = File.ReadAllText("appsettings.json");
try
{
/*
* Read color mode, convert to enum and set ColorMode.
* Note, SystemColorMode in appsettings is case-insensitive.
* If the value is not a valid SystemColorMode, the ColorMode is set to System.
*/
var settings = JsonSerializer.Deserialize<ApplicationSetting>(json)!.VisualConfiguration;
var test = Enum.TryParse(settings.SystemColorMode, true, out SystemColorMode mode);
ColorMode = test ? mode : SystemColorMode.System;
}
catch (Exception)
{
ColorMode = SystemColorMode.System;
}
ModeText = ColorMode switch
{
SystemColorMode.Dark => "Dark mode",
SystemColorMode.Classic => "Light mode",
SystemColorMode.System => "System mode",
_ => ModeText
};
}
public static JsonSerializerOptions Indented => new() { WriteIndented = true };
}
In the constructor of the settings form, a ComboBox is loaded with Enum.GetValues(typeof(SystemColorMode))
followed by setting the current color mode in the ComboBox.
To set the mode, the change button is clicked which save the selection to appsettings.json followed by prompting the user to restart the application which reads the color mode in Program.cs, if the user decides not to restart the application, the next time the application starts the color is set.
All of the code is in the following project along with other NET9/C#13 features.
Special library
Also included is a class project that provides code to read color mode. The code to change color mode at runtime is not included as there is code in the first project and each developer may want to implement color mode changes differently.
To use the library, add the project as a dependency to a project. Add Application.SetColorMode(Configuration.Instance.ColorMode);
as shown below.
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.SetColorMode(Configuration.Instance.ColorMode);
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
}
}
Add the following section to appsettings.json.
{
"VisualConfiguration": {
"_comment": "Must be Dark, Classic or System which can be case-insensitive",
"SystemColorMode": "Dark"
}
}
Add the following to the Windows Form project, project file.
<PropertyGroup>
<NoWarn>$(NoWarn);WFO5001</NoWarn>
</PropertyGroup>
Source code
Standalone form project Class project
The following project is a mixture of NET9/C#13 features with dark mode.
Comments
Since dark mode is experimental some controls may not present as each developer wants them. For instance, a DataGridView. In the provided code check out the following language extension method.
public static void ColorizeDarkModeHeaders(this DataGridView source)
{
source.EnableHeadersVisualStyles = false;
source.ColumnHeadersDefaultCellStyle.BackColor = Color.Blue;
}
Which presents as follows.
Not what you expect?
If something does not appear correct, ask on Stackoverflow and when asking write a well formed question.
Summary
With the provided code, thanks to Microsoft developers can implement dark, system and light mode in Window Form projects.
Take time to study the provided code rather than simply copy and pasting provided code into a project.
As with any experimental feature there may be changes in future editions of the .NET Framework.
Posted on November 16, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.