mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
Refactor error handling, DPI scaling, and UI events
- Replace custom error UI with Logger.LogError for exceptions - Remove acrylic backdrop; use semi-transparent black background - Scale IdentifyWindow size for DPI using GetDpiForWindow - Remove ValueChanged handlers from sliders; use PointerCaptureLost - Delete unused event handlers and clean up using directives - Simplify input source switching logic in MainWindow
This commit is contained in:
@@ -10,8 +10,6 @@ using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using PowerDisplay.Helpers;
|
||||
using PowerDisplay.Serialization;
|
||||
@@ -75,11 +73,7 @@ namespace PowerDisplay
|
||||
/// </summary>
|
||||
private void OnUnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
// Try to display error information
|
||||
ShowStartupError(e.Exception);
|
||||
|
||||
// Mark exception as handled to prevent app crash
|
||||
e.Handled = true;
|
||||
Logger.LogError("Unhandled exception", e.Exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -196,7 +190,7 @@ namespace PowerDisplay
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowStartupError(ex);
|
||||
Logger.LogError("PowerDisplay startup failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,66 +254,6 @@ namespace PowerDisplay
|
||||
CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show startup error
|
||||
/// </summary>
|
||||
private void ShowStartupError(Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogError($"PowerDisplay startup failed: {ex.Message}");
|
||||
|
||||
var errorWindow = new Window { Title = "PowerDisplay - Startup Error" };
|
||||
var panel = new StackPanel { Margin = new Thickness(20), Spacing = 16 };
|
||||
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "PowerDisplay Startup Failed",
|
||||
FontSize = 20,
|
||||
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
|
||||
});
|
||||
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"Error: {ex.Message}",
|
||||
FontSize = 14,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
});
|
||||
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"Details:\n{ex}",
|
||||
FontSize = 12,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Foreground = new SolidColorBrush(Microsoft.UI.Colors.Gray),
|
||||
Margin = new Thickness(0, 10, 0, 0),
|
||||
});
|
||||
|
||||
var closeButton = new Button
|
||||
{
|
||||
Content = "Close",
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Margin = new Thickness(0, 10, 0, 0),
|
||||
};
|
||||
closeButton.Click += (_, _) => errorWindow.Close();
|
||||
panel.Children.Add(closeButton);
|
||||
|
||||
errorWindow.Content = new ScrollViewer
|
||||
{
|
||||
Content = panel,
|
||||
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
|
||||
MaxHeight = 600,
|
||||
MaxWidth = 800,
|
||||
};
|
||||
|
||||
errorWindow.Activate();
|
||||
}
|
||||
catch
|
||||
{
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the main window instance
|
||||
/// </summary>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<Window
|
||||
<Window
|
||||
x:Class="PowerDisplay.PowerDisplayXAML.IdentifyWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
<Grid Background="Transparent">
|
||||
<Grid Background="#1A000000">
|
||||
<TextBlock
|
||||
x:Name="NumberText"
|
||||
HorizontalAlignment="Center"
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Composition.SystemBackdrops;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using PowerDisplay.Helpers;
|
||||
@@ -18,12 +18,17 @@ namespace PowerDisplay.PowerDisplayXAML
|
||||
/// <summary>
|
||||
/// Interaction logic for IdentifyWindow.xaml
|
||||
/// </summary>
|
||||
public sealed partial class IdentifyWindow : Window, IDisposable
|
||||
public sealed partial class IdentifyWindow : Window
|
||||
{
|
||||
// Window size in device-independent units (DIU)
|
||||
private const int WindowWidthDiu = 300;
|
||||
private const int WindowHeightDiu = 280;
|
||||
|
||||
private AppWindow? _appWindow;
|
||||
private DesktopAcrylicController? _acrylicController;
|
||||
private SystemBackdropConfiguration? _configurationSource;
|
||||
private bool _disposed;
|
||||
private double _dpiScale = 1.0;
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
private static partial uint GetDpiForWindow(IntPtr hwnd);
|
||||
|
||||
public IdentifyWindow(int number)
|
||||
{
|
||||
@@ -49,6 +54,9 @@ namespace PowerDisplay.PowerDisplayXAML
|
||||
var windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
|
||||
_appWindow = AppWindow.GetFromWindowId(windowId);
|
||||
|
||||
// Get DPI scale for this window
|
||||
_dpiScale = GetDpiForWindow(hwnd) / 96.0;
|
||||
|
||||
if (_appWindow != null)
|
||||
{
|
||||
// Remove title bar using OverlappedPresenter
|
||||
@@ -61,38 +69,17 @@ namespace PowerDisplay.PowerDisplayXAML
|
||||
presenter.IsMaximizable = false;
|
||||
}
|
||||
|
||||
// Set window size to fit the large number (200pt font)
|
||||
_appWindow.Resize(new SizeInt32 { Width = 300, Height = 280 });
|
||||
// Set window size scaled for DPI
|
||||
// AppWindow.Resize expects physical pixels
|
||||
int physicalWidth = (int)(WindowWidthDiu * _dpiScale);
|
||||
int physicalHeight = (int)(WindowHeightDiu * _dpiScale);
|
||||
_appWindow.Resize(new SizeInt32 { Width = physicalWidth, Height = physicalHeight });
|
||||
}
|
||||
|
||||
// Set window topmost and hide from taskbar
|
||||
WindowHelper.SetWindowTopmost(hwnd, true);
|
||||
WindowHelper.HideFromTaskbar(hwnd);
|
||||
WindowHelper.DisableWindowMovingAndResizing(hwnd);
|
||||
|
||||
// Configure 90% transparent acrylic backdrop
|
||||
ConfigureAcrylicBackdrop();
|
||||
}
|
||||
|
||||
private void ConfigureAcrylicBackdrop()
|
||||
{
|
||||
if (!DesktopAcrylicController.IsSupported())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_configurationSource = new SystemBackdropConfiguration();
|
||||
_acrylicController = new DesktopAcrylicController();
|
||||
|
||||
// Set 90% transparency (TintOpacity 0.1 = 10% tint = 90% transparent)
|
||||
_acrylicController.TintColor = Windows.UI.Color.FromArgb(255, 0, 0, 0);
|
||||
_acrylicController.TintOpacity = 0.1f;
|
||||
_acrylicController.LuminosityOpacity = 0f;
|
||||
|
||||
// Add target using WinRT cast
|
||||
var target = WinRT.CastExtensions.As<Microsoft.UI.Composition.ICompositionSupportsSystemBackdrop>(this);
|
||||
_acrylicController.AddSystemBackdropTarget(target);
|
||||
_acrylicController.SetSystemBackdropConfiguration(_configurationSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,26 +93,16 @@ namespace PowerDisplay.PowerDisplayXAML
|
||||
}
|
||||
|
||||
var workArea = displayArea.WorkArea;
|
||||
int windowWidth = 300;
|
||||
int windowHeight = 280;
|
||||
|
||||
// Window size in physical pixels (already scaled for DPI)
|
||||
int physicalWidth = (int)(WindowWidthDiu * _dpiScale);
|
||||
int physicalHeight = (int)(WindowHeightDiu * _dpiScale);
|
||||
|
||||
// Calculate center position (WorkArea coordinates are in physical pixels)
|
||||
int x = workArea.X + ((workArea.Width - windowWidth) / 2);
|
||||
int y = workArea.Y + ((workArea.Height - windowHeight) / 2);
|
||||
int x = workArea.X + ((workArea.Width - physicalWidth) / 2);
|
||||
int y = workArea.Y + ((workArea.Height - physicalHeight) / 2);
|
||||
|
||||
_appWindow.Move(new PointInt32(x, y));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
_acrylicController?.Dispose();
|
||||
_acrylicController = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,6 @@
|
||||
Minimum="{x:Bind MinBrightness, Mode=OneWay}"
|
||||
PointerCaptureLost="Slider_PointerCaptureLost"
|
||||
Tag="Brightness"
|
||||
ValueChanged="Slider_ValueChanged"
|
||||
Value="{x:Bind Brightness, Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
@@ -226,7 +225,6 @@
|
||||
Minimum="0"
|
||||
PointerCaptureLost="Slider_PointerCaptureLost"
|
||||
Tag="Contrast"
|
||||
ValueChanged="Slider_ValueChanged"
|
||||
Value="{x:Bind ContrastPercent, Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
@@ -260,7 +258,6 @@
|
||||
Minimum="{x:Bind MinVolume, Mode=OneWay}"
|
||||
PointerCaptureLost="Slider_PointerCaptureLost"
|
||||
Tag="Volume"
|
||||
ValueChanged="Slider_ValueChanged"
|
||||
Value="{x:Bind Volume, Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -9,12 +9,9 @@ using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using PowerDisplay.Common.Models;
|
||||
using PowerDisplay.Configuration;
|
||||
using PowerDisplay.Helpers;
|
||||
@@ -602,17 +599,6 @@ namespace PowerDisplay
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Slider ValueChanged event handler - does nothing during drag
|
||||
/// This allows the slider UI to update smoothly without triggering hardware operations
|
||||
/// </summary>
|
||||
private void Slider_ValueChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
|
||||
{
|
||||
// During drag, this event fires 60-120 times per second
|
||||
// We intentionally do nothing here to keep UI smooth
|
||||
// The actual ViewModel update happens in PointerCaptureLost after drag completes
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Slider PointerCaptureLost event handler - updates ViewModel when drag completes
|
||||
/// This is the WinUI3 recommended way to detect drag completion
|
||||
@@ -663,75 +649,6 @@ namespace PowerDisplay
|
||||
Logger.LogDebug($"[UI] ViewModel property {propertyName} updated successfully");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Input source item click handler - switches the monitor input source
|
||||
/// </summary>
|
||||
private async void InputSourceItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Logger.LogInfo("[UI] InputSourceItem_Click: Event triggered!");
|
||||
|
||||
if (sender is not Button button)
|
||||
{
|
||||
Logger.LogWarning("[UI] InputSourceItem_Click: sender is not Button");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"[UI] InputSourceItem_Click: Button clicked, Tag type = {button.Tag?.GetType().Name ?? "null"}, DataContext type = {button.DataContext?.GetType().Name ?? "null"}");
|
||||
|
||||
// Get the InputSourceItem from Tag (not DataContext - Flyout doesn't inherit DataContext properly)
|
||||
var inputSourceItem = button.Tag as InputSourceItem;
|
||||
if (inputSourceItem == null)
|
||||
{
|
||||
Logger.LogWarning("[UI] InputSourceItem_Click: Tag is not InputSourceItem");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"[UI] InputSourceItem_Click: InputSourceItem found - Value=0x{inputSourceItem.Value:X2}, Name={inputSourceItem.Name}, MonitorId={inputSourceItem.MonitorId}");
|
||||
|
||||
int inputSourceValue = inputSourceItem.Value;
|
||||
string monitorId = inputSourceItem.MonitorId;
|
||||
|
||||
// Use MonitorId for direct lookup (Flyout popup is not in visual tree)
|
||||
MonitorViewModel? monitorVm = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(monitorId) && _viewModel != null)
|
||||
{
|
||||
monitorVm = _viewModel.Monitors.FirstOrDefault(m => m.Id == monitorId);
|
||||
Logger.LogInfo($"[UI] InputSourceItem_Click: Found MonitorViewModel by ID: {monitorVm?.Name ?? "null"}");
|
||||
}
|
||||
|
||||
// Fallback: search through all monitors (for backwards compatibility)
|
||||
if (monitorVm == null && _viewModel != null)
|
||||
{
|
||||
Logger.LogInfo("[UI] InputSourceItem_Click: MonitorId lookup failed, trying fallback search");
|
||||
foreach (var vm in _viewModel.Monitors)
|
||||
{
|
||||
if (vm.SupportsInputSource && vm.AvailableInputSources != null)
|
||||
{
|
||||
if (vm.AvailableInputSources.Any(s => s.Value == inputSourceValue))
|
||||
{
|
||||
monitorVm = vm;
|
||||
Logger.LogInfo($"[UI] InputSourceItem_Click: Found MonitorViewModel by fallback: {vm.Name}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (monitorVm == null)
|
||||
{
|
||||
Logger.LogWarning("[UI] InputSourceItem_Click: Could not find MonitorViewModel");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"[UI] Switching input source for {monitorVm.Name} to 0x{inputSourceValue:X2} ({inputSourceItem.Name})");
|
||||
|
||||
// Set the input source
|
||||
await monitorVm.SetInputSourceAsync(inputSourceValue);
|
||||
|
||||
Logger.LogInfo("[UI] InputSourceItem_Click: SetInputSourceAsync completed");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Input source ListView selection changed handler - switches the monitor input source
|
||||
/// </summary>
|
||||
@@ -766,13 +683,6 @@ namespace PowerDisplay
|
||||
|
||||
// Set the input source
|
||||
await monitorVm.SetInputSourceAsync(selectedItem.Value);
|
||||
|
||||
// Close the flyout after selection
|
||||
if (listView.Parent is StackPanel stackPanel &&
|
||||
stackPanel.Parent is Flyout flyout)
|
||||
{
|
||||
flyout.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user