Extract SecondaryWindowCoordinator for plot/gadget lifecycle

Move the PlotWindow + SensorGadgetWindow create/show/sync/close lifecycle out of MainWindow into Services/SecondaryWindowCoordinator. The coordinator owns the _plotWindow and _gadgetWindow fields and exposes UpdatePlotWindowVisibility / UpdateGadgetVisibility / SyncGadgetSensors / ApplyTheme / RedrawPlot / CloseAll.

MainWindow's matching methods become one-line delegators; the gadget's HideShowMainWindowRequested event is wired back via a callback passed into the coordinator's constructor.

MainWindow shrinks from 1362 to 1300 lines; no behaviour change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-06-02 13:04:51 -05:00
co-authored by Copilot
parent 69012a0711
commit 0cfb9ba3a4
2 changed files with 119 additions and 72 deletions
@@ -59,9 +59,8 @@ public sealed class MainWindow : Window
private TextBlock? _measurementTextBlock;
private readonly List<Grid> _sensorRowGrids = [];
private readonly double[] _sensorColumnWidths = [DefaultSensorColumnWidth, 120, 120, 120];
private PlotWindow? _plotWindow;
private readonly SecondaryWindowCoordinator _secondaryWindows;
private Grid? _sensorHeader;
private SensorGadgetWindow? _gadgetWindow;
private double _stableDeviceColumnWidth = DefaultSensorColumnWidth;
private bool _deviceColumnWidthSettled;
private bool _firstLayoutRecorded;
@@ -106,6 +105,7 @@ public sealed class MainWindow : Window
() => ViewModel.TemperatureUnit));
_trayIconService.IsMainIconEnabled = ViewModel.MinimizeToTray;
_dialogService = new DialogService(() => Content.XamlRoot, ViewModel, WindowNative.GetWindowHandle(this));
_secondaryWindows = new SecondaryWindowCoordinator(ViewModel, HideShowMainWindow);
Grid root = MeasureStartup("MainWindow.BuildRoot", BuildRoot);
MeasureStartup("MainWindow.AssignContent", () => Content = root);
@@ -653,7 +653,7 @@ public sealed class MainWindow : Window
UpdateSensorColumnWidths();
_trayIconService.Update();
_gadgetWindow?.UpdateSensors(ViewModel.GetGadgetSensorItems());
_secondaryWindows.SyncGadgetSensors();
DrawPlot();
}
catch (Exception ex)
@@ -711,7 +711,7 @@ public sealed class MainWindow : Window
_timer.Stop();
_deviceColumnWidthSettleTimer.Stop();
_trayIconService.Dispose();
CloseSecondaryWindows();
_secondaryWindows.CloseAll();
_placementService.Save();
ViewModel.Dispose();
_startupTrace?.Dispose();
@@ -742,23 +742,6 @@ public sealed class MainWindow : Window
Close();
}
private void CloseSecondaryWindows()
{
if (_gadgetWindow != null)
{
SensorGadgetWindow gadgetWindow = _gadgetWindow;
_gadgetWindow = null;
gadgetWindow.CloseFromOwner();
}
if (_plotWindow != null)
{
PlotWindow plotWindow = _plotWindow;
_plotWindow = null;
plotWindow.CloseFromOwner();
}
}
private void HideShowMainWindow()
{
if (_chromeManager.IsHiddenOrMinimizedOrInvisible)
@@ -792,62 +775,17 @@ public sealed class MainWindow : Window
private void SyncGadgetSensors()
{
_gadgetWindow?.UpdateSensors(ViewModel.GetGadgetSensorItems());
_secondaryWindows.SyncGadgetSensors();
}
private void UpdateGadgetVisibility()
{
if (ViewModel.ShowGadget)
{
if (_gadgetWindow == null)
{
_gadgetWindow = new SensorGadgetWindow(ViewModel);
_gadgetWindow.HideShowMainWindowRequested += (_, _) => HideShowMainWindow();
_gadgetWindow.UserClosed += (_, _) =>
{
_gadgetWindow = null;
ViewModel.ShowGadget = false;
};
SyncGadgetSensors();
_gadgetWindow.Activate();
}
return;
}
if (_gadgetWindow != null)
{
SensorGadgetWindow gadgetWindow = _gadgetWindow;
_gadgetWindow = null;
gadgetWindow.CloseFromOwner();
}
_secondaryWindows.UpdateGadgetVisibility();
}
private void UpdatePlotWindowVisibility()
{
if (ViewModel.IsPlotWindowVisible)
{
if (_plotWindow == null)
{
_plotWindow = new PlotWindow(ViewModel.Settings, ViewModel);
_plotWindow.ApplyTheme(ViewModel.ThemeMode);
_plotWindow.UserClosed += (_, _) =>
{
_plotWindow = null;
ViewModel.ShowPlot = false;
};
_plotWindow.Activate();
}
return;
}
if (_plotWindow != null)
{
PlotWindow plotWindow = _plotWindow;
_plotWindow = null;
plotWindow.CloseFromOwner();
}
_secondaryWindows.UpdatePlotWindowVisibility();
}
private void RebuildSensorTree()
@@ -1102,7 +1040,7 @@ public sealed class MainWindow : Window
{
ViewModel.ResetPlot();
_plotView.ResetZoom();
_plotWindow?.RedrawPlot();
_secondaryWindows.RedrawPlot();
}
private void DrawPlot()
@@ -1112,7 +1050,7 @@ public sealed class MainWindow : Window
else
_plotView.Redraw();
_plotWindow?.RedrawPlot();
_secondaryWindows.RedrawPlot();
}
private void UpdatePlotLayout()
@@ -1148,7 +1086,7 @@ public sealed class MainWindow : Window
};
_plotView.ApplyTheme(ViewModel.ThemeMode);
_plotWindow?.ApplyTheme(ViewModel.ThemeMode);
_secondaryWindows.ApplyTheme(ViewModel.ThemeMode);
}
private void UpdateSensorColumnWidths()
@@ -0,0 +1,109 @@
// 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 LibreHardwareMonitor.Windows.WinUI.ViewModels;
namespace LibreHardwareMonitor.Windows.WinUI.Services;
internal sealed class SecondaryWindowCoordinator
{
private readonly MainWindowViewModel _viewModel;
private readonly Action _hideShowMainWindowRequested;
private PlotWindow? _plotWindow;
private SensorGadgetWindow? _gadgetWindow;
public SecondaryWindowCoordinator(MainWindowViewModel viewModel, Action hideShowMainWindowRequested)
{
_viewModel = viewModel ?? throw new ArgumentNullException(nameof(viewModel));
_hideShowMainWindowRequested = hideShowMainWindowRequested ?? throw new ArgumentNullException(nameof(hideShowMainWindowRequested));
}
public void UpdateGadgetVisibility()
{
if (_viewModel.ShowGadget)
{
if (_gadgetWindow == null)
{
_gadgetWindow = new SensorGadgetWindow(_viewModel);
_gadgetWindow.HideShowMainWindowRequested += (_, _) => _hideShowMainWindowRequested();
_gadgetWindow.UserClosed += (_, _) =>
{
_gadgetWindow = null;
_viewModel.ShowGadget = false;
};
SyncGadgetSensors();
_gadgetWindow.Activate();
}
return;
}
if (_gadgetWindow != null)
{
SensorGadgetWindow gadgetWindow = _gadgetWindow;
_gadgetWindow = null;
gadgetWindow.CloseFromOwner();
}
}
public void UpdatePlotWindowVisibility()
{
if (_viewModel.IsPlotWindowVisible)
{
if (_plotWindow == null)
{
_plotWindow = new PlotWindow(_viewModel.Settings, _viewModel);
_plotWindow.ApplyTheme(_viewModel.ThemeMode);
_plotWindow.UserClosed += (_, _) =>
{
_plotWindow = null;
_viewModel.ShowPlot = false;
};
_plotWindow.Activate();
}
return;
}
if (_plotWindow != null)
{
PlotWindow plotWindow = _plotWindow;
_plotWindow = null;
plotWindow.CloseFromOwner();
}
}
public void SyncGadgetSensors()
{
_gadgetWindow?.UpdateSensors(_viewModel.GetGadgetSensorItems());
}
public void ApplyTheme(AppThemeMode themeMode)
{
_plotWindow?.ApplyTheme(themeMode);
}
public void RedrawPlot()
{
_plotWindow?.RedrawPlot();
}
public void CloseAll()
{
if (_gadgetWindow != null)
{
SensorGadgetWindow gadgetWindow = _gadgetWindow;
_gadgetWindow = null;
gadgetWindow.CloseFromOwner();
}
if (_plotWindow != null)
{
PlotWindow plotWindow = _plotWindow;
_plotWindow = null;
plotWindow.CloseFromOwner();
}
}
}