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