[Feature] PowerToys hotkey conflict detection (#41029)

<!-- 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
Implements comprehensive hotkey conflict detection and resolution system
for PowerToys, providing real-time conflict checking and centralized
management interface.

## PR Checklist

- [ ] **Closes:** #xxx
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **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)
- [x] **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: [Shortcut conflict detction dev
spec](https://github.com/MicrosoftDocs/windows-dev-docs/pull/5519)

## TODO Lists
- [x] Add real-time hotkey validation functionality to the hotkey dialog
- [x] Immediately detect conflicts and update shortcut conflict status
after applying new shortcuts
- [x] Return conflict list from runner hotkey conflict detector for
conflict checking.
- [x] Implement the Tooltip for every shortcut control 
- [x] Add dialog UI for showing all the shortcut conflicts
- [x] Support changing shortcut directly inside the shortcut conflict
window/dialog, no need to nav to the settings page.
- [x] Redesign the `ShortcutConflictDialogContentControl` to align with
the spec
- [x] Add navigating and changing hotkey auctionability to the
`ShortcutConflictDialogContentControl`
- [x] Add telemetry. Impemented in [another
PR](https://github.com/shuaiyuanxx/PowerToys/pull/47)

## Shortcut Conflict Support Modules

![image](https://github.com/user-attachments/assets/3915174e-d1e7-4f86-8835-2a1bafcc85c9)

<details>
<summary>Demo videos</summary>


https://github.com/user-attachments/assets/476d992c-c6ca-4bcd-a3f2-b26cc612d1b9


https://github.com/user-attachments/assets/1c1a2537-de54-4db2-bdbf-6f1908ff1ce7


https://github.com/user-attachments/assets/9c992254-fc2b-402c-beec-20fceef25e6b


https://github.com/user-attachments/assets/d66abc1c-b8bf-45f8-a552-ec989dab310f
</details>

<!-- 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
Manually validation performed.

---------

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
This commit is contained in:
Shawn Yuan
2025-08-20 09:31:52 +08:00
committed by GitHub
parent ce4d8dc11e
commit 75526b9580
104 changed files with 4578 additions and 366 deletions

View File

@@ -232,6 +232,12 @@ namespace Microsoft.PowerToys.Settings.UI
});
ipcmanager.Start();
GlobalHotkeyConflictManager.Initialize(message =>
{
ipcmanager.Send(message);
return 0;
});
if (!ShowOobe && !ShowScoobe && !ShowFlyout)
{
settingsWindow = new MainWindow();
@@ -320,10 +326,18 @@ namespace Microsoft.PowerToys.Settings.UI
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow));
settingsWindow.Activate();
settingsWindow.NavigateToSection(StartupPage);
// In DEBUG mode, we might not have IPC set up, so provide a dummy implementation
GlobalHotkeyConflictManager.Initialize(message =>
{
// In debug mode, just log or do nothing
System.Diagnostics.Debug.WriteLine($"IPC Message: {message}");
return 0;
});
#else
/* If we try to run Settings as a standalone app, it will start PowerToys.exe if not running and open Settings again through it in the Dashboard page. */
Common.UI.SettingsDeepLink.OpenSettings(Common.UI.SettingsDeepLink.SettingsWindow.Dashboard, true);
Exit();
/* If we try to run Settings as a standalone app, it will start PowerToys.exe if not running and open Settings again through it in the Dashboard page. */
Common.UI.SettingsDeepLink.OpenSettings(Common.UI.SettingsDeepLink.SettingsWindow.Dashboard, true);
Exit();
#endif
}
}

View File

@@ -8,7 +8,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid Visibility="{x:Bind HasConflicts, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<Button Click="ShortcutConflictBtn_Click" Style="{StaticResource SubtleButtonStyle}">
<Grid ColumnSpacing="16">
<Grid.ColumnDefinitions>
@@ -21,13 +21,13 @@
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
Glyph="&#xE814;" />
<StackPanel Grid.Column="1" Orientation="Vertical">
<TextBlock FontWeight="SemiBold" Text="Shortcut conflicts" />
<TextBlock x:Uid="ShortcutConflictControl_Title" FontWeight="SemiBold" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="2 conflicts found" />
Text="{x:Bind ConflictText, Mode=OneWay}" />
</StackPanel>
</Grid>
</Button>
</Grid>
</UserControl>
</UserControl>

View File

@@ -4,37 +4,122 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
using Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Microsoft.Windows.ApplicationModel.Resources;
namespace Microsoft.PowerToys.Settings.UI.Controls
{
public sealed partial class ShortcutConflictControl : UserControl
public sealed partial class ShortcutConflictControl : UserControl, INotifyPropertyChanged
{
private static readonly ResourceLoader ResourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
public static readonly DependencyProperty AllHotkeyConflictsDataProperty =
DependencyProperty.Register(
nameof(AllHotkeyConflictsData),
typeof(AllHotkeyConflictsData),
typeof(ShortcutConflictControl),
new PropertyMetadata(null, OnAllHotkeyConflictsDataChanged));
public AllHotkeyConflictsData AllHotkeyConflictsData
{
get => (AllHotkeyConflictsData)GetValue(AllHotkeyConflictsDataProperty);
set => SetValue(AllHotkeyConflictsDataProperty, value);
}
public int ConflictCount
{
get
{
if (AllHotkeyConflictsData == null)
{
return 0;
}
int count = 0;
if (AllHotkeyConflictsData.InAppConflicts != null)
{
count += AllHotkeyConflictsData.InAppConflicts.Count;
}
if (AllHotkeyConflictsData.SystemConflicts != null)
{
count += AllHotkeyConflictsData.SystemConflicts.Count;
}
return count;
}
}
public string ConflictText
{
get
{
var count = ConflictCount;
return count switch
{
0 => ResourceLoader.GetString("ShortcutConflictControl_NoConflictsFound"),
1 => ResourceLoader.GetString("ShortcutConflictControl_SingleConflictFound"),
_ => string.Format(
System.Globalization.CultureInfo.CurrentCulture,
ResourceLoader.GetString("ShortcutConflictControl_MultipleConflictsFound"),
count),
};
}
}
public bool HasConflicts => ConflictCount > 0;
private static void OnAllHotkeyConflictsDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ShortcutConflictControl control)
{
control.UpdateProperties();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void UpdateProperties()
{
OnPropertyChanged(nameof(ConflictCount));
OnPropertyChanged(nameof(ConflictText));
OnPropertyChanged(nameof(HasConflicts));
// Update visibility based on conflict count
Visibility = HasConflicts ? Visibility.Visible : Visibility.Collapsed;
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ShortcutConflictControl()
{
InitializeComponent();
GetShortcutConflicts();
}
DataContext = this;
private void GetShortcutConflicts()
{
// TO DO: Implement the logic to retrieve and display shortcut conflicts. Make sure to Collapse this control if not conflicts are found.
// Initially hide the control if no conflicts
Visibility = HasConflicts ? Visibility.Visible : Visibility.Collapsed;
}
private void ShortcutConflictBtn_Click(object sender, RoutedEventArgs e)
{
// TO DO: Handle the button click event to show the shortcut conflicts window.
if (AllHotkeyConflictsData == null || !HasConflicts)
{
return;
}
// Create and show the new window instead of dialog
var conflictWindow = new ShortcutConflictWindow();
// Show the window
conflictWindow.Activate();
}
}
}

View File

@@ -0,0 +1,176 @@
<winuiex:WindowEx
x:Class="Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard.ShortcutConflictWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:converters="using:Microsoft.PowerToys.Settings.UI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hotkeyConflicts="using:Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:winuiex="using:WinUIEx"
MinWidth="480"
MinHeight="600"
MaxWidth="900"
MaxHeight="1000"
Closed="WindowEx_Closed"
IsMaximizable="False"
IsMinimizable="False"
mc:Ignorable="d">
<Window.SystemBackdrop>
<MicaBackdrop />
</Window.SystemBackdrop>
<Grid x:Name="RootGrid">
<Grid.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<LinearGradientBrush x:Key="WindowsLogoGradient" StartPoint="0,0" EndPoint="1,1">
<GradientStop Offset="0.0" Color="#FF80F9FF" />
<GradientStop Offset="1" Color="#FF0B9CFF" />
</LinearGradientBrush>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<LinearGradientBrush x:Key="WindowsLogoGradient" StartPoint="0,0" EndPoint="1,1">
<GradientStop Offset="0.0" Color="#FF4DD2FF" />
<GradientStop Offset="0.75" Color="#FF0078D4" />
</LinearGradientBrush>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<SolidColorBrush x:Key="WindowsLogoGradient" Color="{StaticResource SystemColorHighlightTextColor}" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Title Bar Area -->
<Grid
x:Name="titleBar"
Height="48"
ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftPaddingColumn" Width="0" />
<ColumnDefinition x:Name="IconColumn" Width="Auto" />
<ColumnDefinition x:Name="TitleColumn" Width="Auto" />
<ColumnDefinition x:Name="RightPaddingColumn" Width="0" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="1"
Width="16"
Height="16"
VerticalAlignment="Center"
Source="/Assets/Settings/icon.ico" />
<TextBlock
x:Uid="ShortcutConflictWindow_TitleTxt"
Grid.Column="2"
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}" />
</Grid>
<!-- Description text -->
<TextBlock
x:Uid="ShortcutConflictWindow_Description"
Grid.Row="1"
Margin="16,24,16,24"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource BodyTextBlockStyle}"
TextWrapping="Wrap" />
<!-- Main Content Area -->
<ScrollViewer Grid.Row="2">
<Grid Margin="16,0,16,16">
<!-- Conflicts List -->
<ItemsControl x:Name="ConflictItemsControl" ItemsSource="{x:Bind ViewModel.ConflictItems, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" Spacing="32" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="hotkeyConflicts:HotkeyConflictGroupData">
<StackPanel Orientation="Vertical">
<!-- Hotkey Header -->
<controls:ShortcutWithTextLabelControl
x:Uid="ShortcutConflictWindow_ModulesUsingShortcut"
Margin="0,0,0,8"
FontWeight="SemiBold"
Keys="{x:Bind Hotkey.GetKeysList()}"
LabelPlacement="Before" />
<!-- PowerToys Module Cards -->
<ItemsControl Grid.Row="1" ItemsSource="{x:Bind Modules}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="hotkeyConflicts:ModuleHotkeyData">
<tkcontrols:SettingsCard
Margin="0,0,0,4"
Click="SettingsCard_Click"
Description="{x:Bind DisplayName}"
Header="{x:Bind Header}"
IsClickEnabled="True">
<tkcontrols:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False" UriSource="{x:Bind IconPath}" />
</tkcontrols:SettingsCard.HeaderIcon>
<!-- ShortcutControl with TwoWay binding and enabled for editing -->
<controls:ShortcutControl
x:Name="ShortcutControl"
MinWidth="140"
Margin="2"
VerticalAlignment="Center"
HasConflict="True"
HotkeySettings="{x:Bind HotkeySettings, Mode=TwoWay}"
IsEnabled="True" />
</tkcontrols:SettingsCard>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- System Conflict Card (only show if it's a system conflict) -->
<tkcontrols:SettingsCard
x:Name="SystemConflictCard"
x:Uid="ShortcutConflictWindow_SystemCard"
Visibility="{x:Bind IsSystemConflict}">
<tkcontrols:SettingsCard.HeaderIcon>
<PathIcon Data="M9 20H0V11H9V20ZM20 20H11V11H20V20ZM9 9H0V0H9V9ZM20 9H11V0H20V9Z" Foreground="{ThemeResource WindowsLogoGradient}" />
</tkcontrols:SettingsCard.HeaderIcon>
<!-- System shortcut message -->
<TextBlock
x:Uid="ShortcutConflictWindow_SystemShortcutMessage"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
</tkcontrols:SettingsCard>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</ScrollViewer>
<!-- Empty State (when no conflicts) -->
<StackPanel
x:Name="EmptyStatePanel"
Grid.Row="2"
Margin="24"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="Collapsed">
<FontIcon
HorizontalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
FontSize="48"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE73E;" />
<TextBlock Margin="0,16,0,4" HorizontalAlignment="Center">
<Run x:Uid="ShortcutConflictWindow_NoConflictsTitle" />
<Run x:Uid="ShortcutConflictWindow_NoConflictsDescription" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</TextBlock>
</StackPanel>
</Grid>
</winuiex:WindowEx>

View File

@@ -0,0 +1,91 @@
// 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 CommunityToolkit.WinUI.Controls;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
using Microsoft.PowerToys.Settings.UI.Services;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Windows.Graphics;
using WinUIEx;
namespace Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard
{
public sealed partial class ShortcutConflictWindow : WindowEx
{
public ShortcutConflictViewModel DataContext { get; }
public ShortcutConflictViewModel ViewModel { get; private set; }
public ShortcutConflictWindow()
{
var settingsUtils = new SettingsUtils();
ViewModel = new ShortcutConflictViewModel(
settingsUtils,
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils),
ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
this.Activated += Window_Activated_SetIcon;
// Set localized window title
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
this.ExtendsContentIntoTitleBar = true;
this.Title = resourceLoader.GetString("ShortcutConflictWindow_Title");
this.CenterOnScreen();
ViewModel.OnPageLoaded();
}
private void CenterOnScreen()
{
var displayArea = DisplayArea.GetFromWindowId(this.AppWindow.Id, DisplayAreaFallback.Nearest);
if (displayArea != null)
{
var windowSize = this.AppWindow.Size;
var centeredPosition = new PointInt32
{
X = (displayArea.WorkArea.Width - windowSize.Width) / 2,
Y = (displayArea.WorkArea.Height - windowSize.Height) / 2,
};
this.AppWindow.Move(centeredPosition);
}
}
private void SettingsCard_Click(object sender, RoutedEventArgs e)
{
if (sender is SettingsCard settingsCard &&
settingsCard.DataContext is ModuleHotkeyData moduleData)
{
var moduleType = moduleData.ModuleType;
NavigationService.Navigate(ModuleHelper.GetModulePageType(moduleType));
this.Close();
}
}
private void WindowEx_Closed(object sender, WindowEventArgs args)
{
ViewModel?.Dispose();
}
private void Window_Activated_SetIcon(object sender, WindowActivatedEventArgs args)
{
// Set window icon
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
WindowId windowId = Win32Interop.GetWindowIdFromWindow(hWnd);
AppWindow appWindow = AppWindow.GetFromWindowId(windowId);
appWindow.SetIcon("Assets\\Settings\\icon.ico");
}
}
}

View File

@@ -188,4 +188,4 @@
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</ResourceDictionary>

View File

@@ -3,6 +3,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:converters="using:Microsoft.PowerToys.Settings.UI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="LayoutRoot"
@@ -39,6 +40,7 @@
Content="{Binding}"
CornerRadius="{StaticResource ControlCornerRadius}"
FontWeight="SemiBold"
IsInvalid="{Binding ElementName=LayoutRoot, Path=HasConflict}"
IsTabStop="False"
Style="{StaticResource AccentKeyVisualStyle}" />
</DataTemplate>

View File

@@ -3,9 +3,15 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using CommunityToolkit.WinUI;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
using Microsoft.PowerToys.Settings.UI.Services;
using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
@@ -33,8 +39,9 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register("Enabled", typeof(bool), typeof(ShortcutControl), null);
public static readonly DependencyProperty HotkeySettingsProperty = DependencyProperty.Register("HotkeySettings", typeof(HotkeySettings), typeof(ShortcutControl), null);
public static readonly DependencyProperty AllowDisableProperty = DependencyProperty.Register("AllowDisable", typeof(bool), typeof(ShortcutControl), new PropertyMetadata(false, OnAllowDisableChanged));
public static readonly DependencyProperty HasConflictProperty = DependencyProperty.Register("HasConflict", typeof(bool), typeof(ShortcutControl), new PropertyMetadata(false, OnHasConflictChanged));
public static readonly DependencyProperty TooltipProperty = DependencyProperty.Register("Tooltip", typeof(string), typeof(ShortcutControl), new PropertyMetadata(null, OnTooltipChanged));
private static ResourceLoader resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
@@ -58,6 +65,28 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
description.Text = text;
}
private static void OnHasConflictChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as ShortcutControl;
if (control == null)
{
return;
}
control.UpdateKeyVisualStyles();
}
private static void OnTooltipChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as ShortcutControl;
if (control == null)
{
return;
}
control.UpdateTooltip();
}
private ShortcutDialogContentControl c = new ShortcutDialogContentControl();
private ContentDialog shortcutDialog;
@@ -67,6 +96,18 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
set => SetValue(AllowDisableProperty, value);
}
public bool HasConflict
{
get => (bool)GetValue(HasConflictProperty);
set => SetValue(HasConflictProperty, value);
}
public string Tooltip
{
get => (string)GetValue(TooltipProperty);
set => SetValue(TooltipProperty, value);
}
public bool Enabled
{
get
@@ -101,14 +142,54 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
if (hotkeySettings != value)
{
// Unsubscribe from old settings
if (hotkeySettings != null)
{
hotkeySettings.PropertyChanged -= OnHotkeySettingsPropertyChanged;
}
hotkeySettings = value;
SetValue(HotkeySettingsProperty, value);
// Subscribe to new settings
if (hotkeySettings != null)
{
hotkeySettings.PropertyChanged += OnHotkeySettingsPropertyChanged;
// Update UI based on conflict properties
UpdateConflictStatusFromHotkeySettings();
}
SetKeys();
c.Keys = HotkeySettings.GetKeysList();
c.Keys = HotkeySettings?.GetKeysList();
}
}
}
private void OnHotkeySettingsPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(HotkeySettings.HasConflict) ||
e.PropertyName == nameof(HotkeySettings.ConflictDescription))
{
UpdateConflictStatusFromHotkeySettings();
}
}
private void UpdateConflictStatusFromHotkeySettings()
{
if (hotkeySettings != null)
{
// Update the ShortcutControl's conflict properties from HotkeySettings
HasConflict = hotkeySettings.HasConflict;
Tooltip = hotkeySettings.HasConflict ? hotkeySettings.ConflictDescription : null;
}
else
{
HasConflict = false;
Tooltip = null;
}
}
public ShortcutControl()
{
InitializeComponent();
@@ -136,6 +217,29 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
OnAllowDisableChanged(this, null);
}
private void UpdateKeyVisualStyles()
{
if (PreviewKeysControl?.ItemsSource != null)
{
// Force refresh of the ItemsControl to update KeyVisual styles
var items = PreviewKeysControl.ItemsSource;
PreviewKeysControl.ItemsSource = null;
PreviewKeysControl.ItemsSource = items;
}
}
private void UpdateTooltip()
{
if (!string.IsNullOrEmpty(Tooltip))
{
ToolTipService.SetToolTip(EditButton, Tooltip);
}
else
{
ToolTipService.SetToolTip(EditButton, null);
}
}
private void ShortcutControl_Unloaded(object sender, RoutedEventArgs e)
{
shortcutDialog.PrimaryButtonClick -= ShortcutDialog_PrimaryButtonClick;
@@ -147,6 +251,12 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
App.GetSettingsWindow().Activated -= ShortcutDialog_SettingsWindow_Activated;
}
// Unsubscribe from HotkeySettings property changes
if (hotkeySettings != null)
{
hotkeySettings.PropertyChanged -= OnHotkeySettingsPropertyChanged;
}
// Dispose the HotkeySettingsControlHook object to terminate the hook threads when the textbox is unloaded
hook?.Dispose();
@@ -168,6 +278,9 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
App.GetSettingsWindow().Activated += ShortcutDialog_SettingsWindow_Activated;
}
// Initialize tooltip when loaded
UpdateTooltip();
}
private void KeyEventHandler(int key, bool matchValue, int matchValueCode)
@@ -302,6 +415,8 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
KeyEventHandler(key, true, key);
c.Keys = internalSettings.GetKeysList();
c.ConflictMessage = string.Empty;
c.HasConflict = false;
if (internalSettings.GetKeysList().Count == 0)
{
@@ -336,12 +451,74 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
else
{
EnableKeys();
if (lastValidSettings.IsValid())
{
if (string.Equals(lastValidSettings.ToString(), hotkeySettings.ToString(), StringComparison.OrdinalIgnoreCase))
{
c.HasConflict = hotkeySettings.HasConflict;
c.ConflictMessage = hotkeySettings.ConflictDescription;
}
else
{
// Check for conflicts with the new hotkey settings
CheckForConflicts(lastValidSettings);
}
}
}
}
c.IsWarningAltGr = internalSettings.Ctrl && internalSettings.Alt && !internalSettings.Win && (internalSettings.Code > 0);
}
private void CheckForConflicts(HotkeySettings settings)
{
void UpdateUIForConflict(bool hasConflict, HotkeyConflictResponse hotkeyConflictResponse)
{
DispatcherQueue.TryEnqueue(() =>
{
if (hasConflict)
{
// Build conflict message from all conflicts - only show module names
var conflictingModules = new HashSet<string>();
foreach (var conflict in hotkeyConflictResponse.AllConflicts)
{
if (!string.IsNullOrEmpty(conflict.ModuleName))
{
conflictingModules.Add(conflict.ModuleName);
}
}
if (conflictingModules.Count > 0)
{
var moduleNames = conflictingModules.ToArray();
var conflictMessage = moduleNames.Length == 1
? $"Conflict detected with {moduleNames[0]}"
: $"Conflicts detected with: {string.Join(", ", moduleNames)}";
c.ConflictMessage = conflictMessage;
}
else
{
c.ConflictMessage = "Conflict detected with unknown module";
}
c.HasConflict = true;
}
else
{
c.ConflictMessage = string.Empty;
c.HasConflict = false;
}
});
}
HotkeyConflictHelper.CheckHotkeyConflict(
settings,
ShellPage.SendDefaultIPCMessage,
UpdateUIForConflict);
}
private void EnableKeys()
{
shortcutDialog.IsPrimaryButtonEnabled = true;
@@ -416,6 +593,9 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
c.Keys = null;
c.Keys = HotkeySettings.GetKeysList();
c.HasConflict = hotkeySettings.HasConflict;
c.ConflictMessage = hotkeySettings.ConflictDescription;
// 92 means the Win key. The logic is: warning should be visible if the shortcut contains Alt AND contains Ctrl AND NOT contains Win.
// Additional key must be present, as this is a valid, previously used shortcut shown at dialog open. Check for presence of non-modifier-key is not necessary therefore
c.IsWarningAltGr = c.Keys.Contains("Ctrl") && c.Keys.Contains("Alt") && !c.Keys.Contains(92);
@@ -434,16 +614,32 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
lastValidSettings = hotkeySettings;
shortcutDialog.Hide();
// Send RequestAllConflicts IPC to update the UI after changed hotkey settings.
GlobalHotkeyConflictManager.Instance?.RequestAllConflicts();
}
private void ShortcutDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (ComboIsValid(lastValidSettings))
{
HotkeySettings = lastValidSettings with { };
if (c.HasConflict)
{
lastValidSettings = lastValidSettings with { HasConflict = true };
}
else
{
lastValidSettings = lastValidSettings with { HasConflict = false };
}
HotkeySettings = lastValidSettings;
}
SetKeys();
// Send RequestAllConflicts IPC to update the UI after changed hotkey settings.
GlobalHotkeyConflictManager.Instance?.RequestAllConflicts();
shortcutDialog.Hide();
}
@@ -520,7 +716,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
private void SetKeys()
{
var keys = HotkeySettings.GetKeysList();
var keys = HotkeySettings?.GetKeysList();
if (keys != null && keys.Count > 0)
{

View File

@@ -63,6 +63,13 @@
IsOpen="{Binding ElementName=ShortcutContentControl, Path=IsWarningAltGr, Mode=OneWay}"
IsTabStop="{Binding ElementName=ShortcutContentControl, Path=IsWarningAltGr, Mode=OneWay}"
Severity="Warning" />
<InfoBar
Title="Hotkey Conflict"
IsClosable="False"
IsOpen="{Binding ElementName=ShortcutContentControl, Path=HasConflict, Mode=OneWay}"
IsTabStop="{Binding ElementName=ShortcutContentControl, Path=HasConflict, Mode=OneWay}"
Message="{Binding ElementName=ShortcutContentControl, Path=ConflictMessage, Mode=OneWay}"
Severity="Warning" />
</Grid>
<tk7controls:MarkdownTextBlock
x:Uid="InvalidShortcutWarningLabel"
@@ -71,4 +78,4 @@
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</StackPanel>
</Grid>
</UserControl>
</UserControl>

View File

@@ -11,6 +11,24 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
public sealed partial class ShortcutDialogContentControl : UserControl
{
public static readonly DependencyProperty KeysProperty = DependencyProperty.Register("Keys", typeof(List<object>), typeof(ShortcutDialogContentControl), new PropertyMetadata(default(string)));
public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register("IsError", typeof(bool), typeof(ShortcutDialogContentControl), new PropertyMetadata(false));
public static readonly DependencyProperty IsWarningAltGrProperty = DependencyProperty.Register("IsWarningAltGr", typeof(bool), typeof(ShortcutDialogContentControl), new PropertyMetadata(false));
public static readonly DependencyProperty HasConflictProperty = DependencyProperty.Register("HasConflict", typeof(bool), typeof(ShortcutDialogContentControl), new PropertyMetadata(false));
public static readonly DependencyProperty ConflictMessageProperty = DependencyProperty.Register("ConflictMessage", typeof(string), typeof(ShortcutDialogContentControl), new PropertyMetadata(string.Empty));
public bool HasConflict
{
get => (bool)GetValue(HasConflictProperty);
set => SetValue(HasConflictProperty, value);
}
public string ConflictMessage
{
get => (string)GetValue(ConflictMessageProperty);
set => SetValue(ConflictMessageProperty, value);
}
public ShortcutDialogContentControl()
{
this.InitializeComponent();
@@ -22,22 +40,16 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
set { SetValue(KeysProperty, value); }
}
public static readonly DependencyProperty KeysProperty = DependencyProperty.Register("Keys", typeof(List<object>), typeof(SettingsPageControl), new PropertyMetadata(default(string)));
public bool IsError
{
get => (bool)GetValue(IsErrorProperty);
set => SetValue(IsErrorProperty, value);
}
public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register("IsError", typeof(bool), typeof(ShortcutDialogContentControl), new PropertyMetadata(false));
public bool IsWarningAltGr
{
get => (bool)GetValue(IsWarningAltGrProperty);
set => SetValue(IsWarningAltGrProperty, value);
}
public static readonly DependencyProperty IsWarningAltGrProperty = DependencyProperty.Register("IsWarningAltGr", typeof(bool), typeof(ShortcutDialogContentControl), new PropertyMetadata(false));
}
}

View File

@@ -16,6 +16,7 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ItemsControl
x:Name="ShortcutsControl"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
IsTabStop="False"
@@ -32,14 +33,27 @@
AutomationProperties.AccessibilityView="Raw"
Content="{Binding}"
FontSize="12"
IsTabStop="False" />
IsTabStop="False"
Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<tk7controls:MarkdownTextBlock
x:Name="LabelControl"
Grid.Column="1"
VerticalAlignment="Center"
Background="Transparent"
Text="{x:Bind Text}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LabelPlacementStates">
<VisualState x:Name="LabelAfter" />
<VisualState x:Name="LabelBefore">
<VisualState.Setters>
<Setter Target="LabelControl.(Grid.Column)" Value="0" />
<Setter Target="ShortcutsControl.(Grid.Column)" Value="1" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</UserControl>

View File

@@ -17,7 +17,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(default(string)));
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(default(string)));
public List<object> Keys
{
@@ -25,11 +25,40 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
set { SetValue(KeysProperty, value); }
}
public static readonly DependencyProperty KeysProperty = DependencyProperty.Register("Keys", typeof(List<object>), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(default(string)));
public static readonly DependencyProperty KeysProperty = DependencyProperty.Register(nameof(Keys), typeof(List<object>), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(default(string)));
public LabelPlacement LabelPlacement
{
get { return (LabelPlacement)GetValue(LabelPlacementProperty); }
set { SetValue(LabelPlacementProperty, value); }
}
public static readonly DependencyProperty LabelPlacementProperty = DependencyProperty.Register(nameof(LabelPlacement), typeof(LabelPlacement), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(defaultValue: LabelPlacement.After, OnIsLabelPlacementChanged));
public ShortcutWithTextLabelControl()
{
this.InitializeComponent();
}
private static void OnIsLabelPlacementChanged(DependencyObject d, DependencyPropertyChangedEventArgs newValue)
{
if (d is ShortcutWithTextLabelControl labelControl)
{
if (labelControl.LabelPlacement == LabelPlacement.Before)
{
VisualStateManager.GoToState(labelControl, "LabelBefore", true);
}
else
{
VisualStateManager.GoToState(labelControl, "LabelAfter", true);
}
}
}
}
public enum LabelPlacement
{
Before,
After,
}
}

View File

@@ -20,33 +20,56 @@
</HyperlinkButton>
</StackPanel>
<StackPanel
Orientation="Vertical"
Spacing="8"
Visibility="{x:Bind ShowDataDiagnosticsSetting, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<StackPanel Orientation="Vertical" Visibility="{x:Bind ShowDataDiagnosticsSetting, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<TextBlock
x:Uid="Oobe_Overview_Telemetry_Title"
Margin="0,20,0,0"
Style="{StaticResource SubtitleTextBlockStyle}" />
Margin="0,24,0,0"
Style="{StaticResource BodyStrongTextBlockStyle}" />
<TextBlock x:Uid="Oobe_Overview_Telemetry_Desc" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<tkcontrols:SettingsCard
x:Uid="Oobe_Overview_EnableDataDiagnostics"
Margin="0,8,0,0"
HeaderIcon="{ui:FontIcon Glyph=&#xE9D9;}"
IsEnabled="{x:Bind ShowDataDiagnosticsSetting, Mode=OneWay}">
<tkcontrols:SettingsCard.Description>
<StackPanel Orientation="Vertical">
<TextBlock
x:Uid="GeneralPage_EnableDataDiagnosticsText"
Style="{StaticResource SecondaryTextStyle}"
TextWrapping="WrapWholeWords" />
<HyperlinkButton
x:Uid="GeneralPage_DiagnosticsAndFeedback_Link"
Margin="0,2,0,0"
FontWeight="SemiBold"
NavigateUri="https://aka.ms/powertoys-data-and-privacy-documentation" />
<HyperlinkButton
x:Uid="Oobe_Overview_DiagnosticsAndFeedback_Settings_Link"
Margin="0,2,0,0"
Click="GeneralSettingsLaunchButton_Click"
FontWeight="SemiBold" />
</StackPanel>
</tkcontrols:SettingsCard.Description>
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind EnableDataDiagnostics, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<HyperlinkButton
x:Uid="Oobe_Overview_DiagnosticsAndFeedback_Settings_Link"
Margin="-8,0,0,0"
Click="GeneralSettingsLaunchButton_Click" />
<HyperlinkButton
x:Uid="Oobe_Overview_DiagnosticsAndFeedback_Link"
Margin="-8,0,0,0"
NavigateUri="https://aka.ms/powertoys-data-and-privacy-documentation" />
<!-- Show title and description only when there are conflicts -->
<TextBlock
x:Uid="Oobe_Overview_Hotkey_Conflict_Title"
Margin="0,24,0,8"
Style="{StaticResource BodyStrongTextBlockStyle}" />
<!-- Always show shortcut status card -->
<tkcontrols:SettingsCard Description="{x:Bind ConflictDescription, Mode=OneWay}" Header="{x:Bind ConflictText, Mode=OneWay}">
<tkcontrols:SettingsCard.HeaderIcon>
<FontIcon Foreground="{x:Bind IconForeground, Mode=OneWay}" Glyph="{x:Bind IconGlyph, Mode=OneWay}" />
</tkcontrols:SettingsCard.HeaderIcon>
<!-- Only show button when there are conflicts -->
<Button
x:Uid="ResolveConflicts_Button"
Click="ShortcutConflictBtn_Click"
Visibility="{x:Bind HasConflicts, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</tkcontrols:SettingsCard>
</StackPanel>
</StackPanel>
</controls:OOBEPageControl.PageContent>

View File

@@ -2,21 +2,32 @@
// 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.ComponentModel;
using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.PowerToys.Settings.UI.Services;
using Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard;
using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
{
public sealed partial class OobeOverview : Page
public sealed partial class OobeOverview : Page, INotifyPropertyChanged
{
public OobePowerToysModule ViewModel { get; set; }
private bool _enableDataDiagnostics;
private AllHotkeyConflictsData _allHotkeyConflictsData = new AllHotkeyConflictsData();
private Windows.ApplicationModel.Resources.ResourceLoader resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
public bool EnableDataDiagnostics
{
@@ -41,8 +52,151 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
}
}
public AllHotkeyConflictsData AllHotkeyConflictsData
{
get => _allHotkeyConflictsData;
set
{
if (_allHotkeyConflictsData != value)
{
_allHotkeyConflictsData = value;
OnPropertyChanged(nameof(AllHotkeyConflictsData));
OnPropertyChanged(nameof(ConflictCount));
OnPropertyChanged(nameof(ConflictText));
OnPropertyChanged(nameof(ConflictDescription));
OnPropertyChanged(nameof(HasConflicts));
OnPropertyChanged(nameof(IconGlyph));
OnPropertyChanged(nameof(IconForeground));
}
}
}
public int ConflictCount
{
get
{
if (AllHotkeyConflictsData == null)
{
return 0;
}
int count = 0;
if (AllHotkeyConflictsData.InAppConflicts != null)
{
count += AllHotkeyConflictsData.InAppConflicts.Count;
}
if (AllHotkeyConflictsData.SystemConflicts != null)
{
count += AllHotkeyConflictsData.SystemConflicts.Count;
}
return count;
}
}
public string ConflictText
{
get
{
var count = ConflictCount;
if (count == 0)
{
// Return no-conflict message
try
{
return resourceLoader.GetString("ShortcutConflictControl_NoConflictsFound");
}
catch
{
return "No conflicts found";
}
}
else if (count == 1)
{
// Try to get localized string
try
{
return resourceLoader.GetString("ShortcutConflictControl_SingleConflictFound");
}
catch
{
return "1 shortcut conflict";
}
}
else
{
// Try to get localized string
try
{
var template = resourceLoader.GetString("ShortcutConflictControl_MultipleConflictsFound");
return string.Format(System.Globalization.CultureInfo.CurrentCulture, template, count);
}
catch
{
return $"{count} shortcut conflicts";
}
}
}
}
public string ConflictDescription
{
get
{
var count = ConflictCount;
if (count == 0)
{
// Return no-conflict description
try
{
return resourceLoader.GetString("ShortcutConflictWindow_NoConflictsDescription");
}
catch
{
return "All shortcuts function correctly";
}
}
else
{
// Return conflict description
try
{
return resourceLoader.GetString("Oobe_Overview_Hotkey_Conflict_Card_Description");
}
catch
{
return "Shortcuts configured by PowerToys are conflicting";
}
}
}
}
public bool HasConflicts => ConflictCount > 0;
public string IconGlyph => HasConflicts ? "\uE814" : "\uE73E";
public SolidColorBrush IconForeground
{
get
{
if (HasConflicts)
{
// Red color for conflicts
return (SolidColorBrush)App.Current.Resources["SystemFillColorCriticalBrush"];
}
else
{
// Green color for no conflicts
return (SolidColorBrush)App.Current.Resources["SystemFillColorSuccessBrush"];
}
}
}
public bool ShowDataDiagnosticsSetting => GetIsDataDiagnosticsInfoBarEnabled();
public event PropertyChangedEventHandler PropertyChanged;
private bool GetIsDataDiagnosticsInfoBarEnabled()
{
var isDataDiagnosticsGpoDisallowed = GPOWrapper.GetAllowDataDiagnosticsValue() == GpoRuleConfigured.Disabled;
@@ -57,7 +211,27 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
_enableDataDiagnostics = DataDiagnosticsSettings.GetEnabledValue();
ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.Overview]);
DataContext = ViewModel;
DataContext = this;
// Subscribe to hotkey conflict updates
if (GlobalHotkeyConflictManager.Instance != null)
{
GlobalHotkeyConflictManager.Instance.ConflictsUpdated += OnConflictsUpdated;
GlobalHotkeyConflictManager.Instance.RequestAllConflicts();
}
}
private void OnConflictsUpdated(object sender, AllHotkeyConflictsEventArgs e)
{
this.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () =>
{
AllHotkeyConflictsData = e.Conflicts ?? new AllHotkeyConflictsData();
});
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
@@ -80,6 +254,18 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
ViewModel.LogOpeningSettingsEvent();
}
private void ShortcutConflictBtn_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
if (AllHotkeyConflictsData == null || !HasConflicts)
{
return;
}
// Create and show the shortcut conflict window
var conflictWindow = new ShortcutConflictWindow();
conflictWindow.Activate();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
@@ -88,6 +274,12 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
ViewModel.LogClosingModuleEvent();
// Unsubscribe from conflict updates when leaving the page
if (GlobalHotkeyConflictManager.Instance != null)
{
GlobalHotkeyConflictManager.Instance.ConflictsUpdated -= OnConflictsUpdated;
}
}
}
}

