Files
PowerToys/src/modules/MeasureTool/MeasureToolUI/MeasureToolXAML/MainWindow.xaml.cs
Shawn Yuan ab47d54463 Fix WinuiEx crash issue (#45443)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Fixes a crash related to `IsShownInSwitchers` when explorer.exe is not
running. The property has been removed from XAML and is now set in the
C# backend with added exception handling to improve stability. No
changes were made for projects where the property is set to true, as
they are not affected.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: vanzue <vanzue@outlook.com>
2026-02-06 16:58:29 +08:00

222 lines
7.5 KiB
C#

// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Automation.Provider;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Settings.UI.Library.Enumerations;
using Windows.Graphics;
using WinUIEx;
using static NativeMethods;
namespace MeasureToolUI
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : WindowEx, IDisposable
{
private const int WindowWidth = 216;
private const int WindowHeight = 50;
private readonly Settings settings = new();
private PowerToys.MeasureToolCore.Core _coreLogic;
private AppWindow _appWindow;
private PointInt32 _initialPosition;
protected override void OnPositionChanged(PointInt32 position)
{
_appWindow.Move(_initialPosition);
this.SetWindowSize(WindowWidth, WindowHeight);
}
public MainWindow(PowerToys.MeasureToolCore.Core core)
{
InitializeComponent();
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
WindowId windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
_appWindow = AppWindow.GetFromWindowId(windowId);
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
var presenter = _appWindow.Presenter as OverlappedPresenter;
presenter.IsAlwaysOnTop = true;
this.SetIsAlwaysOnTop(true);
this.SetIsResizable(false);
this.SetIsMinimizable(false);
this.SetIsMaximizable(false);
IsTitleBarVisible = false;
try
{
this.SetIsShownInSwitchers(false);
}
catch (NotImplementedException)
{
// WinUI will throw if explorer is not running, safely ignore
}
catch (Exception)
{
}
// Remove the caption style from the window style. Windows App SDK 1.6 added it, which made the title bar and borders appear for Measure Tool. This code removes it.
var windowStyle = GetWindowLong(hwnd, GWL_STYLE);
windowStyle = windowStyle & (~WS_CAPTION);
_ = SetWindowLong(hwnd, GWL_STYLE, windowStyle);
_coreLogic = core;
_coreLogic.InitResources();
Closed += MainWindow_Closed;
DisplayArea displayArea = DisplayArea.GetFromWindowId(windowId, DisplayAreaFallback.Nearest);
float dpiScale = _coreLogic.GetDPIScaleForWindow((int)hwnd);
_initialPosition = new PointInt32(displayArea.WorkArea.X + (displayArea.WorkArea.Width / 2) - (int)(dpiScale * WindowWidth / 2), displayArea.WorkArea.Y + (int)(dpiScale * 12));
_coreLogic.SetToolbarBoundingBox(
_initialPosition.X,
_initialPosition.Y,
_initialPosition.X + (int)(dpiScale * WindowWidth),
_initialPosition.Y + (int)(dpiScale * WindowHeight));
OnPositionChanged(_initialPosition);
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
private void StackPanel_Loaded(object sender, RoutedEventArgs e)
{
SelectDefaultMeasureStyle();
}
private void MainWindow_Closed(object sender, WindowEventArgs args)
{
_coreLogic?.Dispose();
_coreLogic = null;
}
private void UpdateToolUsageCompletionEvent(object sender)
{
_coreLogic.SetToolCompletionEvent(new PowerToys.MeasureToolCore.ToolSessionCompleted(() =>
{
DispatcherQueue.TryEnqueue(() =>
{
((ToggleButton)sender).IsChecked = false;
});
}));
}
private void UncheckOtherButtons(ToggleButton button)
{
var panel = button.Parent as Panel;
foreach (var elem in panel.Children)
{
if (elem is ToggleButton otherButton)
{
if (!button.Equals(otherButton))
{
otherButton.IsChecked = false;
}
}
}
}
private void HandleToolClick(object toolButton, Action startToolAction)
{
ToggleButton button = toolButton as ToggleButton;
if (button == null)
{
return;
}
if (button.IsChecked.GetValueOrDefault())
{
UncheckOtherButtons(button);
_coreLogic.ResetState();
UpdateToolUsageCompletionEvent(toolButton);
startToolAction();
}
else
{
_coreLogic.ResetState();
}
}
private void BoundsTool_Click(object sender, RoutedEventArgs e)
{
HandleToolClick(sender, () => _coreLogic.StartBoundsTool());
}
private void MeasureTool_Click(object sender, RoutedEventArgs e)
{
HandleToolClick(sender, () => _coreLogic.StartMeasureTool(true, true));
}
private void HorizontalMeasureTool_Click(object sender, RoutedEventArgs e)
{
HandleToolClick(sender, () => _coreLogic.StartMeasureTool(true, false));
}
private void VerticalMeasureTool_Click(object sender, RoutedEventArgs e)
{
HandleToolClick(sender, () => _coreLogic.StartMeasureTool(false, true));
}
private void ClosePanelTool_Click(object sender, RoutedEventArgs e)
{
_coreLogic.ResetState();
this.Close();
}
private void SelectDefaultMeasureStyle()
{
ToggleButton responsibleBtn = settings.DefaultMeasureStyle switch
{
MeasureToolMeasureStyle.None => null,
MeasureToolMeasureStyle.Bounds => btnBounds,
MeasureToolMeasureStyle.Spacing => btnSpacing,
MeasureToolMeasureStyle.HorizontalSpacing => btnHorizontalSpacing,
MeasureToolMeasureStyle.VerticalSpacing => btnVerticalSpacing,
_ => null,
};
if (responsibleBtn is not null)
{
var peer = FrameworkElementAutomationPeer.FromElement(responsibleBtn) as ToggleButtonAutomationPeer;
peer.Toggle();
}
}
public void Dispose()
{
_coreLogic?.Dispose();
}
private void KeyboardAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
{
if (args.Element is ToggleButton toggle)
{
var peer = new ToggleButtonAutomationPeer(toggle);
peer.Toggle();
args.Handled = true;
}
else if (args.Element is Button button)
{
var peer = new ButtonAutomationPeer(button);
if (peer.GetPattern(PatternInterface.Invoke) is IInvokeProvider provider)
{
provider.Invoke();
args.Handled = true;
}
}
}
}
}