-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
Updated 8/10/2024]
After careful, long and intensive discussions, all of us decision makers around this feature came to the conclusion that we needed to move this feature to .NET 10. I don't exaggerate by saying, that I am probably not only one of the people most disappointed by that (still correct) decision, but also one of the most effected (@RussKie FYI), as you can imagine, since I need to reconceptualize quite some work which is based on this.
While nothing is planned to change in terms of APIs, we already know we will separate ColorMode
and VisualStylesMode
out into separate PRs, because it makes backwards compatibility testing and maintaining WAY easier.
That's why I've just close the respective PR #10985, and then I will cherry-pick commits starting end of next week (I need a few days to grief and recharge), so that we will have 2 new PRs ASAP, resembling the same work, which we aim to bring directly into Preview 1 for .NET 10.
Rationale
With a few new APIs which the Windows Team now made publicly available, we can start to introduce Dark Mode functionality for WinForms. This is one of our most requested customer features. By introducing CsWin32 to our WinForms repo, adapting more and more Windows Dark Mode Features overtime to WinForms is also comparatively easy to achieve.
What this feature is and is not
- We have NO intention to introduce full-blown theming support in WinForms with this.
- We want to be very mindful with this topic, since there are a lot of theming standards already out there.
- The API is meant as an opt in: You can use it, but if you do not, nothing should break. Existing theming concepts should continue to work as they are.
- For .NET 9, the new APIs will be all under the
Experimental
attribute, so they might be subject to change or even to be replaced.
Goals and Non-Goals of this first experimental iteration
Goal:
- In .NET 9+, any app just recompiling (or rolling forward) retains light theme.
- Allow an app wide dark theme setting (either explicitly dark or "use system light/dark").
- When high contrast is enabled, ignore this setting and use the high-contrast colors.
- Avoid having a mess of light & dark mode but allow overrides on a per window/control basis.
Non-Goals
- Support theming more than just light / dark mode.
- Support dark mode prior to Windows 11.
In addition, we figure that not only Dark Mode in particular is one of the most requested features, but it can also impose a real accessibility challenge for people - all the more, when users with visual impairments or on the neuro-diversity spectrum need to deal for example with an 80% dark and 20% bright control (like control's content dark, scrollbars light). We know, we cannot address every control, since some of them are not really themable, but we want to standardize with this what we can, to honor those requirements as best as possible, und make sure a control can be completely dark themed (or not at all).
General approach for applying Dark Mode
As with the existing APIs on Application, we have a Set method and a correlating property to control dark mode at application level:
Later I guess we should have an additional project setting, so that also the Designer could pick up that color scheme, and then we generate and inject via ApplicationConfiguration.Initialize()
.
If you pick System
, then the System setting is applied wherever possible.
(Challenging are TabControl
, MonthCalendar
and therefore DateTimePicker
- but it seems we have workarounds for that to mitigate this - more to follow soon.)
System
as a dark mode setting is a "logical ambient" property, means that any control with that setting looks at its parent - a top-level control without a parent would look at Application.ColorMode
and the Application using System
would then look at the System.
Versioning Visual Styles for current and future A11Y requirements
Visual Styles Mode for versioning how controls render in different versions of the Framework is essential to ensure backward compatibility. This is especially important when new accessibility laws require changes in the UI, such as increasing the minimum size, padding, or margins of controls.
In some cases, these adjustments can break the pixel-perfect layout of Forms or User Controls. Additionally, controls derived from a base control that changes how it requests space can potentially break other derived controls. Here are a few scenarios where versioning visual styles is required:
-
New Accessibility Laws: To comply with new accessibility standards, controls may need to have larger minimum sizes, padding, or margins. For example, adjusting the padding of a
TextBoxBase
derived control might not be feasible without breaking existing layouts. -
Dark Mode: Dark Mode can introduce contrast issues with closely positioned controls, especially when they have non-controllable border sizes or padding. Ensuring proper spacing and rendering adjustments in Dark Mode is crucial.
- High-DPI Modes: Controls with borders defined by fixed pixel widths rather than DPI-scaled thicknesses can lead to scaling issues. Adjustments may be needed to ensure these controls render correctly in high-DPI environments.
The APIs are added to Application and Control classes, incorporating "VisualStyles" in their names to handle these scenarios. This ensures that both new and existing applications can benefit from enhanced rendering while maintaining compatibility with previous versions of the Framework.
Form (Window) Title Bar control
There will be new APIs to allow customization of the Form's title bar, including setting colors for the window border, caption, and caption text. This is based on new official public Win32 APIs, ensuring consistent and customizable window theming across different applications.
The following is taken from the WinForms Async Feature demo, also showing, how closely related async and A11Y are in terms of styling, fluent design and responsive UIs (Only the first seconds are relevant, as they demo the changing of the color of the Window's title bar.)
These new APIs are basically a wrapper around (now) public Windows DWM API DwmSetWindowAttribute
using the respective attributes:
DWMWA_BORDER_COLOR
DWMWA_CAPTION_COLOR
DWMWA_TEXT_COLOR
DWMWA_WINDOW_CORNER_PREFERENCE
Current Progress
One thing which is already be important to know (and to have in the docs later, should we get this feature in for .NET 9): We will most likely not achieve dark mode for all controls from the get-go (probably even for some of them ever). Challenging controls are MonthCalendar
, DateTimePicker
and TabControl
at this point. Meanwhile, there seem to be workarounds available, which would mitigate issues with those controls.
- New API for DarkMode control on Application.
- New API for DarkMode control on Control.
- Mapping SystemColors -> Application.SystemColors/patch for DarkMode
- New API for controlling Form Titelbar Color/BorderColor/TextColor
- Scrollbars for Form/ScrollableControl/ListView/TreeView/ListBox/ComboBox
- Radio Button
- CheckBox
- ComboBox DarkMode
- Button DarkMode
- TreeView DarkMode
- ListView DarkMode
- TextBox/RichtextBox
- Strips - Still have issues, but the basics work fine!
- Groupbox Caption
- ColumnHeaders ListView Forground Text
- ListView Forground Group Caption (might be an issue to get it brighter).
- DataGridView (still needs some fine-tuning though in edge cases).
- TabControl (Scrollbars are working in DarkMode alright, but theming this thing would be a very improvised construct. If someone has a good idea or approach, have at it!)
- MonthCalendar - doesn't style with XP-styling at all, could be styled, if we disabled XP styling.
- DateTimePicker - uses MonthCalendar, so same applies.
Current list of new APIs
Note: We will for .NET 9 implement the new APIs under the Experimental
attribute, so we can collect and react to respective feedback, and then introduce the new APIs finally in .NET 10 based on that feedback.
[Subject to change by API Review]
// ***************************************
// *** Dark Mode
// ***************************************
namespace System.Windows.Forms
{
// Note: We would call it now SystemColorMode to acknowledge that at one point
// Windows might add more modes we need to pick up. So, just "DarkMode" in that case
// wouldn't do it. ColorMode would also not work, since it already exists.
// Note: Nothing to do with theming. We are just taking what Windows offers!
[Experimental("WFO9000")]
public enum SystemColorMode
{
Classic = 0 // Reason: We might later pick up Color Modes from Windows,
// which are also Light-based. This implies "original setting".
System = 1, // Takes the Mode from the Window OS Environment.
Dark = 2, // We are forcing the primary dark mode scheme.
}
public static partial class Application
{
[Experimental("WFO9000")]
public static SystemColorMode ColorMode { get; } // Returns the actual color mode (Classic or Dark - never System).
// Sets the Color mode for the Application. Every value can be passed. If a mode is not available,
// nothing happens. You can check the actual result with ColorMode.
[Experimental("WFO9000")]
public static bool SetColorMode(SystemColorMode colorMode)
// Returns the ColorMode of the Windows OS System, either Classic or Dark.
[Experimental("WFO9000")]
public static SystemColorMode SystemColorMode { get; }
}
public class TextBoxBase : Control
{
// For A11Y reasons, we un-shadow Padding for TextBoxBase to make Padding
// for TextBox based controls actually work. Will only have any effect, if
// VisualStylesMode is set to > Classic. Note, that we've modernized the
// Adorners in that case, also to visually indicating more default padding
// in ANY of the BorderStyle settings, to meet the minimum upcoming
// A11Y requirements of 24 pixels (Border-less 96dpi textboxes only have 18/22 Px).
- public new Padding Padding {get; set; }
}
public enum ControlStyles
{ .
.
.
/// <summary>
/// For certain UI-related color modes (Dark Mode/Light Mode), controls
/// can opt-in to automatically apply the appropriate DWM theming.
/// </summary>
[Experimental("WFO9001")]
ApplyThemingImplicitly = 0b00001000_00000000_00000000, // 0x00080000
}
// ***************************************
// Windows Title Bar Control (based on new official public Win32 APIs)
// ***************************************
public unsafe partial class Form
{
// Existing partial class members...
[Experimental("WFO9000")]
public void SetWindowBorderColor(System.Drawing.Color color)
[Experimental("WFO9000")]
public void SetWindowCaptionColor(System.Drawing.Color color)
[Experimental("WFO9000")]
public void SetWindowCaptionTextColor(System.Drawing.Color color)
[Experimental("WFO9000")]
public void SetWindowCornerPreference(WindowCornerPreference cornerPreference)
[Experimental("WFO9000")]
public enum WindowCornerPreference
{
Default = 0,
DoNotRound = 1,
Round = 2,
RoundSmall = 3
}
}
// ***************************************
// Visual Styles Versioning [Updated after API-Review]
// ***************************************
[Experimental("WFO9001")]
public enum VisualStylesMode : Short
{
Classic = 0, // was 1, saves time on initialization.
Disabled = 1, // was 0, saves time on initialization.
Net10 = 2
Latest = Short.Max
}
public static partial class Application
{
[Experimental("WFO9001")]
public static VisualStylesMode DefaultVisualStylesMode { get; }
[Experimental("WFO9001")]
public static void SetDefaultVisualStylesMode(VisualStylesMode styleSetting)
}
public unsafe partial class Control
{
[Experimental("WFO9001")]
public VisualStylesMode VisualStylesMode { get; set; }
[Experimental("WFO9001")]
public event EventHandler? VisualStylesModeChanged
[Experimental("WFO9001")]
protected virtual void OnVisualStylesModeChanged(EventArgs e)
// Allows a derived Control to set a particular Styles mode for backwards compatibility reasons.
[Experimental("WFO9001")]
protected virtual VisualStylesMode DefaultVisualStylesMode { get; }
}
/// To address checkbox A11Y min size requirements.
public enum Appearance
{
Normal = 0,
Button = 1,
/// <summary>
/// The appearance of a Modern UI toggle switch.
/// This setting is not taken into account, when <see cref="VisualStylesMode"/> is set
/// to <see cref="VisualStylesMode.Disabled"/> or <see cref="VisualStylesMode.Classic"/>.
/// </summary>
[Experimental("WFO9001")]
ToggleSwitch = 2
}
}
New Visual Basic Framework APIs
Since WinForms Apps, which are based on the Windows Forms Application Framework, do not have the equivalent of the Program.cs
, we would suggest the following new APIs in the same style as the existing VB default settings:
Namespace Microsoft.VisualBasic.ApplicationServices
Public Class ApplyApplicationDefaultsEventArgs
Public Property ColorMode As System.Windows.Forms.SystemColorMode
Public Property VisualStylesMode As System.Windows.Forms.VisualStylesMode
End Class
Public Class WindowsFormsApplicationBase
Public Property ColorMode As System.Windows.Forms.SystemColorMode
Public Property VisualStylesMode As System.Windows.Forms.VisualStylesMode
End Class
End Namespace