View File

@@ -11,141 +11,177 @@
Loaded="Page_Loaded"
mc:Ignorable="d">
<!-- Main layout container -->
<Grid>
<!-- Main content grid -->
<Grid Margin="0,24,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Margin="0,24,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Compact Header overlay that covers both InfoBar and Title sections -->
<Border
x:Name="HeaderOverlay"
Grid.Row="0"
Grid.RowSpan="2"
Margin="0,-24,0,0"
VerticalAlignment="Top"
BorderThickness="0"
Canvas.ZIndex="1">
<tkcontrols:SettingsCard
x:Name="WhatsNewDataDiagnosticsInfoBar"
x:Uid="Oobe_WhatsNew_DataDiagnostics_InfoBar"
Margin="0,-24,0,0"
Background="{ThemeResource InfoBarInformationalSeverityBackgroundBrush}"
IsTabStop="{x:Bind ShowDataDiagnosticsInfoBar, Mode=OneWay}"
Visibility="{x:Bind ShowDataDiagnosticsInfoBar, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<tkcontrols:SettingsCard.HeaderIcon>
<FontIcon Foreground="{ThemeResource InfoBarInformationalSeverityIconBackground}" Glyph="&#xF167;" />
</tkcontrols:SettingsCard.HeaderIcon>
<tkcontrols:SettingsCard.Description>
<StackPanel>
<TextBlock x:Name="WhatsNewDataDiagnosticsInfoBarDescText">
<Hyperlink NavigateUri="https://aka.ms/powertoys-data-and-privacy-documentation">
<Run x:Uid="Oobe_WhatsNew_DataDiagnostics_InfoBar_Desc" />
</Hyperlink>
</TextBlock>
<TextBlock x:Name="WhatsNewDataDiagnosticsInfoBarDescTextYesClicked" Visibility="Collapsed">
<Run x:Uid="Oobe_WhatsNew_DataDiagnostics_Yes_Click_InfoBar_Desc" />
<Hyperlink Click="DataDiagnostics_OpenSettings_Click">
<Run x:Uid="Oobe_WhatsNew_DataDiagnostics_Yes_Click_OpenSettings_Text" />
</Hyperlink>
</TextBlock>
</StackPanel>
</tkcontrols:SettingsCard.Description>
<StackPanel Orientation="Horizontal" Spacing="8">
<Button
x:Name="DataDiagnosticsButtonYes"
x:Uid="Oobe_WhatsNew_DataDiagnostics_Button_Yes"
Click="DataDiagnostics_InfoBar_YesNo_Click"
CommandParameter="Yes" />
<HyperlinkButton
x:Name="DataDiagnosticsButtonNo"
x:Uid="Oobe_WhatsNew_DataDiagnostics_Button_No"
Click="DataDiagnostics_InfoBar_YesNo_Click"
CommandParameter="No" />
<Button
Margin="16,0,0,0"
Click="DataDiagnostics_InfoBar_Close_Click"
Content="{ui:FontIcon Glyph=&#xE894;,
FontSize=16}"
Style="{StaticResource SubtleButtonStyle}" />
</StackPanel>
</tkcontrols:SettingsCard>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel
Grid.Row="1"
Margin="32,16,0,16"
VerticalAlignment="Top"
Orientation="Vertical">
<TextBlock
x:Uid="Oobe_WhatsNew"
AutomationProperties.HeadingLevel="Level1"
Style="{StaticResource TitleTextBlockStyle}" />
<HyperlinkButton NavigateUri="https://github.com/microsoft/PowerToys/releases" Style="{StaticResource TextButtonStyle}">
<TextBlock x:Uid="Oobe_WhatsNew_DetailedReleaseNotesLink" TextWrapping="Wrap" />
</HyperlinkButton>
</StackPanel>
<tkcontrols:SettingsCard
x:Name="WhatsNewDataDiagnosticsInfoBar"
x:Uid="Oobe_WhatsNew_DataDiagnostics_InfoBar"
Grid.Row="0"
Padding="12,8,12,8"
Background="{ThemeResource InfoBarInformationalSeverityBackgroundBrush}"
IsTabStop="{x:Bind ShowDataDiagnosticsInfoBar, Mode=OneWay}"
Visibility="{x:Bind ShowDataDiagnosticsInfoBar, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<tkcontrols:SettingsCard.HeaderIcon>
<FontIcon Foreground="{ThemeResource InfoBarInformationalSeverityIconBackground}" Glyph="&#xF167;" />
</tkcontrols:SettingsCard.HeaderIcon>
<tkcontrols:SettingsCard.Description>
<StackPanel>
<TextBlock x:Name="WhatsNewDataDiagnosticsInfoBarDescText">
<Hyperlink NavigateUri="https://aka.ms/powertoys-data-and-privacy-documentation">
<Run x:Uid="Oobe_WhatsNew_DataDiagnostics_InfoBar_Desc" />
</Hyperlink>
</TextBlock>
<TextBlock x:Name="WhatsNewDataDiagnosticsInfoBarDescTextYesClicked" Visibility="Collapsed">
<Run x:Uid="Oobe_WhatsNew_DataDiagnostics_Yes_Click_InfoBar_Desc" />
<Hyperlink Click="DataDiagnostics_OpenSettings_Click">
<Run x:Uid="Oobe_WhatsNew_DataDiagnostics_Yes_Click_OpenSettings_Text" />
</Hyperlink>
</TextBlock>
</StackPanel>
</tkcontrols:SettingsCard.Description>
<StackPanel Orientation="Horizontal" Spacing="8">
<Button
x:Name="DataDiagnosticsButtonYes"
x:Uid="Oobe_WhatsNew_DataDiagnostics_Button_Yes"
Click="DataDiagnostics_InfoBar_YesNo_Click"
CommandParameter="Yes" />
<HyperlinkButton
x:Name="DataDiagnosticsButtonNo"
x:Uid="Oobe_WhatsNew_DataDiagnostics_Button_No"
Click="DataDiagnostics_InfoBar_YesNo_Click"
CommandParameter="No" />
<Button
Margin="16,0,0,0"
Click="DataDiagnostics_InfoBar_Close_Click"
Content="{ui:FontIcon Glyph=&#xE894;,
FontSize=16}"
Style="{StaticResource SubtleButtonStyle}" />
</StackPanel>
</tkcontrols:SettingsCard>
<InfoBar
x:Name="ErrorInfoBar"
x:Uid="Oobe_WhatsNew_LoadingError"
Grid.Row="2"
VerticalAlignment="Top"
IsClosable="False"
IsTabStop="False"
Severity="Error">
<InfoBar.ActionButton>
<Button
x:Uid="RetryBtn"
HorizontalAlignment="Right"
Click="LoadReleaseNotes_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="16" Glyph="&#xE72C;" />
<TextBlock x:Uid="RetryLabel" />
</StackPanel>
</Button>
</InfoBar.ActionButton>
</InfoBar>
<InfoBar
x:Name="ProxyWarningInfoBar"
x:Uid="Oobe_WhatsNew_ProxyAuthenticationWarning"
Grid.Row="2"
VerticalAlignment="Top"
IsClosable="False"
IsTabStop="False"
Severity="Warning">
<InfoBar.ActionButton>
<Button
x:Uid="RetryBtn"
HorizontalAlignment="Right"
Click="LoadReleaseNotes_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="16" Glyph="&#xE72C;" />
<TextBlock x:Uid="RetryLabel" />
</StackPanel>
</Button>
</InfoBar.ActionButton>
</InfoBar>
<Grid Grid.Row="1" Margin="16,12,0,12">
<StackPanel
VerticalAlignment="Top"
Orientation="Vertical"
Spacing="4">
<TextBlock
x:Uid="Oobe_WhatsNew"
AutomationProperties.HeadingLevel="Level1"
Style="{StaticResource TitleTextBlockStyle}" />
<HyperlinkButton NavigateUri="https://github.com/microsoft/PowerToys/releases" Style="{StaticResource TextButtonStyle}">
<TextBlock x:Uid="Oobe_WhatsNew_DetailedReleaseNotesLink" TextWrapping="Wrap" />
</HyperlinkButton>
</StackPanel>
<ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto">
<Grid Margin="32,24,32,24">
<ProgressRing
x:Name="LoadingProgressRing"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsIndeterminate="True"
Visibility="Visible" />
<tk7controls:MarkdownTextBlock
x:Name="ReleaseNotesMarkdown"
VerticalAlignment="Top"
Background="Transparent"
Header1FontSize="20"
Header1FontWeight="SemiBold"
Header1Margin="0,0,0,4"
Header3FontSize="16"
Header3FontWeight="SemiBold"
Header4FontSize="16"
Header4FontWeight="SemiBold"
HorizontalRuleMargin="24"
LinkClicked="ReleaseNotesMarkdown_LinkClicked"
ListMargin="-18,4,0,12"
ParagraphMargin="0,0,0,0"
TableMargin="24"
Visibility="Collapsed" />
</Grid>
</ScrollViewer>
<!-- ShortcutConflictControl positioned at the right side -->
<controls:ShortcutConflictControl
Grid.RowSpan="2"
Margin="0,0,16,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
AllHotkeyConflictsData="{x:Bind AllHotkeyConflictsData, Mode=OneWay}"
Visibility="{x:Bind HasConflicts, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</Grid>
</Grid>
</Border>
<!-- Reduced spacer for the compact header overlay -->
<Grid Grid.Row="0" Height="0" />
<Grid Grid.Row="1" Height="80" />
<InfoBar
x:Name="ErrorInfoBar"
x:Uid="Oobe_WhatsNew_LoadingError"
Grid.Row="2"
VerticalAlignment="Top"
IsClosable="False"
IsTabStop="False"
Severity="Error">
<InfoBar.ActionButton>
<Button
x:Uid="RetryBtn"
HorizontalAlignment="Right"
Click="LoadReleaseNotes_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="16" Glyph="&#xE72C;" />
<TextBlock x:Uid="RetryLabel" />
</StackPanel>
</Button>
</InfoBar.ActionButton>
</InfoBar>
<InfoBar
x:Name="ProxyWarningInfoBar"
x:Uid="Oobe_WhatsNew_ProxyAuthenticationWarning"
Grid.Row="2"
VerticalAlignment="Top"
IsClosable="False"
IsTabStop="False"
Severity="Warning">
<InfoBar.ActionButton>
<Button
x:Uid="RetryBtn"
HorizontalAlignment="Right"
Click="LoadReleaseNotes_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="16" Glyph="&#xE72C;" />
<TextBlock x:Uid="RetryLabel" />
</StackPanel>
</Button>
</InfoBar.ActionButton>
</InfoBar>
<ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto">
<Grid Margin="32,16,32,24">
<ProgressRing
x:Name="LoadingProgressRing"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsIndeterminate="True"
Visibility="Visible" />
<tk7controls:MarkdownTextBlock
x:Name="ReleaseNotesMarkdown"
VerticalAlignment="Top"
Background="Transparent"
Header1FontSize="20"
Header1FontWeight="SemiBold"
Header1Margin="0,0,0,4"
Header3FontSize="16"
Header3FontWeight="SemiBold"
Header4FontSize="16"
Header4FontWeight="SemiBold"
HorizontalRuleMargin="24"
LinkClicked="ReleaseNotesMarkdown_LinkClicked"
ListMargin="-18,4,0,12"
ParagraphMargin="0,0,0,0"
TableMargin="24"
Visibility="Collapsed" />
</Grid>
</ScrollViewer>
</Grid>
</Grid>
</Page>
</Page>

