简体   繁体   中英

WinForms Dark title bar on Windows 10

I have a WinForms application which automatically adjusts to the dark/light theme on Windows 10. My problem is that the title bar of my window always stays white, regardless which theme the user selects.

在此处输入图像描述
Top is current, bottom is how I want it (simulated with Photoshop)

See explorer for example. That is not an UWP app, however it uses a dark title bar on Windows 1903 and newer (when a dark theme is selected).

How can I achieve the same thing? I do not want to use any custom titlebar as I want the application to look and behave like any native application on older Windows versions as well.

So after some long searching, I have finally found the answer for this. The trick is to use dwmapi.dll 's DwmSetWindowAttribute and passing the undocumented constant DWMWA_USE_IMMERSIVE_DARK_MODE into the function. In C#, the code for this looks a little something like this (works with both WinForms and WPF):

/*
using System.Runtime.InteropServices;
*/

[DllImport("dwmapi.dll")]
private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

private const int DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19;
private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;

private static bool UseImmersiveDarkMode(IntPtr handle, bool enabled)
{
    if (IsWindows10OrGreater(17763))
    {
        var attribute = DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1;
        if (IsWindows10OrGreater(18985))
        {
            attribute = DWMWA_USE_IMMERSIVE_DARK_MODE;
        }

        int useImmersiveDarkMode = enabled ? 1 : 0;
        return DwmSetWindowAttribute(handle, (int)attribute, ref useImmersiveDarkMode, sizeof(int)) == 0;
    }

    return false;
}

private static bool IsWindows10OrGreater(int build = -1)
{
    return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= build;
}

The fastest way:

[DllImport("DwmApi")] //System.Runtime.InteropServices
private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, int[] attrValue, int attrSize);

protected override void OnHandleCreated(EventArgs e)
{
    if (DwmSetWindowAttribute(Handle, 19, new[] { 1 }, 4) != 0)
        DwmSetWindowAttribute(Handle, 20, new[] { 1 }, 4);
}

For the solution from Jonas Kohl, remember that for .net fw 4.8.1 and prior, the version returned is not ok, fixed in.Net6, here a snippet (.Net 5 is not managed):

    private static bool IsWindows10OrGreater(int build = -1)
    {
        return WindowsVersion() >= 10 && WindowsBuildNumber() >= build;
    }

    public static int WindowsVersion()
    {
     //for .Net4.8 and Minor
     #if NETFRAMEWORK
        int result = 10;
        var reg = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
        string[] productName = reg.GetValue("ProductName").ToString().Split((char)32);
        int.TryParse(productName[1], out result);
        return result;
     #else
        //fixed in .Net6
        return System.Environment.OSVersion.Version.Major;
     #endif
    }

    public static int WindowsBuildNumber()
    {
        //for .Net4.8 and Minor
    #if NETFRAMEWORK
        int result = 22000;
        var reg = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
        string buildNumber = reg.GetValue("CurrentBuildNumber").ToString();
        int.TryParse(buildNumber, out result);
        return result;
    #endif

    #if NET
        //fixed in .Net6
        return System.Environment.OSVersion.Version.Build;
    #endif
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM