From 69012a0711551c48a4a7bd6f8454107e77bac8f6 Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Tue, 2 Jun 2026 12:08:24 -0500 Subject: [PATCH] Extract WindowChromeManager + WindowPlacementService Move Win32 show/hide/minimize/restore tray-chrome logic (the four user32 P/Invokes, the SW_* constants, and the _isMainWindowHidden flag) into Services/WindowChromeManager, and the DPI-aware bounds save/restore + Maximize logic into Services/WindowPlacementService. MainWindow's MainWindow_Closed now delegates to _placementService.Save(); HideShowMainWindow / MinimizeOrHideMainWindow / HideMainWindowToTray delegate to _chromeManager. Also removes stale PlotBounds/PlotAxisLayout/PlotSeriesSample records that were left behind from the PlotView extraction (dead code; the records now live as private nested types in PlotView). MainWindow shrinks from 1445 to 1362 lines; no behaviour change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MainWindow.cs | 117 +++--------------- .../Services/WindowChromeManager.cs | 61 +++++++++ .../Services/WindowPlacementService.cs | 83 +++++++++++++ 3 files changed, 161 insertions(+), 100 deletions(-) create mode 100644 LibreHardwareMonitor.Windows.WinUI/Services/WindowChromeManager.cs create mode 100644 LibreHardwareMonitor.Windows.WinUI/Services/WindowPlacementService.cs diff --git a/LibreHardwareMonitor.Windows.WinUI/MainWindow.cs b/LibreHardwareMonitor.Windows.WinUI/MainWindow.cs index 24aa8a9..58e7521 100644 --- a/LibreHardwareMonitor.Windows.WinUI/MainWindow.cs +++ b/LibreHardwareMonitor.Windows.WinUI/MainWindow.cs @@ -8,7 +8,6 @@ using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Threading.Tasks; using LibreHardwareMonitor.Hardware; using LibreHardwareMonitor.Windows.WinUI.Controls; @@ -24,7 +23,6 @@ using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Markup; using Microsoft.UI.Xaml.Media; -using Windows.Graphics; using WinRT.Interop; using IOPath = System.IO.Path; @@ -44,6 +42,8 @@ public sealed class MainWindow : Window private const int MaxTextMeasurementCacheEntries = 4096; private readonly AppWindow _appWindow; + private readonly WindowChromeManager _chromeManager; + private readonly WindowPlacementService _placementService; private readonly Grid _contentGrid; private readonly DispatcherQueueTimer _deviceColumnWidthSettleTimer; private readonly DispatcherQueueTimer _timer; @@ -70,7 +70,6 @@ public sealed class MainWindow : Window private bool _isClosingForExit; private bool _isShuttingDown; private bool _runtimeErrorLogged; - private bool _isMainWindowHidden; private bool _isMonitoringStarted; private bool _startupCompletionRequested; private bool _startupCompleteRecorded; @@ -97,6 +96,9 @@ public sealed class MainWindow : Window appWindow.Title = "Libre Hardware Monitor"; return appWindow; }); + IntPtr mainHwnd = WindowNative.GetWindowHandle(this); + _chromeManager = new WindowChromeManager(mainHwnd); + _placementService = new WindowPlacementService(_appWindow, settings, mainHwnd); _trayIconService = MeasureStartup("MainWindow.CreateTrayIconService", () => new TrayIconService( WindowNative.GetWindowHandle(this), @@ -126,8 +128,8 @@ public sealed class MainWindow : Window _sensorTree = sensorTree; _plotView = plotView; - MeasureStartup("MainWindow.RestoreWindowBounds", RestoreWindowBounds); - MeasureStartup("MainWindow.MaximizeWindow", MaximizeWindow); + MeasureStartup("MainWindow.RestoreWindowBounds", _placementService.Restore); + MeasureStartup("MainWindow.MaximizeWindow", _placementService.Maximize); MeasureStartup("MainWindow.ApplyTheme", ApplyTheme); MeasureStartup("MainWindow.UpdatePlotLayout", UpdatePlotLayout); @@ -710,7 +712,7 @@ public sealed class MainWindow : Window _deviceColumnWidthSettleTimer.Stop(); _trayIconService.Dispose(); CloseSecondaryWindows(); - SaveWindowBounds(); + _placementService.Save(); ViewModel.Dispose(); _startupTrace?.Dispose(); } @@ -759,11 +761,15 @@ public sealed class MainWindow : Window private void HideShowMainWindow() { - IntPtr hwnd = WindowNative.GetWindowHandle(this); - if (_isMainWindowHidden || !IsWindowVisible(hwnd) || IsIconic(hwnd)) - RestoreMainWindow(); + if (_chromeManager.IsHiddenOrMinimizedOrInvisible) + { + _chromeManager.Restore(); + Activate(); + } else + { HideMainWindowToTray(); + } } private void MinimizeOrHideMainWindow() @@ -771,28 +777,12 @@ public sealed class MainWindow : Window if (ViewModel.MinimizeToTray) HideMainWindowToTray(); else - { - _isMainWindowHidden = false; - ShowWindow(WindowNative.GetWindowHandle(this), ShowWindowMinimize); - } + _chromeManager.Minimize(); } private void HideMainWindowToTray() { - _isMainWindowHidden = true; - ShowWindow(WindowNative.GetWindowHandle(this), ShowWindowHide); - } - - private void RestoreMainWindow() - { - IntPtr hwnd = WindowNative.GetWindowHandle(this); - _isMainWindowHidden = false; - ShowWindow(hwnd, ShowWindowShow); - // Only un-minimize. The window is hidden to the tray with SW_HIDE while keeping its maximized state, so - // SW_SHOW alone brings it back as it was; an unconditional SW_RESTORE would also un-maximize it. - if (IsIconic(hwnd)) - ShowWindow(hwnd, ShowWindowRestore); - Activate(); + _chromeManager.HideToTray(); } private void SyncTraySensors() @@ -1161,45 +1151,6 @@ public sealed class MainWindow : Window _plotWindow?.ApplyTheme(ViewModel.ThemeMode); } - private void RestoreWindowBounds() - { - // AppWindow.Resize takes physical pixels and the app is PerMonitorV2 DPI-aware, so the logical default/minimum - // sizes must be scaled by the window's DPI. Without this, on a high-DPI display (e.g. a 200% laptop panel) the - // window — and any SW_RESTORE from the tray, which un-maximizes to this size — came out at half size. - double scale = GetWindowScale(); - int minWidth = (int)Math.Round(470 * scale); - int minHeight = (int)Math.Round(640 * scale); - int width = Math.Max(minWidth, ViewModel.Settings.GetValue("mainForm.Width", (int)Math.Round(760 * scale))); - int height = Math.Max(minHeight, ViewModel.Settings.GetValue("mainForm.Height", (int)Math.Round(680 * scale))); - _appWindow.Resize(new SizeInt32(width, height)); - - int x = ViewModel.Settings.GetValue("mainForm.Location.X", int.MinValue); - int y = ViewModel.Settings.GetValue("mainForm.Location.Y", int.MinValue); - if (x != int.MinValue && y != int.MinValue) - _appWindow.Move(new PointInt32(x, y)); - } - - private double GetWindowScale() - { - uint dpi = GetDpiForWindow(WindowNative.GetWindowHandle(this)); - return dpi == 0 ? 1.0 : dpi / 96.0; - } - - private void MaximizeWindow() - { - if (_appWindow.Presenter is OverlappedPresenter presenter) - presenter.Maximize(); - } - - private void SaveWindowBounds() - { - ViewModel.Settings.SetValue("mainForm.Location.X", _appWindow.Position.X); - ViewModel.Settings.SetValue("mainForm.Location.Y", _appWindow.Position.Y); - ViewModel.Settings.SetValue("mainForm.Width", _appWindow.Size.Width); - ViewModel.Settings.SetValue("mainForm.Height", _appWindow.Size.Height); - ViewModel.Save(); - } - private void UpdateSensorColumnWidths() { MeasureStartup("MainWindow.UpdateSensorColumnWidths", UpdateSensorColumnWidthsCore, () => FormattableString.Invariant($"rows={_sensorRowGrids.Count}, cacheEntries={_textMeasurementCache.Count}, deviceWidth={_sensorColumnWidths[0]:F0}, settled={_deviceColumnWidthSettled}")); @@ -1398,40 +1349,6 @@ public sealed class MainWindow : Window return item; } - private const int ShowWindowHide = 0; - private const int ShowWindowShow = 5; - private const int ShowWindowMinimize = 6; - private const int ShowWindowRestore = 9; - - [DllImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool ShowWindow(IntPtr windowHandle, int command); - - [DllImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool IsWindowVisible(IntPtr windowHandle); - - [DllImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool IsIconic(IntPtr windowHandle); - - [DllImport("user32.dll")] - private static extern uint GetDpiForWindow(IntPtr windowHandle); - - private sealed record PlotBounds(double Left, double Top, double Width, double Height) - { - public double Bottom => Top + Height; - - public double Right => Left + Width; - } - - private sealed record PlotAxisLayout(SensorType SensorType, string Unit, double Top, double Height, double MinValue, double MaxValue) - { - public double Bottom => Top + Height; - } - - private sealed record PlotSeriesSample(PlotSeriesViewModel Series, IReadOnlyList Points); - private static void Bind(DependencyObject target, DependencyProperty property, object source, string path, BindingMode mode = BindingMode.OneWay) { BindingOperations.SetBinding(target, property, new Binding diff --git a/LibreHardwareMonitor.Windows.WinUI/Services/WindowChromeManager.cs b/LibreHardwareMonitor.Windows.WinUI/Services/WindowChromeManager.cs new file mode 100644 index 0000000..ae8f8e3 --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI/Services/WindowChromeManager.cs @@ -0,0 +1,61 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// Copyright (C) LibreHardwareMonitor and Contributors. + +using System; +using System.Runtime.InteropServices; + +namespace LibreHardwareMonitor.Windows.WinUI.Services; + +internal sealed class WindowChromeManager +{ + private const int ShowWindowHide = 0; + private const int ShowWindowShow = 5; + private const int ShowWindowMinimize = 6; + private const int ShowWindowRestore = 9; + + private readonly IntPtr _hwnd; + + public WindowChromeManager(IntPtr hwnd) + { + _hwnd = hwnd; + } + + public bool IsHidden { get; private set; } + + public bool IsHiddenOrMinimizedOrInvisible => IsHidden || !IsWindowVisible(_hwnd) || IsIconic(_hwnd); + + public void HideToTray() + { + IsHidden = true; + ShowWindow(_hwnd, ShowWindowHide); + } + + public void Minimize() + { + IsHidden = false; + ShowWindow(_hwnd, ShowWindowMinimize); + } + + public void Restore() + { + IsHidden = false; + ShowWindow(_hwnd, ShowWindowShow); + // Only un-minimize. The window is hidden to the tray with SW_HIDE while keeping its maximized state, so + // SW_SHOW alone brings it back as it was; an unconditional SW_RESTORE would also un-maximize it. + if (IsIconic(_hwnd)) + ShowWindow(_hwnd, ShowWindowRestore); + } + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool ShowWindow(IntPtr windowHandle, int command); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsWindowVisible(IntPtr windowHandle); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsIconic(IntPtr windowHandle); +} diff --git a/LibreHardwareMonitor.Windows.WinUI/Services/WindowPlacementService.cs b/LibreHardwareMonitor.Windows.WinUI/Services/WindowPlacementService.cs new file mode 100644 index 0000000..3998c92 --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI/Services/WindowPlacementService.cs @@ -0,0 +1,83 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// Copyright (C) LibreHardwareMonitor and Contributors. + +using System; +using System.Runtime.InteropServices; +using Microsoft.UI.Windowing; +using Windows.Graphics; + +namespace LibreHardwareMonitor.Windows.WinUI.Services; + +internal sealed class WindowPlacementService +{ + private readonly AppWindow _appWindow; + private readonly AppSettings _settings; + private readonly IntPtr _hwnd; + private readonly string _settingPrefix; + private readonly int _defaultLogicalMinWidth; + private readonly int _defaultLogicalMinHeight; + private readonly int _defaultLogicalWidth; + private readonly int _defaultLogicalHeight; + + public WindowPlacementService( + AppWindow appWindow, + AppSettings settings, + IntPtr hwnd, + string settingPrefix = "mainForm.", + int defaultLogicalMinWidth = 470, + int defaultLogicalMinHeight = 640, + int defaultLogicalWidth = 760, + int defaultLogicalHeight = 680) + { + _appWindow = appWindow; + _settings = settings; + _hwnd = hwnd; + _settingPrefix = settingPrefix; + _defaultLogicalMinWidth = defaultLogicalMinWidth; + _defaultLogicalMinHeight = defaultLogicalMinHeight; + _defaultLogicalWidth = defaultLogicalWidth; + _defaultLogicalHeight = defaultLogicalHeight; + } + + public double GetWindowScale() + { + uint dpi = GetDpiForWindow(_hwnd); + return dpi == 0 ? 1.0 : dpi / 96.0; + } + + public void Restore() + { + // AppWindow.Resize takes physical pixels and the app is PerMonitorV2 DPI-aware, so the logical default/minimum + // sizes must be scaled by the window's DPI. Without this, on a high-DPI display (e.g. a 200% laptop panel) the + // window — and any SW_RESTORE from the tray, which un-maximizes to this size — came out at half size. + double scale = GetWindowScale(); + int minWidth = (int)Math.Round(_defaultLogicalMinWidth * scale); + int minHeight = (int)Math.Round(_defaultLogicalMinHeight * scale); + int width = Math.Max(minWidth, _settings.GetValue(_settingPrefix + "Width", (int)Math.Round(_defaultLogicalWidth * scale))); + int height = Math.Max(minHeight, _settings.GetValue(_settingPrefix + "Height", (int)Math.Round(_defaultLogicalHeight * scale))); + _appWindow.Resize(new SizeInt32(width, height)); + + int x = _settings.GetValue(_settingPrefix + "Location.X", int.MinValue); + int y = _settings.GetValue(_settingPrefix + "Location.Y", int.MinValue); + if (x != int.MinValue && y != int.MinValue) + _appWindow.Move(new PointInt32(x, y)); + } + + public void Maximize() + { + if (_appWindow.Presenter is OverlappedPresenter presenter) + presenter.Maximize(); + } + + public void Save() + { + _settings.SetValue(_settingPrefix + "Location.X", _appWindow.Position.X); + _settings.SetValue(_settingPrefix + "Location.Y", _appWindow.Position.Y); + _settings.SetValue(_settingPrefix + "Width", _appWindow.Size.Width); + _settings.SetValue(_settingPrefix + "Height", _appWindow.Size.Height); + } + + [DllImport("user32.dll")] + private static extern uint GetDpiForWindow(IntPtr windowHandle); +}