View File

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
@@ -17,9 +18,11 @@ using CommunityToolkit.WinUI.UI.Controls;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.PowerToys.Settings.UI.SerializationContext;
using Microsoft.PowerToys.Settings.UI.Services;
using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Xaml.Controls;
@@ -27,12 +30,54 @@ using Microsoft.UI.Xaml.Navigation;
namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
{
public sealed partial class OobeWhatsNew : Page
public sealed partial class OobeWhatsNew : Page, INotifyPropertyChanged
{
public OobePowerToysModule ViewModel { get; set; }
private AllHotkeyConflictsData _allHotkeyConflictsData = new AllHotkeyConflictsData();
public bool ShowDataDiagnosticsInfoBar => GetShowDataDiagnosticsInfoBar();
public AllHotkeyConflictsData AllHotkeyConflictsData
{
get => _allHotkeyConflictsData;
set
{
if (_allHotkeyConflictsData != value)
{
_allHotkeyConflictsData = value;
OnPropertyChanged(nameof(AllHotkeyConflictsData));
OnPropertyChanged(nameof(HasConflicts));
}
}
}
public bool HasConflicts
{
get
{
if (AllHotkeyConflictsData == null)
{
return false;
}
int count = 0;
if (AllHotkeyConflictsData.InAppConflicts != null)
{
count += AllHotkeyConflictsData.InAppConflicts.Count;
}
if (AllHotkeyConflictsData.SystemConflicts != null)
{
count += AllHotkeyConflictsData.SystemConflicts.Count;
}
return count > 0;
}
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Initializes a new instance of the <see cref="OobeWhatsNew"/> class.
/// </summary>
@@ -40,7 +85,27 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
{
this.InitializeComponent();
ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.WhatsNew]);
DataContext = ViewModel;
DataContext = this;
// Subscribe to hotkey conflict updates
if (GlobalHotkeyConflictManager.Instance != null)
{
GlobalHotkeyConflictManager.Instance.ConflictsUpdated += OnConflictsUpdated;
GlobalHotkeyConflictManager.Instance.RequestAllConflicts();
}
}
private void OnConflictsUpdated(object sender, AllHotkeyConflictsEventArgs e)
{
this.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () =>
{
AllHotkeyConflictsData = e.Conflicts ?? new AllHotkeyConflictsData();
});
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool GetShowDataDiagnosticsInfoBar()
@@ -184,6 +249,12 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
ViewModel.LogClosingModuleEvent();
// Unsubscribe from conflict updates when leaving the page
if (GlobalHotkeyConflictManager.Instance != null)
{
GlobalHotkeyConflictManager.Instance.ConflictsUpdated -= OnConflictsUpdated;
}
}
private void ReleaseNotesMarkdown_LinkClicked(object sender, LinkClickedEventArgs e)

