Extract DialogService from MainWindow

Moves the seven ContentDialog flows (rename, pen color, sensor parameters,
web-server settings, web-server authentication, save report, about) plus the
save-report file picker out of MainWindow into a DialogService. The service
reads the live XamlRoot through a provider and writes results back through the
view model; MainWindow's menu/context-menu handlers now delegate to it. The
parameter-editor row builder, the IPv4-address helper, and the
ParameterEditorRow record (all used only by these dialogs) moved with them, and
four now-unused usings were dropped.

First code-only step of the XAML migration; pure relocation, behavior
unchanged. Verified by clean build, 199 tests, and an app smoke-run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 11:26:35 -05:00
co-authored by Claude Opus 4.8
parent 6523b570c2
commit ad16c9f3a4
2 changed files with 386 additions and 348 deletions
@@ -0,0 +1,376 @@
// 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.Globalization;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using LibreHardwareMonitor.Hardware;
using LibreHardwareMonitor.Windows.WinUI.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Windows.Storage;
using Windows.Storage.Pickers;
using WinRT.Interop;
namespace LibreHardwareMonitor.Windows.WinUI;
/// <summary>
/// Builds and shows the app's content dialogs (rename, pen color, sensor parameters, web-server settings and
/// authentication, save report, about) and the save-report file picker. Extracted from <see cref="MainWindow" /> so the
/// modal UI flows live in one place; it reads the live <see cref="XamlRoot" /> through a provider and writes results
/// back through the view model.
/// </summary>
internal sealed class DialogService
{
private readonly Func<XamlRoot?> _xamlRootProvider;
private readonly MainWindowViewModel _viewModel;
private readonly IntPtr _windowHandle;
public DialogService(Func<XamlRoot?> xamlRootProvider, MainWindowViewModel viewModel, IntPtr windowHandle)
{
_xamlRootProvider = xamlRootProvider;
_viewModel = viewModel;
_windowHandle = windowHandle;
}
public async Task RenameAsync(SensorTreeItemViewModel item)
{
if (!item.CanRename)
return;
TextBox nameTextBox = new() { Text = item.Text, SelectionStart = 0, SelectionLength = item.Text.Length };
ContentDialog dialog = new()
{
XamlRoot = _xamlRootProvider(),
Title = "Rename Sensor",
Content = nameTextBox,
PrimaryButtonText = "Save",
CloseButtonText = "Cancel",
DefaultButton = ContentDialogButton.Primary
};
if (await dialog.ShowAsync() == ContentDialogResult.Primary)
item.Text = nameTextBox.Text.Trim();
}
public async Task ShowPenColorAsync(SensorTreeItemViewModel item)
{
if (item.Sensor == null)
return;
ColorPicker picker = new()
{
Color = item.PenColor ?? global::Windows.UI.Color.FromArgb(255, 0, 120, 212),
IsAlphaEnabled = false,
IsColorSliderVisible = true,
IsColorChannelTextInputVisible = true,
IsHexInputVisible = true
};
ContentDialog dialog = new()
{
XamlRoot = _xamlRootProvider(),
Title = "Pen Color",
Content = picker,
PrimaryButtonText = "Save",
CloseButtonText = "Cancel",
DefaultButton = ContentDialogButton.Primary
};
if (await dialog.ShowAsync() == ContentDialogResult.Primary)
_viewModel.SetSensorPenColor(item, picker.Color);
}
public async Task ShowParametersAsync(SensorTreeItemViewModel item)
{
if (item.Sensor == null || item.Sensor.Parameters.Count == 0)
return;
StackPanel panel = new() { Spacing = 10 };
TextBlock caption = new()
{
Text = item.Sensor.Name,
FontWeight = new global::Windows.UI.Text.FontWeight { Weight = 600 },
TextWrapping = TextWrapping.Wrap
};
panel.Children.Add(caption);
ParameterEditorRow[] rows = item.Sensor.Parameters.Select(CreateParameterEditorRow).ToArray();
foreach (ParameterEditorRow row in rows)
panel.Children.Add(row.Container);
ContentDialog dialog = new()
{
XamlRoot = _xamlRootProvider(),
Title = "Parameters",
Content = new ScrollViewer { Content = panel, MaxHeight = 520 },
PrimaryButtonText = "OK",
CloseButtonText = "Cancel",
DefaultButton = ContentDialogButton.Primary
};
dialog.PrimaryButtonClick += (sender, args) =>
{
foreach (ParameterEditorRow row in rows)
{
if (row.UseDefault.IsChecked == true)
continue;
if (!float.TryParse(row.Value.Text, NumberStyles.Float, CultureInfo.CurrentCulture, out float _)
&& !float.TryParse(row.Value.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out float _))
{
row.Value.Header = "Invalid value";
args.Cancel = true;
return;
}
}
foreach (ParameterEditorRow row in rows)
{
if (row.UseDefault.IsChecked == true)
row.Parameter.IsDefault = true;
else if (float.TryParse(row.Value.Text, NumberStyles.Float, CultureInfo.CurrentCulture, out float currentCultureValue)
|| float.TryParse(row.Value.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out currentCultureValue))
row.Parameter.Value = currentCultureValue;
}
};
await dialog.ShowAsync();
}
public async Task ShowWebServerSettingsAsync()
{
ComboBox interfaces = new() { MinWidth = 260 };
foreach (string address in GetLocalIPv4Addresses())
interfaces.Items.Add(address);
interfaces.Items.Add("0.0.0.0");
interfaces.SelectedItem = interfaces.Items.Contains(_viewModel.ListenerIp) ? _viewModel.ListenerIp : "0.0.0.0";
TextBox port = new()
{
Header = "Port",
Text = _viewModel.ListenerPort.ToString(CultureInfo.InvariantCulture),
InputScope = new InputScope { Names = { new InputScopeName(InputScopeNameValue.Number) } }
};
HyperlinkButton link = new()
{
Content = _viewModel.WebServerUrl,
NavigateUri = new Uri(_viewModel.WebServerUrl)
};
StackPanel content = new() { Spacing = 12 };
content.Children.Add(new TextBlock { Text = "Interface", FontWeight = new global::Windows.UI.Text.FontWeight { Weight = 600 } });
content.Children.Add(interfaces);
content.Children.Add(port);
content.Children.Add(link);
ContentDialog dialog = new()
{
XamlRoot = _xamlRootProvider(),
Title = "Remote Web Server",
Content = content,
PrimaryButtonText = "Save",
CloseButtonText = "Cancel",
DefaultButton = ContentDialogButton.Primary
};
dialog.PrimaryButtonClick += (_, args) =>
{
if (!int.TryParse(port.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out int parsedPort) || parsedPort < 1 || parsedPort > 65535)
{
port.Header = "Port must be 1-65535";
args.Cancel = true;
return;
}
_viewModel.ListenerIp = interfaces.SelectedItem as string ?? "0.0.0.0";
_viewModel.ListenerPort = parsedPort;
};
await dialog.ShowAsync();
}
public async Task ShowWebServerAuthenticationAsync()
{
CheckBox enabled = new() { Content = "Enable HTTP authentication", IsChecked = _viewModel.AuthWebServer };
TextBox userName = new()
{
Header = "Username",
Text = _viewModel.AuthWebServerUserName,
IsEnabled = enabled.IsChecked == true
};
PasswordBox password = new()
{
Header = "New password",
IsEnabled = enabled.IsChecked == true
};
enabled.Checked += (_, _) => userName.IsEnabled = password.IsEnabled = true;
enabled.Unchecked += (_, _) => userName.IsEnabled = password.IsEnabled = false;
StackPanel content = new() { Spacing = 12 };
content.Children.Add(enabled);
content.Children.Add(userName);
content.Children.Add(password);
ContentDialog dialog = new()
{
XamlRoot = _xamlRootProvider(),
Title = "Remote Web Server Authentication",
Content = content,
PrimaryButtonText = "Save",
CloseButtonText = "Cancel",
DefaultButton = ContentDialogButton.Primary
};
dialog.PrimaryButtonClick += (_, _) =>
{
_viewModel.AuthWebServer = enabled.IsChecked == true;
_viewModel.AuthWebServerUserName = userName.Text;
_viewModel.SetAuthPassword(password.Password);
};
await dialog.ShowAsync();
}
public async Task SaveReportAsync()
{
FileSavePicker picker = new()
{
SuggestedFileName = "LibreHardwareMonitor.Report",
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
};
picker.FileTypeChoices.Add("Text", [".txt"]);
InitializeWithWindow.Initialize(picker, _windowHandle);
StorageFile? file = await picker.PickSaveFileAsync();
if (file == null)
return;
CachedFileManager.DeferUpdates(file);
await FileIO.WriteTextAsync(file, _viewModel.GetReport());
await CachedFileManager.CompleteUpdatesAsync(file);
}
public async Task ShowAboutAsync()
{
StackPanel content = new() { Spacing = 8 };
content.Children.Add(new TextBlock
{
Text = "Libre Hardware Monitor",
FontWeight = new global::Windows.UI.Text.FontWeight { Weight = 600 },
FontSize = 18
});
content.Children.Add(new TextBlock
{
Text = $"Version {typeof(DialogService).Assembly.GetName().Version}",
TextWrapping = TextWrapping.Wrap
});
content.Children.Add(new TextBlock
{
Text = "A free, open source hardware monitoring application.",
TextWrapping = TextWrapping.Wrap
});
content.Children.Add(new HyperlinkButton
{
Content = "Project website",
NavigateUri = new Uri("https://github.com/LibreHardwareMonitor/LibreHardwareMonitor")
});
content.Children.Add(new HyperlinkButton
{
Content = "Mozilla Public License 2.0",
NavigateUri = new Uri("https://www.mozilla.org/MPL/2.0/")
});
ContentDialog dialog = new()
{
XamlRoot = _xamlRootProvider(),
Title = "Libre Hardware Monitor",
Content = content,
CloseButtonText = "OK"
};
await dialog.ShowAsync();
}
private static ParameterEditorRow CreateParameterEditorRow(IParameter parameter)
{
Grid grid = new()
{
ColumnDefinitions =
{
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) },
new ColumnDefinition { Width = GridLength.Auto },
new ColumnDefinition { Width = new GridLength(120) }
},
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto }
},
ColumnSpacing = 8
};
TextBlock name = new()
{
Text = parameter.Name,
VerticalAlignment = VerticalAlignment.Center,
TextWrapping = TextWrapping.Wrap
};
grid.Children.Add(name);
CheckBox useDefault = new()
{
Content = "Default",
IsChecked = parameter.IsDefault,
VerticalAlignment = VerticalAlignment.Center
};
Grid.SetColumn(useDefault, 1);
grid.Children.Add(useDefault);
TextBox value = new()
{
Text = parameter.Value.ToString(CultureInfo.CurrentCulture),
IsEnabled = !parameter.IsDefault
};
Grid.SetColumn(value, 2);
grid.Children.Add(value);
TextBlock description = new()
{
Text = parameter.Description,
Opacity = 0.72,
TextWrapping = TextWrapping.Wrap
};
Grid.SetRow(description, 1);
Grid.SetColumnSpan(description, 3);
grid.Children.Add(description);
useDefault.Checked += (_, _) => value.IsEnabled = false;
useDefault.Unchecked += (_, _) => value.IsEnabled = true;
return new ParameterEditorRow(parameter, grid, useDefault, value);
}
private static string[] GetLocalIPv4Addresses()
{
try
{
return Dns.GetHostEntry(Dns.GetHostName())
.AddressList
.Where(address => address.AddressFamily == AddressFamily.InterNetwork)
.Select(address => address.ToString())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
}
catch
{
return [];
}
}
private sealed record ParameterEditorRow(IParameter Parameter, Grid Container, CheckBox UseDefault, TextBox Value);
}
+10 -348
View File
@@ -8,8 +8,6 @@ using System.Collections.Specialized;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using LibreHardwareMonitor.Hardware; using LibreHardwareMonitor.Hardware;
@@ -29,8 +27,6 @@ using Microsoft.UI.Xaml.Markup;
using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes; using Microsoft.UI.Xaml.Shapes;
using Windows.Graphics; using Windows.Graphics;
using Windows.Storage;
using Windows.Storage.Pickers;
using WinRT.Interop; using WinRT.Interop;
using IOPath = System.IO.Path; using IOPath = System.IO.Path;
@@ -82,6 +78,7 @@ public sealed class MainWindow : Window
private readonly Grid _sensorPane; private readonly Grid _sensorPane;
private readonly WinUiStartupTrace? _startupTrace; private readonly WinUiStartupTrace? _startupTrace;
private readonly TrayIconService _trayIconService; private readonly TrayIconService _trayIconService;
private readonly DialogService _dialogService;
private readonly Dictionary<(string Text, bool Bold), double> _textMeasurementCache = new(); private readonly Dictionary<(string Text, bool Bold), double> _textMeasurementCache = new();
private TextBlock? _measurementTextBlock; private TextBlock? _measurementTextBlock;
private readonly List<Grid> _sensorRowGrids = []; private readonly List<Grid> _sensorRowGrids = [];
@@ -131,6 +128,7 @@ public sealed class MainWindow : Window
settings, settings,
() => ViewModel.TemperatureUnit)); () => ViewModel.TemperatureUnit));
_trayIconService.IsMainIconEnabled = ViewModel.MinimizeToTray; _trayIconService.IsMainIconEnabled = ViewModel.MinimizeToTray;
_dialogService = new DialogService(() => Content.XamlRoot, ViewModel, WindowNative.GetWindowHandle(this));
Grid root = MeasureStartup("MainWindow.BuildRoot", BuildRoot); Grid root = MeasureStartup("MainWindow.BuildRoot", BuildRoot);
MeasureStartup("MainWindow.AssignContent", () => Content = root); MeasureStartup("MainWindow.AssignContent", () => Content = root);
@@ -382,7 +380,7 @@ public sealed class MainWindow : Window
Bind(menuBar, Control.IsEnabledProperty, ViewModel, nameof(ViewModel.IsHardwareInteractionEnabled)); Bind(menuBar, Control.IsEnabledProperty, ViewModel, nameof(ViewModel.IsHardwareInteractionEnabled));
MenuBarItem file = new() { Title = "File" }; MenuBarItem file = new() { Title = "File" };
file.Items.Add(CreateMenuItem("Save Report...", async (_, _) => await SaveReportAsync())); file.Items.Add(CreateMenuItem("Save Report...", async (_, _) => await _dialogService.SaveReportAsync()));
file.Items.Add(new MenuFlyoutSeparator()); file.Items.Add(new MenuFlyoutSeparator());
file.Items.Add(CreateMenuItem("Reset", (_, _) => ViewModel.ResetHardware())); file.Items.Add(CreateMenuItem("Reset", (_, _) => ViewModel.ResetHardware()));
MenuFlyoutSubItem hardware = new() { Text = "Hardware" }; MenuFlyoutSubItem hardware = new() { Text = "Hardware" };
@@ -501,17 +499,17 @@ public sealed class MainWindow : Window
ToggleMenuFlyoutItem runWebServer = CreateToggleItem("Run", nameof(ViewModel.RunWebServer)); ToggleMenuFlyoutItem runWebServer = CreateToggleItem("Run", nameof(ViewModel.RunWebServer));
runWebServer.IsEnabled = !ViewModel.IsWebServerUnavailable; runWebServer.IsEnabled = !ViewModel.IsWebServerUnavailable;
webServer.Items.Add(runWebServer); webServer.Items.Add(runWebServer);
MenuFlyoutItem interfacePort = CreateMenuItem("Interface / Port", async (_, _) => await ShowWebServerSettingsAsync()); MenuFlyoutItem interfacePort = CreateMenuItem("Interface / Port", async (_, _) => await _dialogService.ShowWebServerSettingsAsync());
interfacePort.IsEnabled = !ViewModel.IsWebServerUnavailable; interfacePort.IsEnabled = !ViewModel.IsWebServerUnavailable;
webServer.Items.Add(interfacePort); webServer.Items.Add(interfacePort);
MenuFlyoutItem authentication = CreateMenuItem("Authentication", async (_, _) => await ShowWebServerAuthenticationAsync()); MenuFlyoutItem authentication = CreateMenuItem("Authentication", async (_, _) => await _dialogService.ShowWebServerAuthenticationAsync());
authentication.IsEnabled = !ViewModel.IsWebServerUnavailable; authentication.IsEnabled = !ViewModel.IsWebServerUnavailable;
webServer.Items.Add(authentication); webServer.Items.Add(authentication);
options.Items.Add(webServer); options.Items.Add(webServer);
menuBar.Items.Add(options); menuBar.Items.Add(options);
MenuBarItem help = new() { Title = "Help" }; MenuBarItem help = new() { Title = "Help" };
help.Items.Add(CreateMenuItem("About", async (_, _) => await ShowAboutAsync())); help.Items.Add(CreateMenuItem("About", async (_, _) => await _dialogService.ShowAboutAsync()));
menuBar.Items.Add(help); menuBar.Items.Add(help);
return menuBar; return menuBar;
@@ -1024,7 +1022,7 @@ public sealed class MainWindow : Window
row.DoubleTapped += async (_, _) => row.DoubleTapped += async (_, _) =>
{ {
if (item.Sensor?.Parameters.Count > 0) if (item.Sensor?.Parameters.Count > 0)
await ShowParametersAsync(item); await _dialogService.ShowParametersAsync(item);
}; };
return row; return row;
} }
@@ -1033,9 +1031,9 @@ public sealed class MainWindow : Window
{ {
MenuFlyout flyout = new(); MenuFlyout flyout = new();
if (item.Sensor?.Parameters.Count > 0) if (item.Sensor?.Parameters.Count > 0)
flyout.Items.Add(CreateMenuItem("Parameters...", async (_, _) => await ShowParametersAsync(item))); flyout.Items.Add(CreateMenuItem("Parameters...", async (_, _) => await _dialogService.ShowParametersAsync(item)));
MenuFlyoutItem rename = CreateMenuItem("Rename", async (_, _) => await RenameNodeAsync(item)); MenuFlyoutItem rename = CreateMenuItem("Rename", async (_, _) => await _dialogService.RenameAsync(item));
rename.IsEnabled = item.CanRename; rename.IsEnabled = item.CanRename;
flyout.Items.Add(rename); flyout.Items.Add(rename);
@@ -1081,7 +1079,7 @@ public sealed class MainWindow : Window
hide.IsEnabled = item.CanHide; hide.IsEnabled = item.CanHide;
flyout.Items.Add(hide); flyout.Items.Add(hide);
flyout.Items.Add(new MenuFlyoutSeparator()); flyout.Items.Add(new MenuFlyoutSeparator());
MenuFlyoutItem penColor = CreateMenuItem("Pen Color...", async (_, _) => await ShowPenColorAsync(item)); MenuFlyoutItem penColor = CreateMenuItem("Pen Color...", async (_, _) => await _dialogService.ShowPenColorAsync(item));
penColor.IsEnabled = item.Sensor != null; penColor.IsEnabled = item.Sensor != null;
flyout.Items.Add(penColor); flyout.Items.Add(penColor);
MenuFlyoutItem resetPenColor = CreateMenuItem("Reset Pen Color", (_, _) => ViewModel.SetSensorPenColor(item, null)); MenuFlyoutItem resetPenColor = CreateMenuItem("Reset Pen Color", (_, _) => ViewModel.SetSensorPenColor(item, null));
@@ -1215,264 +1213,6 @@ public sealed class MainWindow : Window
return controlItem; return controlItem;
} }
private async Task RenameNodeAsync(SensorTreeItemViewModel item)
{
if (!item.CanRename)
return;
TextBox nameTextBox = new() { Text = item.Text, SelectionStart = 0, SelectionLength = item.Text.Length };
ContentDialog dialog = new()
{
XamlRoot = Content.XamlRoot,
Title = "Rename Sensor",
Content = nameTextBox,
PrimaryButtonText = "Save",
CloseButtonText = "Cancel",
DefaultButton = ContentDialogButton.Primary
};
if (await dialog.ShowAsync() == ContentDialogResult.Primary)
item.Text = nameTextBox.Text.Trim();
}
private async Task ShowPenColorAsync(SensorTreeItemViewModel item)
{
if (item.Sensor == null)
return;
ColorPicker picker = new()
{
Color = item.PenColor ?? global::Windows.UI.Color.FromArgb(255, 0, 120, 212),
IsAlphaEnabled = false,
IsColorSliderVisible = true,
IsColorChannelTextInputVisible = true,
IsHexInputVisible = true
};
ContentDialog dialog = new()
{
XamlRoot = Content.XamlRoot,
Title = "Pen Color",
Content = picker,
PrimaryButtonText = "Save",
CloseButtonText = "Cancel",
DefaultButton = ContentDialogButton.Primary
};
if (await dialog.ShowAsync() == ContentDialogResult.Primary)
ViewModel.SetSensorPenColor(item, picker.Color);
}
private async Task ShowParametersAsync(SensorTreeItemViewModel item)
{
if (item.Sensor == null || item.Sensor.Parameters.Count == 0)
return;
StackPanel panel = new() { Spacing = 10 };
TextBlock caption = new()
{
Text = item.Sensor.Name,
FontWeight = new global::Windows.UI.Text.FontWeight { Weight = 600 },
TextWrapping = TextWrapping.Wrap
};
panel.Children.Add(caption);
ParameterEditorRow[] rows = item.Sensor.Parameters.Select(CreateParameterEditorRow).ToArray();
foreach (ParameterEditorRow row in rows)
panel.Children.Add(row.Container);
ContentDialog dialog = new()
{
XamlRoot = Content.XamlRoot,
Title = "Parameters",
Content = new ScrollViewer { Content = panel, MaxHeight = 520 },
PrimaryButtonText = "OK",
CloseButtonText = "Cancel",
DefaultButton = ContentDialogButton.Primary
};
dialog.PrimaryButtonClick += (sender, args) =>
{
foreach (ParameterEditorRow row in rows)
{
if (row.UseDefault.IsChecked == true)
continue;
if (!float.TryParse(row.Value.Text, NumberStyles.Float, CultureInfo.CurrentCulture, out float _)
&& !float.TryParse(row.Value.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out float _))
{
row.Value.Header = "Invalid value";
args.Cancel = true;
return;
}
}
foreach (ParameterEditorRow row in rows)
{
if (row.UseDefault.IsChecked == true)
row.Parameter.IsDefault = true;
else if (float.TryParse(row.Value.Text, NumberStyles.Float, CultureInfo.CurrentCulture, out float currentCultureValue)
|| float.TryParse(row.Value.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out currentCultureValue))
row.Parameter.Value = currentCultureValue;
}
};
await dialog.ShowAsync();
}
private static ParameterEditorRow CreateParameterEditorRow(IParameter parameter)
{
Grid grid = new()
{
ColumnDefinitions =
{
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) },
new ColumnDefinition { Width = GridLength.Auto },
new ColumnDefinition { Width = new GridLength(120) }
},
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto }
},
ColumnSpacing = 8
};
TextBlock name = new()
{
Text = parameter.Name,
VerticalAlignment = VerticalAlignment.Center,
TextWrapping = TextWrapping.Wrap
};
grid.Children.Add(name);
CheckBox useDefault = new()
{
Content = "Default",
IsChecked = parameter.IsDefault,
VerticalAlignment = VerticalAlignment.Center
};
Grid.SetColumn(useDefault, 1);
grid.Children.Add(useDefault);
TextBox value = new()
{
Text = parameter.Value.ToString(CultureInfo.CurrentCulture),
IsEnabled = !parameter.IsDefault
};
Grid.SetColumn(value, 2);
grid.Children.Add(value);
TextBlock description = new()
{
Text = parameter.Description,
Opacity = 0.72,
TextWrapping = TextWrapping.Wrap
};
Grid.SetRow(description, 1);
Grid.SetColumnSpan(description, 3);
grid.Children.Add(description);
useDefault.Checked += (_, _) => value.IsEnabled = false;
useDefault.Unchecked += (_, _) => value.IsEnabled = true;
return new ParameterEditorRow(parameter, grid, useDefault, value);
}
private async Task ShowWebServerSettingsAsync()
{
ComboBox interfaces = new() { MinWidth = 260 };
foreach (string address in GetLocalIPv4Addresses())
interfaces.Items.Add(address);
interfaces.Items.Add("0.0.0.0");
interfaces.SelectedItem = interfaces.Items.Contains(ViewModel.ListenerIp) ? ViewModel.ListenerIp : "0.0.0.0";
TextBox port = new()
{
Header = "Port",
Text = ViewModel.ListenerPort.ToString(CultureInfo.InvariantCulture),
InputScope = new InputScope { Names = { new InputScopeName(InputScopeNameValue.Number) } }
};
HyperlinkButton link = new()
{
Content = ViewModel.WebServerUrl,
NavigateUri = new Uri(ViewModel.WebServerUrl)
};
StackPanel content = new() { Spacing = 12 };
content.Children.Add(new TextBlock { Text = "Interface", FontWeight = new global::Windows.UI.Text.FontWeight { Weight = 600 } });
content.Children.Add(interfaces);
content.Children.Add(port);
content.Children.Add(link);
ContentDialog dialog = new()
{
XamlRoot = Content.XamlRoot,
Title = "Remote Web Server",
Content = content,
PrimaryButtonText = "Save",
CloseButtonText = "Cancel",
DefaultButton = ContentDialogButton.Primary
};
dialog.PrimaryButtonClick += (_, args) =>
{
if (!int.TryParse(port.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out int parsedPort) || parsedPort < 1 || parsedPort > 65535)
{
port.Header = "Port must be 1-65535";
args.Cancel = true;
return;
}
ViewModel.ListenerIp = interfaces.SelectedItem as string ?? "0.0.0.0";
ViewModel.ListenerPort = parsedPort;
};
await dialog.ShowAsync();
}
private async Task ShowWebServerAuthenticationAsync()
{
CheckBox enabled = new() { Content = "Enable HTTP authentication", IsChecked = ViewModel.AuthWebServer };
TextBox userName = new()
{
Header = "Username",
Text = ViewModel.AuthWebServerUserName,
IsEnabled = enabled.IsChecked == true
};
PasswordBox password = new()
{
Header = "New password",
IsEnabled = enabled.IsChecked == true
};
enabled.Checked += (_, _) => userName.IsEnabled = password.IsEnabled = true;
enabled.Unchecked += (_, _) => userName.IsEnabled = password.IsEnabled = false;
StackPanel content = new() { Spacing = 12 };
content.Children.Add(enabled);
content.Children.Add(userName);
content.Children.Add(password);
ContentDialog dialog = new()
{
XamlRoot = Content.XamlRoot,
Title = "Remote Web Server Authentication",
Content = content,
PrimaryButtonText = "Save",
CloseButtonText = "Cancel",
DefaultButton = ContentDialogButton.Primary
};
dialog.PrimaryButtonClick += (_, _) =>
{
ViewModel.AuthWebServer = enabled.IsChecked == true;
ViewModel.AuthWebServerUserName = userName.Text;
ViewModel.SetAuthPassword(password.Password);
};
await dialog.ShowAsync();
}
private void SensorTree_SelectionChanged(TreeView sender, TreeViewSelectionChangedEventArgs args) private void SensorTree_SelectionChanged(TreeView sender, TreeViewSelectionChangedEventArgs args)
{ {
if (args.AddedItems.Count == 0) if (args.AddedItems.Count == 0)
@@ -1486,65 +1226,6 @@ public sealed class MainWindow : Window
ViewModel.SelectedItem = element.DataContext as SensorTreeItemViewModel; ViewModel.SelectedItem = element.DataContext as SensorTreeItemViewModel;
} }
private async Task SaveReportAsync()
{
FileSavePicker picker = new()
{
SuggestedFileName = "LibreHardwareMonitor.Report",
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
};
picker.FileTypeChoices.Add("Text", [".txt"]);
InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(this));
StorageFile? file = await picker.PickSaveFileAsync();
if (file == null)
return;
CachedFileManager.DeferUpdates(file);
await FileIO.WriteTextAsync(file, ViewModel.GetReport());
await CachedFileManager.CompleteUpdatesAsync(file);
}
private async Task ShowAboutAsync()
{
StackPanel content = new() { Spacing = 8 };
content.Children.Add(new TextBlock
{
Text = "Libre Hardware Monitor",
FontWeight = new global::Windows.UI.Text.FontWeight { Weight = 600 },
FontSize = 18
});
content.Children.Add(new TextBlock
{
Text = $"Version {typeof(MainWindow).Assembly.GetName().Version}",
TextWrapping = TextWrapping.Wrap
});
content.Children.Add(new TextBlock
{
Text = "A free, open source hardware monitoring application.",
TextWrapping = TextWrapping.Wrap
});
content.Children.Add(new HyperlinkButton
{
Content = "Project website",
NavigateUri = new Uri("https://github.com/LibreHardwareMonitor/LibreHardwareMonitor")
});
content.Children.Add(new HyperlinkButton
{
Content = "Mozilla Public License 2.0",
NavigateUri = new Uri("https://www.mozilla.org/MPL/2.0/")
});
ContentDialog dialog = new()
{
XamlRoot = Content.XamlRoot,
Title = "Libre Hardware Monitor",
Content = content,
CloseButtonText = "OK"
};
await dialog.ShowAsync();
}
private void ResetPlot() private void ResetPlot()
{ {
ViewModel.ResetPlot(); ViewModel.ResetPlot();
@@ -2286,23 +1967,6 @@ public sealed class MainWindow : Window
return item; return item;
} }
private static string[] GetLocalIPv4Addresses()
{
try
{
return Dns.GetHostEntry(Dns.GetHostName())
.AddressList
.Where(address => address.AddressFamily == AddressFamily.InterNetwork)
.Select(address => address.ToString())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
}
catch
{
return [];
}
}
private const int ShowWindowHide = 0; private const int ShowWindowHide = 0;
private const int ShowWindowShow = 5; private const int ShowWindowShow = 5;
private const int ShowWindowMinimize = 6; private const int ShowWindowMinimize = 6;
@@ -2337,8 +2001,6 @@ public sealed class MainWindow : Window
private sealed record PlotSeriesSample(PlotSeriesViewModel Series, IReadOnlyList<PlotPointViewModel> Points); private sealed record PlotSeriesSample(PlotSeriesViewModel Series, IReadOnlyList<PlotPointViewModel> Points);
private sealed record ParameterEditorRow(IParameter Parameter, Grid Container, CheckBox UseDefault, TextBox Value);
private static void Bind(DependencyObject target, DependencyProperty property, object source, string path, BindingMode mode = BindingMode.OneWay) private static void Bind(DependencyObject target, DependencyProperty property, object source, string path, BindingMode mode = BindingMode.OneWay)
{ {
BindingOperations.SetBinding(target, property, new Binding BindingOperations.SetBinding(target, property, new Binding