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:
Yu Leng
2025-12-09 15:28:18 +08:00
parent 25db00ec45
commit a0f45c444f
5 changed files with 28 additions and 210 deletions

View File

@@ -10,8 +10,6 @@ using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Dispatching; using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.Windows.AppLifecycle; using Microsoft.Windows.AppLifecycle;
using PowerDisplay.Helpers; using PowerDisplay.Helpers;
using PowerDisplay.Serialization; using PowerDisplay.Serialization;
@@ -75,11 +73,7 @@ namespace PowerDisplay
/// </summary> /// </summary>
private void OnUnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) private void OnUnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{ {
// Try to display error information Logger.LogError("Unhandled exception", e.Exception);
ShowStartupError(e.Exception);
// Mark exception as handled to prevent app crash
e.Handled = true;
} }
/// <summary> /// <summary>
@@ -196,7 +190,7 @@ namespace PowerDisplay
} }
catch (Exception ex) catch (Exception ex)
{ {
ShowStartupError(ex); Logger.LogError("PowerDisplay startup failed", ex);
} }
} }
@@ -260,66 +254,6 @@ namespace PowerDisplay
CancellationToken.None); 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> /// <summary>
/// Gets the main window instance /// Gets the main window instance
/// </summary> /// </summary>

View File

@@ -1,11 +1,11 @@
<Window <Window
x:Class="PowerDisplay.PowerDisplayXAML.IdentifyWindow" x:Class="PowerDisplay.PowerDisplayXAML.IdentifyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid Background="Transparent"> <Grid Background="#1A000000">
<TextBlock <TextBlock
x:Name="NumberText" x:Name="NumberText"
HorizontalAlignment="Center" HorizontalAlignment="Center"

View File

@@ -4,9 +4,9 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.UI; using Microsoft.UI;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Windowing; using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using PowerDisplay.Helpers; using PowerDisplay.Helpers;
@@ -18,12 +18,17 @@ namespace PowerDisplay.PowerDisplayXAML
/// <summary> /// <summary>
/// Interaction logic for IdentifyWindow.xaml /// Interaction logic for IdentifyWindow.xaml
/// </summary> /// </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 AppWindow? _appWindow;
private DesktopAcrylicController? _acrylicController; private double _dpiScale = 1.0;
private SystemBackdropConfiguration? _configurationSource;
private bool _disposed; [LibraryImport("user32.dll")]
private static partial uint GetDpiForWindow(IntPtr hwnd);
public IdentifyWindow(int number) public IdentifyWindow(int number)
{ {
@@ -49,6 +54,9 @@ namespace PowerDisplay.PowerDisplayXAML
var windowId = Win32Interop.GetWindowIdFromWindow(hwnd); var windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
_appWindow = AppWindow.GetFromWindowId(windowId); _appWindow = AppWindow.GetFromWindowId(windowId);
// Get DPI scale for this window
_dpiScale = GetDpiForWindow(hwnd) / 96.0;
if (_appWindow != null) if (_appWindow != null)
{ {
// Remove title bar using OverlappedPresenter // Remove title bar using OverlappedPresenter
@@ -61,38 +69,17 @@ namespace PowerDisplay.PowerDisplayXAML
presenter.IsMaximizable = false; presenter.IsMaximizable = false;
} }
// Set window size to fit the large number (200pt font) // Set window size scaled for DPI
_appWindow.Resize(new SizeInt32 { Width = 300, Height = 280 }); // 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 // Set window topmost and hide from taskbar
WindowHelper.SetWindowTopmost(hwnd, true); WindowHelper.SetWindowTopmost(hwnd, true);
WindowHelper.HideFromTaskbar(hwnd); WindowHelper.HideFromTaskbar(hwnd);
WindowHelper.DisableWindowMovingAndResizing(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> /// <summary>
@@ -106,26 +93,16 @@ namespace PowerDisplay.PowerDisplayXAML
} }
var workArea = displayArea.WorkArea; 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) // Calculate center position (WorkArea coordinates are in physical pixels)
int x = workArea.X + ((workArea.Width - windowWidth) / 2); int x = workArea.X + ((workArea.Width - physicalWidth) / 2);
int y = workArea.Y + ((workArea.Height - windowHeight) / 2); int y = workArea.Y + ((workArea.Height - physicalHeight) / 2);
_appWindow.Move(new PointInt32(x, y)); _appWindow.Move(new PointInt32(x, y));
} }
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_acrylicController?.Dispose();
_acrylicController = null;
}
} }
} }

View File

@@ -193,7 +193,6 @@
Minimum="{x:Bind MinBrightness, Mode=OneWay}" Minimum="{x:Bind MinBrightness, Mode=OneWay}"
PointerCaptureLost="Slider_PointerCaptureLost" PointerCaptureLost="Slider_PointerCaptureLost"
Tag="Brightness" Tag="Brightness"
ValueChanged="Slider_ValueChanged"
Value="{x:Bind Brightness, Mode=OneWay}" /> Value="{x:Bind Brightness, Mode=OneWay}" />
</Grid> </Grid>
@@ -226,7 +225,6 @@
Minimum="0" Minimum="0"
PointerCaptureLost="Slider_PointerCaptureLost" PointerCaptureLost="Slider_PointerCaptureLost"
Tag="Contrast" Tag="Contrast"
ValueChanged="Slider_ValueChanged"
Value="{x:Bind ContrastPercent, Mode=OneWay}" /> Value="{x:Bind ContrastPercent, Mode=OneWay}" />
</Grid> </Grid>
@@ -260,7 +258,6 @@
Minimum="{x:Bind MinVolume, Mode=OneWay}" Minimum="{x:Bind MinVolume, Mode=OneWay}"
PointerCaptureLost="Slider_PointerCaptureLost" PointerCaptureLost="Slider_PointerCaptureLost"
Tag="Volume" Tag="Volume"
ValueChanged="Slider_ValueChanged"
Value="{x:Bind Volume, Mode=OneWay}" /> Value="{x:Bind Volume, Mode=OneWay}" />
</Grid> </Grid>

View File

@@ -9,12 +9,9 @@ using System.Threading.Tasks;
using ManagedCommon; using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI; using Microsoft.UI;
using Microsoft.UI.Composition;
using Microsoft.UI.Windowing; using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Animation;
using PowerDisplay.Common.Models; using PowerDisplay.Common.Models;
using PowerDisplay.Configuration; using PowerDisplay.Configuration;
using PowerDisplay.Helpers; 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> /// <summary>
/// Slider PointerCaptureLost event handler - updates ViewModel when drag completes /// Slider PointerCaptureLost event handler - updates ViewModel when drag completes
/// This is the WinUI3 recommended way to detect drag completion /// This is the WinUI3 recommended way to detect drag completion
@@ -663,75 +649,6 @@ namespace PowerDisplay
Logger.LogDebug($"[UI] ViewModel property {propertyName} updated successfully"); 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> /// <summary>
/// Input source ListView selection changed handler - switches the monitor input source /// Input source ListView selection changed handler - switches the monitor input source
/// </summary> /// </summary>
@@ -766,13 +683,6 @@ namespace PowerDisplay
// Set the input source // Set the input source
await monitorVm.SetInputSourceAsync(selectedItem.Value); await monitorVm.SetInputSourceAsync(selectedItem.Value);
// Close the flyout after selection
if (listView.Parent is StackPanel stackPanel &&
stackPanel.Parent is Flyout flyout)
{
flyout.Hide();
}
} }
/// <summary> /// <summary>