View File

@@ -14,4 +14,4 @@
<MicaBackdrop />
</Window.SystemBackdrop>
<local:OobeShellPage x:Name="shellPage" />
</winuiex:WindowEx>
</winuiex:WindowEx>

View File

@@ -31,6 +31,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
public void RefreshEnabledState()

View File

@@ -19,6 +19,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ViewModel = new AlwaysOnTopViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<AlwaysOnTopSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
public void RefreshEnabledState()

View File

@@ -26,6 +26,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ShellPage.SendDefaultIPCMessage,
DispatcherQueue);
DataContext = ViewModel;
Loaded += (s, e) => ViewModel.OnPageLoaded();
InitializeComponent();
}

View File

@@ -35,6 +35,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
/// <summary>

View File

@@ -19,6 +19,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ViewModel = new CropAndLockViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<CropAndLockSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
public void RefreshEnabledState()

View File

@@ -133,7 +133,7 @@
Grid.Column="1"
Orientation="Horizontal"
Spacing="16">
<!--<controls:ShortcutConflictControl/>-->
<controls:ShortcutConflictControl AllHotkeyConflictsData="{x:Bind ViewModel.AllHotkeyConflictsData, Mode=OneWay}" />
<controls:CheckUpdateControl />
</StackPanel>
</Grid>

