From f1e4fcb0190c6c01ec6044b78629331113e9aea4 Mon Sep 17 00:00:00 2001
From: SlimeNull <69663231+SlimeNull@users.noreply.github.com>
Date: Wed, 7 Aug 2024 16:53:34 +0800
Subject: [PATCH] Add SnapLayout Options support for Win11 (#345)
---
dnSpy/dnSpy/Controls/WinSysButton.cs | 57 +-
.../Utilities/WindowOption.NativeMethods.cs | 18 +
.../EleCho.WpfSuite/Utilities/WindowOption.cs | 588 ++++++++++++++++++
dnSpy/dnSpy/Themes/wpf.styles.templates.xaml | 14 +-
4 files changed, 645 insertions(+), 32 deletions(-)
create mode 100644 dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOption.NativeMethods.cs
create mode 100644 dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOption.cs
diff --git a/dnSpy/dnSpy/Controls/WinSysButton.cs b/dnSpy/dnSpy/Controls/WinSysButton.cs
index fa9dd54f8e..2c4afbdcfc 100644
--- a/dnSpy/dnSpy/Controls/WinSysButton.cs
+++ b/dnSpy/dnSpy/Controls/WinSysButton.cs
@@ -58,7 +58,10 @@ public CurrentWinSysType CurrentWinSysType {
static WinSysButton() => DefaultStyleKeyProperty.OverrideMetadata(typeof(WinSysButton), new FrameworkPropertyMetadata(typeof(WinSysButton)));
- public WinSysButton() => Loaded += WinSysButton_Loaded;
+ public WinSysButton() {
+ Loaded += WinSysButton_Loaded;
+ Click += WinSysButton_Click;
+ }
void WinSysButton_Loaded(object? sender, RoutedEventArgs e) {
Loaded -= WinSysButton_Loaded;
@@ -67,6 +70,32 @@ void WinSysButton_Loaded(object? sender, RoutedEventArgs e) {
window.StateChanged += window_StateChanged;
}
+ void WinSysButton_Click(object sender, RoutedEventArgs e) {
+ if (window is null)
+ return;
+
+ switch (CurrentWinSysType) {
+ case CurrentWinSysType.Minimize:
+ WindowUtils.Minimize(window);
+ break;
+
+ case CurrentWinSysType.Maximize:
+ WindowUtils.Maximize(window);
+ break;
+
+ case CurrentWinSysType.Restore:
+ WindowUtils.Restore(window);
+ break;
+
+ case CurrentWinSysType.Close:
+ window.Close();
+ break;
+
+ default:
+ throw new ArgumentException("Invalid CurrentWinSysType");
+ }
+ }
+
void window_StateChanged(object? sender, EventArgs e) => OnWinSysTypeChanged(WinSysType);
static void OnWinSysTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
((WinSysButton)d).OnWinSysTypeChanged((WinSysType)e.NewValue);
@@ -97,31 +126,5 @@ void OnWinSysTypeChanged(WinSysType newValue) {
throw new ArgumentException("Invalid WinSysType");
}
}
-
- protected override void OnClick() {
- if (window is null)
- return;
-
- switch (CurrentWinSysType) {
- case CurrentWinSysType.Minimize:
- WindowUtils.Minimize(window);
- break;
-
- case CurrentWinSysType.Maximize:
- WindowUtils.Maximize(window);
- break;
-
- case CurrentWinSysType.Restore:
- WindowUtils.Restore(window);
- break;
-
- case CurrentWinSysType.Close:
- window.Close();
- break;
-
- default:
- throw new ArgumentException("Invalid CurrentWinSysType");
- }
- }
}
}
diff --git a/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOption.NativeMethods.cs b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOption.NativeMethods.cs
new file mode 100644
index 0000000000..38df140a63
--- /dev/null
+++ b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOption.NativeMethods.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace EleCho.WpfSuite {
+ public partial class WindowOption {
+ internal static class NativeDefinition {
+ public const nint WM_NCHITTEST = 0x0084;
+ public const nint WM_NCMOUSELEAVE = 0x02A2;
+ public const nint WM_NCLBUTTONDOWN = 0x00A1;
+ public const nint WM_NCLBUTTONUP = 0x00A2;
+ public const nint WM_MOUSEMOVE = 0x0200;
+
+ public const nint HTCLOSE = 20;
+ public const nint HTMAXBUTTON = 9;
+ public const nint HTMINBUTTON = 8;
+ }
+ }
+}
diff --git a/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOption.cs b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOption.cs
new file mode 100644
index 0000000000..7ff483a145
--- /dev/null
+++ b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOption.cs
@@ -0,0 +1,588 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Interop;
+using System.Windows.Media;
+using System.Windows.Shell;
+using System.Xml.Linq;
+using static EleCho.WpfSuite.WindowOption.NativeDefinition;
+
+namespace EleCho.WpfSuite {
+ ///
+ /// Window options
+ ///
+ public partial class WindowOption {
+ static readonly Version s_versionWindows10_1809 = new Version(10, 0, 17763);
+ static readonly Version s_versionWindows10 = new Version(10, 0);
+
+ ///
+ /// DWM Supports Corner, BorderColor, CaptionColor, TextColor
+ ///
+ static readonly Version s_versionWindows11_22000 = new Version(10, 0, 22000);
+
+ ///
+ /// DWM Supports Dark mode and Backdrop property
+ ///
+ static readonly Version s_versionWindows11_22621 = new Version(10, 0, 22621);
+
+ static readonly Version s_versionCurrentWindows = Environment.OSVersion.Version;
+
+ static Dictionary? s_maximumButtons;
+ static Dictionary? s_minimumButtons;
+ static Dictionary? s_closeButtons;
+
+ static DependencyPropertyKey s_uiElementIsMouseOverPropertyKey =
+ (DependencyPropertyKey)typeof(UIElement).GetField("IsMouseOverPropertyKey", BindingFlags.NonPublic | BindingFlags.Static)!.GetValue(null)!;
+
+ static DependencyPropertyKey s_buttonIsPressedPropertyKey =
+ (DependencyPropertyKey)typeof(ButtonBase).GetField("IsPressedPropertyKey", BindingFlags.NonPublic | BindingFlags.Static)!.GetValue(null)!;
+
+ ///
+ /// Get value of IsMaximumButton property
+ ///
+ ///
+ ///
+ public static bool GetIsMaximumButton(DependencyObject obj) {
+ return (bool)obj.GetValue(IsMaximumButtonProperty);
+ }
+
+ ///
+ /// Set value of IsMaximumButton property
+ ///
+ ///
+ ///
+ public static void SetIsMaximumButton(DependencyObject obj, bool value) {
+ obj.SetValue(IsMaximumButtonProperty, value);
+ }
+
+ ///
+ /// Get value of IsMinimumButton property
+ ///
+ ///
+ ///
+ public static bool GetIsMinimumButton(DependencyObject obj) {
+ return (bool)obj.GetValue(IsMinimumButtonProperty);
+ }
+
+ ///
+ /// Set value of IsMinimumButton property
+ ///
+ ///
+ ///
+ public static void SetIsMinimumButton(DependencyObject obj, bool value) {
+ obj.SetValue(IsMinimumButtonProperty, value);
+ }
+
+ ///
+ /// Get value of IsCloseButton property
+ ///
+ ///
+ ///
+ public static bool GetIsCloseButton(DependencyObject obj) {
+ return (bool)obj.GetValue(IsCloseButtonProperty);
+ }
+
+ ///
+ /// Set value of IsCloseButton property
+ ///
+ ///
+ ///
+ public static void SetIsCloseButton(DependencyObject obj, bool value) {
+ obj.SetValue(IsCloseButtonProperty, value);
+ }
+
+ ///
+ /// The DependencyProperty of IsMaximumButton property
+ ///
+ public static readonly DependencyProperty IsMaximumButtonProperty =
+ DependencyProperty.RegisterAttached("IsMaximumButton", typeof(bool), typeof(WindowOption), new FrameworkPropertyMetadata(false, OnIsMaximumButtonChanged));
+
+ ///
+ /// The DependencyProperty of IsMinimumButton property
+ ///
+ public static readonly DependencyProperty IsMinimumButtonProperty =
+ DependencyProperty.RegisterAttached("IsMinimumButton", typeof(bool), typeof(WindowOption), new FrameworkPropertyMetadata(false, OnIsMinimumButtonChanged));
+
+ ///
+ /// The DependencyProperty of IsCloseButton property
+ ///
+ public static readonly DependencyProperty IsCloseButtonProperty =
+ DependencyProperty.RegisterAttached("IsCloseButton", typeof(bool), typeof(WindowOption), new FrameworkPropertyMetadata(false, OnIsCloseButtonChanged));
+
+ #region DependencyProperty Callbacks
+
+ private static void OnIsMaximumButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ if (d is not FrameworkElement frameworkElement) {
+ throw new InvalidOperationException("Target DependencyObject is not FrameworkElement");
+ }
+
+ if (DesignerProperties.GetIsInDesignMode(d)) {
+ return;
+ }
+
+ if (Window.GetWindow(d) is Window window) {
+ DoAfterWindowSourceInitialized(window, () => {
+ ApplyIsMaximumButton(window, frameworkElement, (bool)e.NewValue);
+ });
+ }
+ else {
+ DoAfterElementLoaded(frameworkElement, () => {
+ if (Window.GetWindow(frameworkElement) is Window loadedWindow) {
+ DoAfterWindowSourceInitialized(loadedWindow, () => {
+ ApplyIsMaximumButton(loadedWindow, frameworkElement, (bool)e.NewValue);
+ });
+ }
+ else {
+ throw new InvalidOperationException("Cannot find Window of Visual");
+ }
+ });
+ }
+ }
+
+ private static void OnIsMinimumButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ if (d is not FrameworkElement frameworkElement) {
+ throw new InvalidOperationException("Target DependencyObject is not FrameworkElement");
+ }
+
+ if (DesignerProperties.GetIsInDesignMode(d)) {
+ return;
+ }
+
+ if (Window.GetWindow(d) is Window window) {
+ DoAfterWindowSourceInitialized(window, () => {
+ ApplyIsMinimumButton(window, frameworkElement, (bool)e.NewValue);
+ });
+ }
+ else {
+ DoAfterElementLoaded(frameworkElement, () => {
+ if (Window.GetWindow(frameworkElement) is Window loadedWindow) {
+ DoAfterWindowSourceInitialized(loadedWindow, () => {
+ ApplyIsMinimumButton(loadedWindow, frameworkElement, (bool)e.NewValue);
+ });
+ }
+ else {
+ throw new InvalidOperationException("Cannot find Window of Visual");
+ }
+ });
+ }
+ }
+
+ private static void OnIsCloseButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ if (d is not FrameworkElement frameworkElement) {
+ throw new InvalidOperationException("Target DependencyObject is not FrameworkElement");
+ }
+
+ if (DesignerProperties.GetIsInDesignMode(d)) {
+ return;
+ }
+
+ if (Window.GetWindow(d) is Window window) {
+ DoAfterWindowSourceInitialized(window, () => {
+ ApplyIsCloseButton(window, frameworkElement, (bool)e.NewValue);
+ });
+ }
+ else {
+ DoAfterElementLoaded(frameworkElement, () => {
+ if (Window.GetWindow(frameworkElement) is Window loadedWindow) {
+ DoAfterWindowSourceInitialized(loadedWindow, () => {
+ ApplyIsCloseButton(loadedWindow, frameworkElement, (bool)e.NewValue);
+ });
+ }
+ else {
+ throw new InvalidOperationException("Cannot find Window of Visual");
+ }
+ });
+ }
+ }
+
+ private static IntPtr WindowCaptionButtonsInteropHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
+ if (handled) {
+ return IntPtr.Zero;
+ }
+
+ switch ((nint)msg) {
+ case NativeDefinition.WM_NCHITTEST: {
+ var x = (int)((ulong)lParam & 0x0000FFFF);
+ var y = (int)((ulong)lParam & 0xFFFF0000) >> 16;
+ var result = default(IntPtr);
+
+ if (s_maximumButtons is not null &&
+ s_maximumButtons.TryGetValue(hwnd, out var maximumButtonVisual)) {
+ var relativePoint = maximumButtonVisual.PointFromScreen(new Point(x, y));
+ var hitResult = VisualTreeHelper.HitTest(maximumButtonVisual, relativePoint);
+
+ if (hitResult is not null) {
+ maximumButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, true);
+
+ handled = true;
+ result = NativeDefinition.HTMAXBUTTON;
+ }
+ else {
+ maximumButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, false);
+
+ if (maximumButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+ }
+ }
+ }
+
+ if (s_minimumButtons is not null &&
+ s_minimumButtons.TryGetValue(hwnd, out var minimumButtonVisual)) {
+ var relativePoint = minimumButtonVisual.PointFromScreen(new Point(x, y));
+ var hitResult = VisualTreeHelper.HitTest(minimumButtonVisual, relativePoint);
+
+ if (hitResult is not null) {
+ minimumButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, true);
+
+ handled = true;
+ result = NativeDefinition.HTMINBUTTON;
+ }
+ else {
+ minimumButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, false);
+
+ if (minimumButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+ }
+ }
+ }
+
+ if (s_closeButtons is not null &&
+ s_closeButtons.TryGetValue(hwnd, out var closeButtonVisual)) {
+ var relativePoint = closeButtonVisual.PointFromScreen(new Point(x, y));
+ var hitResult = VisualTreeHelper.HitTest(closeButtonVisual, relativePoint);
+
+ if (hitResult is not null) {
+ closeButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, true);
+
+ handled = true;
+ result = NativeDefinition.HTCLOSE;
+ }
+ else {
+ closeButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, false);
+
+ if (closeButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ case NativeDefinition.WM_NCLBUTTONDOWN: {
+ var x = (int)((ulong)lParam & 0x0000FFFF);
+ var y = (int)((ulong)lParam & 0xFFFF0000) >> 16;
+
+ if (s_maximumButtons is not null &&
+ s_maximumButtons.TryGetValue(hwnd, out var maximumButtonVisual)) {
+ var relativePoint = maximumButtonVisual.PointFromScreen(new Point(x, y));
+ var hitResult = VisualTreeHelper.HitTest(maximumButtonVisual, relativePoint);
+
+ if (hitResult is not null) {
+ if (maximumButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, true);
+ }
+
+ handled = true;
+ }
+ }
+
+ if (s_minimumButtons is not null &&
+ s_minimumButtons.TryGetValue(hwnd, out var minimumButtonVisual)) {
+ var relativePoint = minimumButtonVisual.PointFromScreen(new Point(x, y));
+ var hitResult = VisualTreeHelper.HitTest(minimumButtonVisual, relativePoint);
+
+ if (hitResult is not null) {
+ if (minimumButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, true);
+ }
+
+ handled = true;
+ }
+ }
+
+ if (s_closeButtons is not null &&
+ s_closeButtons.TryGetValue(hwnd, out var closeButtonVisual)) {
+ var relativePoint = closeButtonVisual.PointFromScreen(new Point(x, y));
+ var hitResult = VisualTreeHelper.HitTest(closeButtonVisual, relativePoint);
+
+ if (hitResult is not null) {
+ if (closeButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, true);
+ }
+
+ handled = true;
+ }
+ }
+
+ break;
+ }
+
+ case NativeDefinition.WM_NCLBUTTONUP: {
+ var x = (int)((ulong)lParam & 0x0000FFFF);
+ var y = (int)((ulong)lParam & 0xFFFF0000) >> 16;
+
+ if (s_maximumButtons is not null &&
+ s_maximumButtons.TryGetValue(hwnd, out var maximumButtonVisual)) {
+ if (maximumButtonVisual is ButtonBase button) {
+ bool shouldClick = false;
+ if ((bool)button.GetValue(s_buttonIsPressedPropertyKey.DependencyProperty)) {
+ shouldClick = true;
+ }
+
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+
+ if (shouldClick) {
+ button.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, button));
+ button.Command?.Execute(button.CommandParameter);
+ }
+
+ handled = true;
+ }
+ }
+
+ if (s_minimumButtons is not null &&
+ s_minimumButtons.TryGetValue(hwnd, out var minimumButtonVisual)) {
+ if (minimumButtonVisual is ButtonBase button) {
+ bool shouldClick = false;
+ if ((bool)button.GetValue(s_buttonIsPressedPropertyKey.DependencyProperty)) {
+ shouldClick = true;
+ }
+
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+
+ if (shouldClick) {
+ button.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, button));
+ button.Command?.Execute(button.CommandParameter);
+ }
+
+ handled = true;
+ }
+ }
+
+ if (s_closeButtons is not null &&
+ s_closeButtons.TryGetValue(hwnd, out var closeButtonVisual)) {
+ if (closeButtonVisual is ButtonBase button) {
+ bool shouldClick = false;
+ if ((bool)button.GetValue(s_buttonIsPressedPropertyKey.DependencyProperty)) {
+ shouldClick = true;
+ }
+
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+
+ if (shouldClick) {
+ button.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, button));
+ button.Command?.Execute(button.CommandParameter);
+ }
+
+ handled = true;
+ }
+ }
+
+ break;
+ }
+
+ case NativeDefinition.WM_NCMOUSELEAVE: {
+ var x = (int)((ulong)lParam & 0x0000FFFF);
+ var y = (int)((ulong)lParam & 0xFFFF0000) >> 16;
+
+ if (s_maximumButtons is not null &&
+ s_maximumButtons.TryGetValue(hwnd, out var maximumButtonVisual)) {
+ maximumButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, false);
+
+ if (maximumButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+ }
+ }
+
+ if (s_minimumButtons is not null &&
+ s_minimumButtons.TryGetValue(hwnd, out var minimumButtonVisual)) {
+ minimumButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, false);
+
+ if (minimumButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+ }
+ }
+
+ if (s_closeButtons is not null &&
+ s_closeButtons.TryGetValue(hwnd, out var closeButtonVisual)) {
+ closeButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, false);
+
+ if (closeButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+ }
+ }
+
+ break;
+ }
+ }
+
+ return IntPtr.Zero;
+ }
+
+ #endregion
+
+ #region Utilities
+
+ private static void DoAfterWindowSourceInitialized(Window window, Action action) {
+ var eventHandler = default(EventHandler);
+
+ eventHandler = (s, e) => {
+ action?.Invoke();
+ window.SourceInitialized -= eventHandler;
+ };
+
+ window.SourceInitialized += eventHandler;
+ }
+
+ private static void DoAfterElementLoaded(FrameworkElement element, Action action) {
+ var eventHandler = default(RoutedEventHandler);
+
+ eventHandler = (s, e) => {
+ action?.Invoke();
+ element.Loaded -= eventHandler;
+ };
+
+ element.Loaded += eventHandler;
+ }
+
+ private static bool HasWindowCaptionButton(nint hwnd) {
+ if (s_minimumButtons is not null && s_minimumButtons.ContainsKey(hwnd))
+ return true;
+ if (s_maximumButtons is not null && s_maximumButtons.ContainsKey(hwnd))
+ return true;
+ if (s_closeButtons is not null && s_closeButtons.ContainsKey(hwnd))
+ return true;
+
+ return false;
+ }
+
+ #endregion
+
+ #region Final Logic
+
+ private static unsafe void ApplyIsMaximumButton(Window window, Visual visual, bool isMaximumButton) {
+ var windowInteropHelper = new WindowInteropHelper(window);
+ var windowHandle = windowInteropHelper.EnsureHandle();
+
+ var hwndSource = HwndSource.FromHwnd(windowHandle);
+
+ if (isMaximumButton) {
+ if (s_maximumButtons is null) {
+ s_maximumButtons = new();
+ }
+
+ if (HasWindowCaptionButton(windowHandle)) {
+ hwndSource.AddHook(WindowCaptionButtonsInteropHook);
+ }
+
+ if (s_maximumButtons.ContainsKey(windowHandle)) {
+ throw new InvalidOperationException("MaximumButton is already set to another Visual");
+ }
+
+ s_maximumButtons[windowHandle] = visual;
+ }
+ else {
+ if (s_maximumButtons is null) {
+ return;
+ }
+
+ s_maximumButtons.Remove(windowHandle);
+
+ if (s_maximumButtons.Count == 0) {
+ s_maximumButtons = null;
+ }
+
+ if (!HasWindowCaptionButton(windowHandle)) {
+ hwndSource.RemoveHook(WindowCaptionButtonsInteropHook);
+ }
+ }
+ }
+
+ private static unsafe void ApplyIsMinimumButton(Window window, Visual visual, bool isMinimumButton) {
+ var windowInteropHelper = new WindowInteropHelper(window);
+ var windowHandle = windowInteropHelper.EnsureHandle();
+
+ var hwndSource = HwndSource.FromHwnd(windowHandle);
+
+ if (isMinimumButton) {
+ if (s_minimumButtons is null) {
+ s_minimumButtons = new();
+ }
+
+ if (HasWindowCaptionButton(windowHandle)) {
+ hwndSource.AddHook(WindowCaptionButtonsInteropHook);
+ }
+
+ if (s_minimumButtons.ContainsKey(windowHandle)) {
+ throw new InvalidOperationException("MinimumButton is already set to another Visual");
+ }
+
+ s_minimumButtons[windowHandle] = visual;
+ }
+ else {
+ if (s_minimumButtons is null) {
+ return;
+ }
+
+ s_minimumButtons.Remove(windowHandle);
+
+ if (s_minimumButtons.Count == 0) {
+ s_minimumButtons = null;
+ }
+
+ if (!HasWindowCaptionButton(windowHandle)) {
+ hwndSource.RemoveHook(WindowCaptionButtonsInteropHook);
+ }
+ }
+ }
+
+ private static unsafe void ApplyIsCloseButton(Window window, Visual visual, bool isCloseButton) {
+ var windowInteropHelper = new WindowInteropHelper(window);
+ var windowHandle = windowInteropHelper.EnsureHandle();
+
+ var hwndSource = HwndSource.FromHwnd(windowHandle);
+
+ if (isCloseButton) {
+ if (s_closeButtons is null) {
+ s_closeButtons = new();
+ }
+
+ if (HasWindowCaptionButton(windowHandle)) {
+ hwndSource.AddHook(WindowCaptionButtonsInteropHook);
+ }
+
+ if (s_closeButtons.ContainsKey(windowHandle)) {
+ throw new InvalidOperationException("MinimumButton is already set to another Visual");
+ }
+
+ s_closeButtons[windowHandle] = visual;
+ }
+ else {
+ if (s_closeButtons is null) {
+ return;
+ }
+
+ s_closeButtons.Remove(windowHandle);
+
+ if (s_closeButtons.Count == 0) {
+ s_closeButtons = null;
+ }
+
+ if (!HasWindowCaptionButton(windowHandle)) {
+ hwndSource.RemoveHook(WindowCaptionButtonsInteropHook);
+ }
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dnSpy/dnSpy/Themes/wpf.styles.templates.xaml b/dnSpy/dnSpy/Themes/wpf.styles.templates.xaml
index bed4d2a1ab..22538ebd82 100644
--- a/dnSpy/dnSpy/Themes/wpf.styles.templates.xaml
+++ b/dnSpy/dnSpy/Themes/wpf.styles.templates.xaml
@@ -22,7 +22,8 @@
xmlns:disasmsettingsx86="clr-namespace:dnSpy.Disassembly.X86"
xmlns:disasmviewersettings="clr-namespace:dnSpy.Disassembly.Viewer"
xmlns:bm="clr-namespace:dnSpy.Bookmarks.Settings"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:ws="clr-namespace:EleCho.WpfSuite">