Skip to content

[Dark Mode] Some toolstrip colors don't change when switching color mode #12027

New issue

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

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

Already on GitHub? # to your account

Open
cyanfish opened this issue Aug 31, 2024 · 5 comments · May be fixed by #13360
Open

[Dark Mode] Some toolstrip colors don't change when switching color mode #12027

cyanfish opened this issue Aug 31, 2024 · 5 comments · May be fixed by #13360
Labels
area-DarkMode Issues relating to Dark Mode feature help wanted Good issue for external contributors
Milestone

Comments

@cyanfish
Copy link

.NET version

9.0.100-rc.2.24430.10

Did it work in .NET Framework?

No

Did it work in any of the earlier releases of .NET Core or .NET 5+?

Dark mode is a new feature in net9

Issue description

Some toolstrip colors don't change when the SystemColorMode is changed while the app is running, even after re-creating the form. (It works fine when setting the SystemColorMode at app start.)

Correct (when starting the app in dark mode):
image

Incorrect (when starting the app in light mode, switching to dark mode, and re-creating the form):
image

I believe the root cause of the issue is here, where there is a static cache of brushes with an index based on the KnownColor enum (e.g. KnownColor.ControlText). I imagine the solution involves purging these caches when the color mode is changed.

public static Brush FromSystemColor(Color c)

Also here has a similar problem, though I haven't evaluated if there are further impacts from these static caches.

public static Pen FromSystemColor(Color c)

Steps to reproduce

  1. Start the application with Application.SetColorMode(SystemColorMode.Classic)
  2. Call Application.SetColorMode(SystemColorMode.Dark)
  3. Close and re-open a form with a toolbar
  4. Observe that ToolStripSeparator and dropdown arrows on ToolStripSplitButton have the wrong color
@cyanfish cyanfish added the untriaged The team needs to look at this issue in the next triage label Aug 31, 2024
@cyanfish
Copy link
Author

Calling this from the main thread after switching color modes seems to be a working hack:

private void ClearCachedBrushesAndPens()
{
    var threadData = (IDictionary<object, object?>) typeof(SystemBrushes).Assembly.GetType("System.Drawing.Gdip")!
        .GetProperty("ThreadData", BindingFlags.Static | BindingFlags.NonPublic)!
        .GetValue(null)!;

    var systemBrushesKey = typeof(SystemBrushes)
        .GetField("s_systemBrushesKey", BindingFlags.Static | BindingFlags.NonPublic)!
        .GetValue(null)!;

    var systemPensKey = typeof(SystemPens)
        .GetField("s_systemPensKey", BindingFlags.Static | BindingFlags.NonPublic)!
        .GetValue(null)!;

    threadData[systemBrushesKey] = null;
    threadData[systemPensKey] = null;
}

@elachlan elachlan added the area-DarkMode Issues relating to Dark Mode feature label Aug 31, 2024
@Zheng-Li01
Copy link
Member

@cyanfish, cannot reproduce the issue based on your description as below screenshot. could you please have a check if there have any wrong steps.
GH12027

@cyanfish
Copy link
Author

cyanfish commented Sep 2, 2024

Sorry, the trigger is actually much more obscure than I thought. It seems to be some kind of race condition when calling SetColorMode in response to the UserPreferenceChanged event for the system switching between light and dark mode.

Specific repro steps with the below code:

  1. Set the Windows color mode to Light
  2. Start the program
  3. Open Form2
  4. Close Form2
  5. Switch the Windows color mode to Dark
  6. Open Form2

But as noted in the code with any kind of delay to the Application.SetColorMode call the issue goes away.

Repro Code
using Microsoft.Win32;

namespace WinFormsApp1;

static class Program
{
    [STAThread]
    static void Main()
    {
        ApplicationConfiguration.Initialize();
#pragma warning disable WFO5001
        Application.SetColorMode(SystemColorMode.System);
#pragma warning restore WFO5001
        Application.Run(new Form1());
    }
}

public class Form1 : Form
{
    private static DarkModeManager _dmm;

    public Form1()
    {
        _dmm = new DarkModeManager();
        var btn = new Button { Text = "Form2", Location = new Point(0, 50) };
        btn.Click += (_, _) => new Form2().Show();
        Controls.Add(btn);
    }
}

public class Form2 : Form
{
    public Form2()
    {
        var tsc = new ToolStripContainer();
        var ts = new ToolStrip();
        ts.Items.Add(new ToolStripSplitButton("A"));
        ts.Items.Add(new ToolStripSeparator());
        ts.Items.Add(new ToolStripSplitButton("B"));
        tsc.TopToolStripPanel.Controls.Add(ts);
        Controls.Add(tsc);
    }
}

public class DarkModeManager
{
    private bool? _value;

    public DarkModeManager()
    {
        SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
    }

    private bool ReadDarkMode()
    {
        try
        {
            using var key =
                Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize");
            return Equals(key?.GetValue("AppsUseLightTheme"), 0);
        }
        catch (Exception)
        {
            return false;
        }
    }

    private void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
    {
        var newValue = ReadDarkMode();
        if (newValue != _value)
        {
            _value = newValue;
#pragma warning disable WFO5001
            // This does not work:
            Application.SetColorMode(newValue ? SystemColorMode.Dark : SystemColorMode.Classic);
            // This works:
            // Task.Delay(100).ContinueWith(_ =>
            //     Application.SetColorMode(newValue ? SystemColorMode.Dark : SystemColorMode.Classic));
#pragma warning restore WFO5001
        }
    }
}

@Zheng-Li01
Copy link
Member

@cyanfish thanks for your response, yes. the issue can be reproduced with your code as below screenshot.
12027.zip

This does not work:
Application.SetColorMode(newValue ? SystemColorMode.Dark : SystemColorMode.Classic);
image
NotWorking

This works:
Task.Delay(100).ContinueWith(_ =>
Application.SetColorMode(newValue ? SystemColorMode.Dark : SystemColorMode.Classic));
image
Working

@merriemcgaw merriemcgaw added help wanted Good issue for external contributors and removed untriaged The team needs to look at this issue in the next triage labels Sep 4, 2024
@dotnet-policy-service dotnet-policy-service bot added this to the Help wanted milestone Sep 4, 2024
Copy link
Contributor

This issue is now marked as "help wanted", and we’re looking for a community volunteer to work on this issue. If we receive no interest in 180 days, we will close the issue. To learn more about how we handle feature requests, please see our documentation.

Happy Coding!

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
area-DarkMode Issues relating to Dark Mode feature help wanted Good issue for external contributors
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants