Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dark mode/Visual Styles (VB Only) fix #11907

Merged
merged 6 commits into from
Aug 19, 2024
68 changes: 47 additions & 21 deletions src/System.Windows.Forms/src/System/Windows/Forms/Form.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ public partial class Form : ContainerControl
private static readonly int s_propOpacity = PropertyStore.CreateKey();
private static readonly int s_propTransparencyKey = PropertyStore.CreateKey();
private static readonly int s_propFormCornerPreference = PropertyStore.CreateKey();
private static readonly int s_propFormBorderColor = PropertyStore.CreateKey();

private static readonly int s_propFormCaptionTextColor = PropertyStore.CreateKey();
private static readonly int s_propFormCaptionBackColor = PropertyStore.CreateKey();

// Form per instance members
// Note: Do not add anything to this list unless absolutely necessary.
Expand Down Expand Up @@ -2376,6 +2380,9 @@ public FormCornerPreference FormCornerPreference
/// Raises the <see cref="FormCornerPreferenceChanged"/> event when the
/// <see cref="FormCornerPreference"/> property changes.
/// </summary>
/// <param name="e">
/// An <see cref="EventArgs"/> that contains the event data, in this case empty.
/// </param>
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
protected virtual void OnFormCornerPreferenceChanged(EventArgs e)
{
Expand Down Expand Up @@ -2408,6 +2415,11 @@ private unsafe void SetFormCornerPreferenceInternal(FormCornerPreference cornerP
/// <summary>
/// Sets or gets the Form's border color.
/// </summary>
/// <returns>
/// The <see cref="Color"/> which has be previously set using this property or <see cref="Color.Empty"/>.
/// Note that the underlying Win32 API does not provide a reliable mechanism to retrieve the current
/// border color.
/// </returns>
/// <remarks>
/// <para>
/// Note: Reading this property is only for tracking purposes. If the Form's border color is
Expand All @@ -2428,14 +2440,16 @@ private unsafe void SetFormCornerPreferenceInternal(FormCornerPreference cornerP
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
Copy link

@memoarfaa memoarfaa Aug 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KlausLoeffelmann
I see this function need to be in some Controls not only in Form So that we can benefit from it to the maximum extent

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function can be applied to all popup

this function can be applied to all popup Windows

like Menus ,Tooltips, ...........

Non round example

2024-08-18_04-42-29.mp4

Round example

2024-08-18_04-51-13.mp4

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, what do you mean "this function can be applied..."
When I see the video clips, I see I few things I have questions about. Like the dark Tab Control. How is that implemented/tuned?

The rounded Buttons look cool (and also the tool tips and other elements I see).
Is this done by custom rendering or did you apply the same Win32 API to it?

Keep in mind:
VisualStylesMode was a concept I came up with primarily driven by accessibility, actually somewhat late in the development cycle (around end of April/begin of May I think I only had the first prototype which I did as a proof of concept first in my spare time) to complement the dark mode feature in terms of contrast issues we saw, and in general to address an upcoming regulatory issue where we need bigger "real estate" for clicking "landing spots" for controls. So, the teeny-tiny Up-Down-Buttons from NumericUpDown or DomainUpDown are very extreme examples for that. They are just an accessibility nightmare IMO. The non-styleble MonthCalendar with its tiny date numbers is another example. And TextBox (TextBoxBase) itself is yet another one, because we cannot adjust its Padding: In 96 dpi without a border, it got only a height of 16 pixels, if I remember correctly. And that's just not good enough in the context of HighDPI-Monitors and mindful accessibility. So - "styling" improvements should have those things in mind. The animated CheckBox looks arguably cooler than the old one as a side effect, alright: But: It has that bigger landing spot and is better recognizable. We need to have a valid argument in that regard to think about improvements in style or real-estate.

Everything that goes beyond that becomes Theming, and that's where we (WinForms Team) all agreed, is a line we cannot cross. Our third parties have those things already in place, and they do an extremely good job with that.

Copy link

@memoarfaa memoarfaa Aug 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, what do you mean "this function can be applied..." When I see the video clips, I see I few things I have questions about. Like the dark Tab Control. How is that implemented/tuned?

this is implemented by paint on Wm_ Paint but there is multiple solution for the Tab Control see #11953.

The rounded Buttons look cool (and also the tool tips and other elements I see). Is this done by custom rendering or did you apply the same Win32 API to it?

The tool tips and pop up menus use DWM_WINDOW_CORNER_PREFERENCE and any Window has WS_POPUP can use it

#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
            if (SystemInformation.HighContrast)
            {
                PInvoke.SetWindowTheme(HWND, string.Empty, string.Empty);
            }
            else if (Application.IsDarkModeEnabled && !_isBalloon)
            {
                DWM_WINDOW_CORNER_PREFERENCE round = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND;
                PInvoke.DwmSetWindowAttribute(HWND, DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, &round, sizeof(DWM_WINDOW_CORNER_PREFERENCE));
                PInvoke.SendMessage(HWND, PInvoke.TTM_SETWINDOWTHEME, default, "DarkMode_Explorer");
            }

#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
        }

the remains elements is use Uxtheme Win32 API and its Documented.

Everything that goes beyond that becomes Theming, and that's where we (WinForms Team) all agreed, is a line we cannot cross. Our third parties have those things already in place, and they do an extremely good job with that.

may me not one line but multiple lines

public Color FormBorderColor
{
get => GetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR);
get => Properties.GetValueOrDefault(s_propFormBorderColor, Color.Empty);
set
{
if (value == FormBorderColor)
{
return;
}

Properties.AddValue(s_propFormBorderColor, value);

if (IsHandleCreated)
{
SetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR, value);
Expand All @@ -2448,6 +2462,9 @@ public Color FormBorderColor
/// <summary>
/// Raises the <see cref="FormBorderColorChanged"/> event when the <see cref="FormBorderColor"/> property changes.
/// </summary>
/// <param name="e">
/// An <see cref="EventArgs"/> that contains the event data, in this case empty.
/// </param>
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
protected virtual void OnFormBorderColorChanged(EventArgs e)
{
Expand All @@ -2458,8 +2475,13 @@ protected virtual void OnFormBorderColorChanged(EventArgs e)
}

/// <summary>
/// Sets or gets the Form's title bar back color.
/// Sets or gets the Form's title bar back color (caption back color).
/// </summary>
/// <returns>
/// The <see cref="Color"/>, which has be previously set using this property or <see cref="Color.Empty"/>.
/// Note that the underlying Win32 API does not provide a reliable mechanism to retrieve the current title
/// bar color.
/// </returns>
/// <remarks>
/// <para>
/// Note: Reading this property is only for tracking purposes. If the window's title bar color is
Expand All @@ -2480,14 +2502,16 @@ protected virtual void OnFormBorderColorChanged(EventArgs e)
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
public Color FormCaptionBackColor
{
get => GetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_CAPTION_COLOR);
get => Properties.GetValueOrDefault(s_propFormCaptionBackColor, Color.Empty);
set
{
if (value == FormCaptionBackColor)
{
return;
}

Properties.AddValue(s_propFormCaptionBackColor, value);

if (IsHandleCreated)
{
SetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_CAPTION_COLOR, value);
Expand All @@ -2498,21 +2522,12 @@ public Color FormCaptionBackColor
}

/// <summary>
/// Raises the <see cref="FormCaptionBackColor"/> event when the <see cref="FormCaptionBackColor"/> property changes.
/// Raises the <see cref="FormCaptionBackColorChanged"/> event when the <see cref="FormCaptionBackColor"/>
/// property changes.
/// </summary>
/// <remarks>
/// <para>
/// Note: Reading this property is only for tracking purposes. If the Form's title bar's back color
/// (window caption background) is changed through other external means (Win32 calls), reading this
/// property will not reflect those changes, as the Win32 API does not provide a mechanism to retrieve
/// the current title bar color.
/// </para>
/// <para>
/// The property only reflects the value that was previously set using this property. The
/// <see cref="FormBorderColorChanged"/> event is raised accordingly when the value is
/// changed, which allows the property to be participating in binding scenarios.
/// </para>
/// </remarks>
/// <param name="e">
/// An <see cref="EventArgs"/> that contains the event data, in this case empty.
/// </param>
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
protected virtual void OnFormCaptionBackColorChanged(EventArgs e)
{
Expand All @@ -2523,8 +2538,13 @@ protected virtual void OnFormCaptionBackColorChanged(EventArgs e)
}

/// <summary>
/// Sets or gets the Form's title bar text color.
/// Sets or gets the Form's title bar text color (windows caption text color).
/// </summary>
/// <returns>
/// The <see cref="Color"/>, which has be previously set using this property or <see cref="Color.Empty"/>.
/// Note that the underlying Win32 API does not provide a reliable mechanism to retrieve the current title
/// bar text color.
/// </returns>
/// <remarks>
/// <para>
/// Note: Reading this property is only for tracking purposes. If the Form's title bar's text color
Expand All @@ -2534,7 +2554,7 @@ protected virtual void OnFormCaptionBackColorChanged(EventArgs e)
/// </para>
/// <para>
/// The property only reflects the value that was previously set using this property. The
/// <see cref="FormBorderColorChanged"/> event is raised accordingly when the value is
/// <see cref="FormCaptionTextColorChanged"/> event is raised accordingly when the value is
/// changed, which allows the property to be participating in binding scenarios.
/// </para>
/// </remarks>
Expand All @@ -2545,14 +2565,16 @@ protected virtual void OnFormCaptionBackColorChanged(EventArgs e)
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
public Color FormCaptionTextColor
{
get => GetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_TEXT_COLOR);
get => Properties.GetValueOrDefault(s_propFormCaptionTextColor, Color.Empty);
set
{
if (value == FormCaptionTextColor)
{
return;
}

Properties.AddValue(s_propFormCaptionTextColor, value);

if (IsHandleCreated)
{
SetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_TEXT_COLOR, value);
Expand All @@ -2563,8 +2585,12 @@ public Color FormCaptionTextColor
}

/// <summary>
/// Raises the <see cref="FormCaptionTextColor"/> event when the <see cref="FormCaptionTextColor"/> property changes.
/// Raises the <see cref="FormCaptionTextColorChanged"/> event when the
/// <see cref="FormCaptionTextColor"/> property changes.
/// </summary>
/// <param name="e">
/// An <see cref="EventArgs"/> that contains the event data, in this case empty.
/// </param>
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
protected virtual void OnFormCaptionTextColorChanged(EventArgs e)
{
Expand Down