View File

@@ -39,6 +39,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ViewModel = new DashboardViewModel(
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
public void RefreshEnabledState()

View File

@@ -19,6 +19,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
var settingsUtils = new SettingsUtils();
ViewModel = new FancyZonesViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<FancyZonesSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
private void OpenColorsSettings_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)

View File

@@ -26,6 +26,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
DataContext = ViewModel;
InitializeComponent();
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
public void RefreshEnabledState()

View File

@@ -48,6 +48,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
InitializeComponent();
this.MouseUtils_MouseJump_Panel.ViewModel = ViewModel;
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
public void RefreshEnabledState()

View File

@@ -47,6 +47,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
DataContext = ViewModel;
InitializeComponent();
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
private void OnConfigFileUpdate()

View File

@@ -23,6 +23,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
DispatcherQueue);
DataContext = ViewModel;
InitializeComponent();
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
public void RefreshEnabledState()

View File

@@ -40,6 +40,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
PowerLauncherSettings settings = SettingsRepository<PowerLauncherSettings>.GetInstance(settingsUtils)?.SettingsConfig;
ViewModel = new PowerLauncherViewModel(settings, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SendDefaultIPCMessageTimed, App.IsDarkTheme);
DataContext = ViewModel;
_ = Helper.GetFileWatcher(PowerLauncherSettings.ModuleName, "settings.json", () =>
{
if (Environment.TickCount < _lastIPCMessageSentTick + 500)
@@ -79,6 +80,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
searchTypePreferencesOptions.Add(Tuple.Create(loader.GetString("PowerLauncher_SearchTypePreference_ApplicationName"), "application_name"));
searchTypePreferencesOptions.Add(Tuple.Create(loader.GetString("PowerLauncher_SearchTypePreference_StringInApplication"), "string_in_application"));
searchTypePreferencesOptions.Add(Tuple.Create(loader.GetString("PowerLauncher_SearchTypePreference_ExecutableName"), "executable_name"));
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
private void OpenColorsSettings_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)

View File

@@ -23,6 +23,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
private void TextExtractor_ComboBox_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)

View File

@@ -141,6 +141,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
// NL moved navigation to general page to the moment when the window is first activated (to not make flyout window disappear)
// shellFrame.Navigate(typeof(GeneralPage));
IPCResponseHandleList.Add(ReceiveMessage);
Services.IPCResponseService.Instance.RegisterForIPC();
SetTitleBar();
if (_navViewParentLookup.Count > 0)

View File

@@ -20,6 +20,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
var settingsUtils = new SettingsUtils();
ViewModel = new ShortcutGuideViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<ShortcutGuideSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
private void OpenColorsSettings_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)

View File

@@ -19,6 +19,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ViewModel = new WorkspacesViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<WorkspacesSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
public void RefreshEnabledState()