diff --git a/DarkNet/DarkNet.cs b/DarkNet/DarkNet.cs index c1f886b..69bbde8 100644 --- a/DarkNet/DarkNet.cs +++ b/DarkNet/DarkNet.cs @@ -14,6 +14,7 @@ using System.Windows.Interop; using Dark.Net.Events; using Microsoft.Win32; +using SystemColors = System.Drawing.SystemColors; namespace Dark.Net; @@ -139,15 +140,15 @@ void OnSourceInitialized(object? _, EventArgs eventArgs) { throw new InvalidOperationException($"Called {nameof(SetWindowThemeWpf)}() too late, call it in OnSourceInitialized or the Window subclass's constructor"); } + window.Closing += OnClosing; + windowThemeState.EffectiveThemeChanged += OnWindowEffectiveThemeChanged; + OnWindowEffectiveThemeChanged(windowThemeState, windowThemeState.EffectiveThemeIsDark); + void OnClosing(object _, CancelEventArgs args) { window.Closing -= OnClosing; windowThemeState.EffectiveThemeChanged -= OnWindowEffectiveThemeChanged; OnWindowClosing(windowHandle); } - - window.Closing += OnClosing; - windowThemeState.EffectiveThemeChanged += OnWindowEffectiveThemeChanged; - OnWindowEffectiveThemeChanged(windowThemeState, windowThemeState.EffectiveThemeIsDark); } } @@ -162,15 +163,15 @@ public virtual void SetWindowThemeForms(Form window, Theme theme, ThemeOptions? $"{nameof(IDarkNet)}.{nameof(SetCurrentProcessTheme)}()"); } + window.Closing += OnClosing; + windowThemeState.EffectiveThemeChanged += OnWindowEffectiveThemeChanged; + OnWindowEffectiveThemeChanged(windowThemeState, windowThemeState.EffectiveThemeIsDark); + void OnClosing(object _, CancelEventArgs args) { window.Closing -= OnClosing; windowThemeState.EffectiveThemeChanged -= OnWindowEffectiveThemeChanged; OnWindowClosing(window.Handle); } - - window.Closing += OnClosing; - windowThemeState.EffectiveThemeChanged += OnWindowEffectiveThemeChanged; - OnWindowEffectiveThemeChanged(windowThemeState, windowThemeState.EffectiveThemeIsDark); } /// @@ -217,7 +218,7 @@ private void ImplicitlySetProcessThemeIfFirstCall(Theme theme) { /// if window.Visibility==VISIBLE and WindowPlacement.ShowCmd == SW_HIDE (or whatever), it was definitely called too early /// if GetWindowInfo().style.WS_VISIBLE == true then it was called too late /// - /// if the window is using dark mode after this call returns, or if it is using light mode + /// The new state of this window's theming /// if it is called too late private WindowThemeState SetModeForWindow(IntPtr windowHandle, Theme windowTheme, ThemeOptions? options = null) { ImplicitlySetProcessThemeIfFirstCall(windowTheme); @@ -270,25 +271,21 @@ private void RefreshTitleBarThemeColor() { /// Apply all of the theme fallback/override logic and call the OS methods to apply the window theme. Handles the window theme, app theme, OS theme, high contrast, different Windows versions, Windows 11 colors, repainting visible windows, and updating context menus. /// /// A pointer to the window to update - /// Windows 11 DWM color overrides - /// if the window is using dark mode after this call returns, or if it is using light mode + /// Current values and optional extra parameters for this window's theme private void RefreshTitleBarThemeColor(IntPtr windowHandle, WindowThemeState windowThemeState) { try { Theme windowTheme = windowThemeState.PreferredTheme; - - Theme appTheme = _preferredAppTheme ?? Theme.Auto; - bool userDefaultAppThemeIsDark = UserDefaultAppThemeIsDark; - bool isHighContrast = IsHighContrast(); + Theme appTheme = _preferredAppTheme ?? Theme.Auto; if (appTheme == Theme.Auto) { - appTheme = userDefaultAppThemeIsDark ? Theme.Dark : Theme.Light; + appTheme = UserDefaultAppThemeIsDark ? Theme.Dark : Theme.Light; } if (windowTheme == Theme.Auto) { windowTheme = appTheme; } - if (isHighContrast) { + if (IsHighContrast()) { windowTheme = Theme.Light; appTheme = Theme.Light; } else if (!Win32.IsDarkModeAllowedForWindow(windowHandle)) { @@ -320,47 +317,80 @@ private void RefreshTitleBarThemeColor(IntPtr windowHandle, WindowThemeState win windowThemeState.EffectiveThemeIsDark = isDarkTheme; - if ((windowThemeState.Options?.TitleBarBackgroundColor ?? _processThemeOptions?.TitleBarBackgroundColor) is { } titleBarBackgroundColor) { - SetDwmWindowColor(windowHandle, DwmWindowAttribute.DwmwaCaptionColor, titleBarBackgroundColor); - } + ApplyCustomTitleBarColors(windowHandle, windowThemeState); + RepaintTitleBar(windowHandle); + Win32.FlushMenuThemes(); // Needed to make the context menu theme change when you change the app theme after showing a window. + ApplyThemeToFormsControls(windowHandle, windowThemeState); + } catch (EntryPointNotFoundException) { + // #9: possibly Wine, do nothing + } + } - if ((windowThemeState.Options?.TitleBarTextColor ?? _processThemeOptions?.TitleBarTextColor) is { } titleBarTextColor) { - SetDwmWindowColor(windowHandle, DwmWindowAttribute.DwmwaTextColor, titleBarTextColor); - } + private void ApplyCustomTitleBarColors(IntPtr windowHandle, WindowThemeState windowThemeState) { + if ((windowThemeState.Options?.TitleBarBackgroundColor ?? _processThemeOptions?.TitleBarBackgroundColor) is { } titleBarBackgroundColor) { + SetDwmWindowColor(windowHandle, DwmWindowAttribute.DwmwaCaptionColor, titleBarBackgroundColor); + } - if ((windowThemeState.Options?.WindowBorderColor ?? _processThemeOptions?.WindowBorderColor) is { } windowBorderColor) { - SetDwmWindowColor(windowHandle, DwmWindowAttribute.DwmwaBorderColor, windowBorderColor); - } + if ((windowThemeState.Options?.TitleBarTextColor ?? _processThemeOptions?.TitleBarTextColor) is { } titleBarTextColor) { + SetDwmWindowColor(windowHandle, DwmWindowAttribute.DwmwaTextColor, titleBarTextColor); + } + + if ((windowThemeState.Options?.WindowBorderColor ?? _processThemeOptions?.WindowBorderColor) is { } windowBorderColor) { + SetDwmWindowColor(windowHandle, DwmWindowAttribute.DwmwaBorderColor, windowBorderColor); + } + } + + /// + /// Needed for subsequent (after the window has already been shown) theme changes, otherwise the title bar will only update after you later hide, blur, or resize the window. + /// Not needed when changing the theme for the first time, before the window has ever been shown. + /// Windows 11 does not need this. Windows 10 needs this (1809, 22H2, and likely every other version). + /// Neither RedrawWindow() nor UpdateWindow() fix this. + /// https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-ncactivate + /// + private static void RepaintTitleBar(IntPtr windowHandle) { + const uint activateNonClientArea = 0x86; + bool isWindowActive = windowHandle == Win32.GetForegroundWindow(); + Win32.SendMessage(windowHandle, activateNonClientArea, new IntPtr(isWindowActive ? 0 : 1), IntPtr.Zero); + Win32.SendMessage(windowHandle, activateNonClientArea, new IntPtr(isWindowActive ? 1 : 0), IntPtr.Zero); + } - /* - * Needed for subsequent (after the window has already been shown) theme changes, otherwise the title bar will only update after you later hide, blur, or resize the window. - * Not needed when changing the theme for the first time, before the window has ever been shown. - * Windows 11 does not need this. - * Windows 10 needs this (1809, 22H2, and likely every other version). - * Neither RedrawWindow() nor UpdateWindow() fix this. - * https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-ncactivate - */ - const uint activateNonClientArea = 0x86; - bool isWindowActive = windowHandle == Win32.GetForegroundWindow(); - Win32.SendMessage(windowHandle, activateNonClientArea, new IntPtr(isWindowActive ? 0 : 1), IntPtr.Zero); - Win32.SendMessage(windowHandle, activateNonClientArea, new IntPtr(isWindowActive ? 1 : 0), IntPtr.Zero); - - // Needed to make the context menu theme change when you change the app theme after showing a window. - Win32.FlushMenuThemes(); - - // Optionally theme Windows Forms scrollbars - bool? windowRequiresThemedScrollbars = windowThemeState.Options?.ApplyThemeToDescendentFormsScrollbars; - bool processRequiresThemedScrollbars = _processThemeOptions?.ApplyThemeToDescendentFormsScrollbars ?? false; - - if ((windowRequiresThemedScrollbars == true || (windowRequiresThemedScrollbars == null && processRequiresThemedScrollbars)) && - Control.FromHandle(windowHandle) is { } formsWindow) { - foreach (Control scrollbar in formsWindow.Controls.Cast().Where(control => control is HScrollBar or VScrollBar or ScrollableControl { AutoScroll: true })) { - Win32.SetWindowTheme(scrollbar.Handle, isDarkTheme ? "DarkMode_Explorer" : null, isDarkTheme ? "ScrollBar" : null); + /// + /// Optionally theme Windows Forms scrollbars + /// + private void ApplyThemeToFormsControls(IntPtr windowHandle, WindowThemeState windowThemeState) { + bool isDarkTheme = windowThemeState.EffectiveThemeIsDark; + if ((windowThemeState.Options?.ApplyThemeToDescendentFormsScrollbars ?? _processThemeOptions?.ApplyThemeToDescendentFormsScrollbars ?? false) && + Control.FromHandle(windowHandle) is { } formsWindow) { + + foreach (Control control in formsWindow.Controls.Cast() + .Where(control => control is HScrollBar or VScrollBar or ScrollableControl { AutoScroll: true } or MdiClient or TreeView)) { + + if (control is TreeView treeView && treeView.ForeColor == GetTreeViewColor(!isDarkTheme, true) && treeView.BackColor == GetTreeViewColor(!isDarkTheme, false)) { + if (isDarkTheme) { + treeView.ForeColor = GetTreeViewColor(isDarkTheme, true); + treeView.BackColor = GetTreeViewColor(isDarkTheme, false); + } else { + treeView.ResetForeColor(); + treeView.ResetBackColor(); + } + } + + Win32.SetWindowTheme(control.Handle, isDarkTheme ? "DarkMode_Explorer" : null, null); + + // Fix scrollbar corners and TreeView borders not repainting with the new theme. Neither Invalidate() nor Refresh() fix this issue, but hiding and showing the control fixes it. + if (control.Visible) { + control.Visible = false; + control.Visible = true; } } + } - } catch (EntryPointNotFoundException) { - // #9: possibly Wine, do nothing + static Color GetTreeViewColor(bool isDarkMode, bool isForeground) { + if (isForeground) { + return isDarkMode ? Color.White : SystemColors.WindowText; + } else { + return isDarkMode ? Color.FromArgb(25, 25, 25) : SystemColors.Window; + } } } @@ -422,6 +452,7 @@ private set { _effectiveProcessThemeIsDark = value; EffectiveCurrentProcessThemeIsDarkChanged?.Invoke(this, value); } + } } diff --git a/darknet-demo-winforms/Form1.Designer.cs b/darknet-demo-winforms/Form1.Designer.cs index ece6702..597b4d8 100644 --- a/darknet-demo-winforms/Form1.Designer.cs +++ b/darknet-demo-winforms/Form1.Designer.cs @@ -29,6 +29,37 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { + System.Windows.Forms.TreeNode treeNode1 = new System.Windows.Forms.TreeNode("Node1"); + System.Windows.Forms.TreeNode treeNode2 = new System.Windows.Forms.TreeNode("Node2"); + System.Windows.Forms.TreeNode treeNode3 = new System.Windows.Forms.TreeNode("Node3"); + System.Windows.Forms.TreeNode treeNode4 = new System.Windows.Forms.TreeNode("Node4"); + System.Windows.Forms.TreeNode treeNode5 = new System.Windows.Forms.TreeNode("Node5"); + System.Windows.Forms.TreeNode treeNode6 = new System.Windows.Forms.TreeNode("Node6"); + System.Windows.Forms.TreeNode treeNode7 = new System.Windows.Forms.TreeNode("Node7"); + System.Windows.Forms.TreeNode treeNode8 = new System.Windows.Forms.TreeNode("Node8"); + System.Windows.Forms.TreeNode treeNode9 = new System.Windows.Forms.TreeNode("Node9"); + System.Windows.Forms.TreeNode treeNode10 = new System.Windows.Forms.TreeNode("Node10"); + System.Windows.Forms.TreeNode treeNode11 = new System.Windows.Forms.TreeNode("Node11"); + System.Windows.Forms.TreeNode treeNode12 = new System.Windows.Forms.TreeNode("Node12"); + System.Windows.Forms.TreeNode treeNode13 = new System.Windows.Forms.TreeNode("Node13"); + System.Windows.Forms.TreeNode treeNode14 = new System.Windows.Forms.TreeNode("Node14"); + System.Windows.Forms.TreeNode treeNode15 = new System.Windows.Forms.TreeNode("Node15"); + System.Windows.Forms.TreeNode treeNode16 = new System.Windows.Forms.TreeNode("Node0 mmmmmmmmmmmmmmmmmmmmm", new System.Windows.Forms.TreeNode[] { + treeNode1, + treeNode2, + treeNode3, + treeNode4, + treeNode5, + treeNode6, + treeNode7, + treeNode8, + treeNode9, + treeNode10, + treeNode11, + treeNode12, + treeNode13, + treeNode14, + treeNode15}); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1)); this.label1 = new System.Windows.Forms.Label(); this.darkModeCheckbox = new System.Windows.Forms.CheckBox(); @@ -36,14 +67,13 @@ private void InitializeComponent() this.vScrollBar1 = new System.Windows.Forms.VScrollBar(); this.panel1 = new System.Windows.Forms.Panel(); this.panel2 = new System.Windows.Forms.Panel(); + this.treeView1 = new System.Windows.Forms.TreeView(); this.panel1.SuspendLayout(); this.SuspendLayout(); // // label1 // - this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); + this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); this.label1.Location = new System.Drawing.Point(12, 201); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(768, 13); @@ -97,12 +127,56 @@ private void InitializeComponent() this.panel2.Size = new System.Drawing.Size(429, 305); this.panel2.TabIndex = 0; // + // treeView1 + // + this.treeView1.BackColor = System.Drawing.SystemColors.Window; + this.treeView1.ForeColor = System.Drawing.SystemColors.WindowText; + this.treeView1.Location = new System.Drawing.Point(44, 47); + this.treeView1.Name = "treeView1"; + treeNode1.Name = "Node1"; + treeNode1.Text = "Node1"; + treeNode2.Name = "Node2"; + treeNode2.Text = "Node2"; + treeNode3.Name = "Node3"; + treeNode3.Text = "Node3"; + treeNode4.Name = "Node4"; + treeNode4.Text = "Node4"; + treeNode5.Name = "Node5"; + treeNode5.Text = "Node5"; + treeNode6.Name = "Node6"; + treeNode6.Text = "Node6"; + treeNode7.Name = "Node7"; + treeNode7.Text = "Node7"; + treeNode8.Name = "Node8"; + treeNode8.Text = "Node8"; + treeNode9.Name = "Node9"; + treeNode9.Text = "Node9"; + treeNode10.Name = "Node10"; + treeNode10.Text = "Node10"; + treeNode11.Name = "Node11"; + treeNode11.Text = "Node11"; + treeNode12.Name = "Node12"; + treeNode12.Text = "Node12"; + treeNode13.Name = "Node13"; + treeNode13.Text = "Node13"; + treeNode14.Name = "Node14"; + treeNode14.Text = "Node14"; + treeNode15.Name = "Node15"; + treeNode15.Text = "Node15"; + treeNode16.Name = "Node0"; + treeNode16.Text = "Node0 mmmmmmmmmmmmmmmmmmmmm"; + this.treeView1.Nodes.AddRange(new System.Windows.Forms.TreeNode[] { + treeNode16}); + this.treeView1.Size = new System.Drawing.Size(143, 250); + this.treeView1.TabIndex = 5; + // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(19)))), ((int)(((byte)(19)))), ((int)(((byte)(19))))); this.ClientSize = new System.Drawing.Size(792, 419); + this.Controls.Add(this.treeView1); this.Controls.Add(this.panel1); this.Controls.Add(this.vScrollBar1); this.Controls.Add(this.hScrollBar1); @@ -126,6 +200,7 @@ private void InitializeComponent() private System.Windows.Forms.VScrollBar vScrollBar1; private System.Windows.Forms.Panel panel1; private System.Windows.Forms.Panel panel2; + private System.Windows.Forms.TreeView treeView1; } }