CmdPal: Fix Dock context menu following active item in Command Bar (#46420)

## Summary of the Pull Request

This PR decouples the Dock control context menu from the item selected
in the Shell Page list / Command Bar.

- Adds a new property to the context menu to control whether it should
react to messages like `UpdateCommandBarMessage`
- The `DockControl` context menu no longer follows those messages

Additional changes:

- Ensures the context menu for Dock-selected search box position
reflects the Dock position (when the Dock is at the bottom, the search
box is also at the bottom)
- Consistently displays the dock item context menu even for items with a
single context menu action (instead of showing the Dock menu, which was
confusing)

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

- [x] Closes: #46404
- [x] Closes: #45892
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **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
- [ ] **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
This commit is contained in:
Jiří Polášek
2026-03-28 00:38:09 +01:00
committed by GitHub
parent 9afa1ec71d
commit f686155d9b
9 changed files with 112 additions and 16 deletions

View File

@@ -52,6 +52,8 @@ public sealed partial class CommandBar : UserControl,
return;
}
ContextControl.PrepareForOpen(message.ContextMenuFilterLocation);
_ = DispatcherQueue.TryEnqueue(
() =>
{
@@ -67,6 +69,13 @@ public sealed partial class CommandBar : UserControl,
else
{
// This is invoked from a specific element
if (!(ContextControl.ViewModel.SelectedItem?.CanOpenContextMenu ?? false))
{
return;
}
ContextControl.PrepareForOpen(message.ContextMenuFilterLocation);
_ = DispatcherQueue.TryEnqueue(
() =>
{

View File

@@ -18,19 +18,32 @@ using Windows.System;
namespace Microsoft.CmdPal.UI.Controls;
public sealed partial class ContextMenu : UserControl,
IRecipient<OpenContextMenuMessage>,
IRecipient<UpdateCommandBarMessage>,
IRecipient<TryCommandKeybindingMessage>
{
public static readonly DependencyProperty ShowFilterBoxProperty =
DependencyProperty.Register(nameof(ShowFilterBox), typeof(bool), typeof(ContextMenu), new PropertyMetadata(true));
public static readonly DependencyProperty SubscribeToCommandBarProperty =
DependencyProperty.Register(nameof(SubscribeToCommandBar), typeof(bool), typeof(ContextMenu), new PropertyMetadata(true, OnSubscribeToCommandBarChanged));
public bool ShowFilterBox
{
get => (bool)GetValue(ShowFilterBoxProperty);
set => SetValue(ShowFilterBoxProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether this control listens to the command bar's
/// selection and keybinding messages. Set to false for standalone usage (e.g. dock)
/// where the caller manages selection and opening directly.
/// </summary>
public bool SubscribeToCommandBar
{
get => (bool)GetValue(SubscribeToCommandBarProperty);
set => SetValue(SubscribeToCommandBarProperty, value);
}
public ContextMenuViewModel ViewModel { get; }
public ContextMenu()
@@ -40,15 +53,57 @@ public sealed partial class ContextMenu : UserControl,
ViewModel = new ContextMenuViewModel(App.Current.Services.GetRequiredService<IFuzzyMatcherProvider>());
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
// RegisterAll isn't AOT compatible
WeakReferenceMessenger.Default.Register<OpenContextMenuMessage>(this);
WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this);
WeakReferenceMessenger.Default.Register<TryCommandKeybindingMessage>(this);
if (SubscribeToCommandBar)
{
HookCommandBar();
}
}
public void Receive(OpenContextMenuMessage message)
private static void OnSubscribeToCommandBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ViewModel.FilterOnTop = message.ContextMenuFilterLocation == ContextMenuFilterLocation.Top;
if (d is ContextMenu control)
{
if (e.NewValue is true)
{
control.HookCommandBar();
}
else
{
control.UnhookCommandBar();
}
}
}
private void HookCommandBar()
{
var messenger = WeakReferenceMessenger.Default;
if (!messenger.IsRegistered<UpdateCommandBarMessage>(this))
{
messenger.Register<UpdateCommandBarMessage>(this);
}
if (!messenger.IsRegistered<TryCommandKeybindingMessage>(this))
{
messenger.Register<TryCommandKeybindingMessage>(this);
}
ViewModel.HookCommandBar();
}
private void UnhookCommandBar()
{
var messenger = WeakReferenceMessenger.Default;
messenger.Unregister<UpdateCommandBarMessage>(this);
messenger.Unregister<TryCommandKeybindingMessage>(this);
ViewModel.UnhookCommandBar();
}
internal void PrepareForOpen(ContextMenuFilterLocation filterLocation)
{
ViewModel.FilterOnTop = filterLocation == ContextMenuFilterLocation.Top;
ViewModel.ResetContextMenu();
UpdateUiForStackChange();

View File

@@ -103,7 +103,7 @@
Opened="ContextMenuFlyout_Opened"
ShouldConstrainToRootBounds="False"
SystemBackdrop="{ThemeResource AcrylicBackgroundFillColorDefaultBackdrop}">
<cpcontrols:ContextMenu x:Name="ContextControl" />
<cpcontrols:ContextMenu x:Name="ContextControl" SubscribeToCommandBar="False" />
</Flyout>
<!-- Edit mode context menu for dock bands -->

View File

@@ -7,6 +7,7 @@ using System.Collections.Specialized;
using System.Runtime.InteropServices;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Dock;
using Microsoft.CmdPal.UI.ViewModels.Messages;
@@ -264,6 +265,13 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
}
}
private ContextMenuFilterLocation GetDockContextMenuFilterLocation()
{
return DockSide == DockSide.Bottom
? ContextMenuFilterLocation.Bottom
: ContextMenuFilterLocation.Top;
}
// Stores the band that was right-clicked for edit mode context menu
private DockBandViewModel? _editModeContextBand;
@@ -297,10 +305,11 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
}
// Normal mode - show the command context menu
if (item.HasMoreCommands)
if (item.CanOpenContextMenu)
{
ContextControl.ViewModel.SelectedItem = item;
ContextControl.ShowFilterBox = true;
ContextControl.PrepareForOpen(GetDockContextMenuFilterLocation());
PreparePopupForShow(ContextMenuFlyout, dockItem);
ContextMenuFlyout.ShowAt(
dockItem,
@@ -390,6 +399,7 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
{
ContextControl.ViewModel.SelectedItem = item;
ContextControl.ShowFilterBox = false;
ContextControl.PrepareForOpen(GetDockContextMenuFilterLocation());
PreparePopupForShow(ContextMenuFlyout, RootGrid);
ContextMenuFlyout.ShowAt(
this.RootGrid,