[Settings] Implement singleton pattern for ShortcutConflictWindow (#42440)

## Summary
Fixes issue where multiple ShortcutConflictWindow instances could be
opened simultaneously. The window now follows the same singleton pattern
as OobeWindow - only one instance can exist at a time, and attempting to
open another brings the existing window to the foreground.

## Changes
Implemented singleton management for `ShortcutConflictWindow` following
the established pattern used by `OobeWindow`:

### App.xaml.cs
- Added static field to store the singleton window instance
- Added `GetShortcutConflictWindow()`, `SetShortcutConflictWindow()`,
and `ClearShortcutConflictWindow()` methods for lifecycle management

### ShortcutConflictWindow.xaml.cs
- Updated `WindowEx_Closed` event handler to call
`App.ClearShortcutConflictWindow()` to properly clean up the singleton
reference when the window is closed

### Updated all three entry points that create ShortcutConflictWindow:
- **ShortcutConflictControl.xaml.cs** (Dashboard conflict warning)
- **ShortcutControl.xaml.cs** (Settings page shortcut controls)
- **OobeOverview.xaml.cs** (OOBE overview page)

Each location now checks if a window already exists using
`App.GetShortcutConflictWindow()`:
- If no window exists, creates a new one and registers it via
`App.SetShortcutConflictWindow()`
- If a window already exists, simply calls `Activate()` to bring it to
the foreground

## Testing
The fix ensures that:
-  Only one ShortcutConflictWindow can be open at a time
-  Clicking the shortcut conflict button when a window is already open
activates the existing window instead of creating a duplicate
-  The window reference is properly cleared when closed, allowing a new
instance to be created in future interactions

Fixes #[issue_number]

> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `i1qvsblobprodcus353.vsblob.vsassets.io`
> - Triggering command: `dotnet build PowerToys.Settings.csproj
--configuration Release` (dns block)
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/microsoft/PowerToys/settings/copilot/coding_agent)
(admins only)
>
> </details>

<!-- START COPILOT CODING AGENT SUFFIX -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>[Settings] Single Shortcuts Conflicts
window</issue_title>
> <issue_description>### Microsoft PowerToys version
> 
> 0.95.0
> 
> ### Installation method
> 
> GitHub
> 
> ### Area(s) with issue?
> 
> Settings
> 
> ### Steps to reproduce
> 
> Multiple shortcut conflicts window can be launched.
> Should have the same behavior of OOBE window.
> If shortcut conflicts window is already opened, pressing the button
should bring the window in foreground.
> 
> ### ✔️ Expected Behavior
> 
> Single shortcuts conflicts window
> 
> ###  Actual Behavior
> 
> Multiple shortcut conflicts window can be launched
> 
> ### Additional Information
> 
> _No response_
> 
> ### Other Software
> 
> _No response_</issue_description>
> 
> <agent_instructions>Settings ShortcutConflictWindow should have the
same behavior of OobeWindow.
> When ShortcutConflictWindow is already opened, activate that window
instead of opening another one. </agent_instructions>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>

Fixes microsoft/PowerToys#42437

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 Share your feedback on Copilot coding agent for the chance to win a
$200 gift card! Click
[here](https://survey3.medallia.com/?EAHeSx-AP01bZqG0Ld9QLQ) to start
the survey.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: davidegiacometti <25966642+davidegiacometti@users.noreply.github.com>
Co-authored-by: Davide Giacometti <davide.giacometti@outlook.it>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: vanzue <vanzue@outlook.com>
This commit is contained in:
Copilot
2026-02-12 17:28:21 +08:00
committed by GitHub
parent 528fb524d0
commit 587385d879
7 changed files with 88 additions and 18 deletions

View File

@@ -55,6 +55,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
// Flag to prevent toggle operations during sorting to avoid race conditions.
private bool _isSorting;
private bool _isDisposed;
private AllHotkeyConflictsData _allHotkeyConflictsData = new AllHotkeyConflictsData();
@@ -132,8 +133,18 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private void OnSettingsChanged(GeneralSettings newSettings)
{
if (_isDisposed)
{
return;
}
dispatcher.TryEnqueue(() =>
{
if (_isDisposed)
{
return;
}
generalSettingsConfig = newSettings;
// Update local field and notify UI if sort order changed
@@ -149,8 +160,18 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
protected override void OnConflictsUpdated(object sender, AllHotkeyConflictsEventArgs e)
{
if (_isDisposed)
{
return;
}
dispatcher.TryEnqueue(() =>
{
if (_isDisposed)
{
return;
}
var allConflictData = e.Conflicts;
foreach (var inAppConflict in allConflictData.InAppConflicts)
{
@@ -363,6 +384,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
/// </summary>
public void ModuleEnabledChangedOnSettingsPage()
{
if (_isDisposed)
{
return;
}
// Ignore if this was triggered by a UI change that we're already handling.
if (_isUpdatingFromUI)
{
@@ -391,6 +417,17 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
/// </summary>
private void RefreshShortcutModules()
{
if (_isDisposed)
{
return;
}
if (!dispatcher.HasThreadAccess)
{
_ = dispatcher.TryEnqueue(DispatcherQueuePriority.Normal, RefreshShortcutModules);
return;
}
ShortcutModules.Clear();
ActionModules.Clear();
@@ -804,6 +841,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
public override void Dispose()
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
base.Dispose();
if (_settingsRepository != null)
{