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:
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user