mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 01:36:31 +02:00
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:
@@ -2,7 +2,6 @@
|
||||
// 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 CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI;
|
||||
@@ -106,7 +105,8 @@ public sealed partial class CommandBarViewModel : ObservableObject,
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(SelectedItem.HasMoreCommands):
|
||||
case nameof(SelectedItem.CanOpenContextMenu):
|
||||
case nameof(SelectedItem.SecondaryCommand):
|
||||
UpdateContextItems();
|
||||
break;
|
||||
}
|
||||
@@ -122,9 +122,7 @@ public sealed partial class CommandBarViewModel : ObservableObject,
|
||||
}
|
||||
|
||||
SecondaryCommand = SelectedItem.SecondaryCommand;
|
||||
var moreCommands = SelectedItem.MoreCommands;
|
||||
|
||||
ShouldShowContextMenu = moreCommands.Count > 1 && SelectedItem.HasMoreCommands;
|
||||
ShouldShowContextMenu = SelectedItem.CanOpenContextMenu;
|
||||
|
||||
OnPropertyChanged(nameof(HasSecondaryCommand));
|
||||
OnPropertyChanged(nameof(SecondaryCommand));
|
||||
|
||||
@@ -86,6 +86,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
|
||||
public CommandItemViewModel? SecondaryCommand => _secondaryMoreCommand;
|
||||
|
||||
public bool CanOpenContextMenu => AllCommands.Any(item => item is CommandItemViewModel command && command.ShouldBeVisible);
|
||||
|
||||
public bool ShouldBeVisible => !string.IsNullOrEmpty(Name);
|
||||
|
||||
public bool HasTitle => !string.IsNullOrEmpty(Title);
|
||||
@@ -224,6 +226,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
UpdateProperty(nameof(MoreCommands));
|
||||
UpdateProperty(nameof(AllCommands));
|
||||
UpdateProperty(nameof(SecondaryCommand), nameof(SecondaryCommandName), nameof(HasMoreCommands));
|
||||
UpdateProperty(nameof(CanOpenContextMenu));
|
||||
UpdateProperty(nameof(IsSelectedInitialized));
|
||||
}
|
||||
|
||||
@@ -336,6 +339,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
UpdateProperty(nameof(Title));
|
||||
UpdateProperty(nameof(Icon));
|
||||
UpdateProperty(nameof(HasText));
|
||||
UpdateProperty(nameof(CanOpenContextMenu));
|
||||
break;
|
||||
|
||||
case nameof(Title):
|
||||
@@ -367,7 +371,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
|
||||
case nameof(model.MoreCommands):
|
||||
BuildAndInitMoreCommands();
|
||||
UpdateProperty(nameof(SecondaryCommand), nameof(SecondaryCommandName), nameof(HasMoreCommands), nameof(AllCommands));
|
||||
UpdateProperty(nameof(SecondaryCommand), nameof(SecondaryCommandName), nameof(HasMoreCommands), nameof(AllCommands), nameof(CanOpenContextMenu));
|
||||
|
||||
break;
|
||||
case nameof(DataPackage):
|
||||
@@ -395,6 +399,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
_itemTitle = model.Title;
|
||||
_titleCache.Invalidate();
|
||||
UpdateProperty(nameof(Title), nameof(Name));
|
||||
UpdateProperty(nameof(CanOpenContextMenu));
|
||||
|
||||
if (_defaultCommandContextItemViewModel is not null)
|
||||
{
|
||||
@@ -532,6 +537,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
UpdateProperty(nameof(SecondaryCommand));
|
||||
UpdateProperty(nameof(SecondaryCommandName));
|
||||
UpdateProperty(nameof(HasMoreCommands));
|
||||
UpdateProperty(nameof(CanOpenContextMenu));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -37,6 +37,8 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
|
||||
|
||||
public bool HasMoreCommands => _snapshot.SecondaryCommand is not null;
|
||||
|
||||
public bool CanOpenContextMenu => _snapshot.AllCommands.Any(item => item is CommandItemViewModel command && command.ShouldBeVisible);
|
||||
|
||||
public string SecondaryCommandName => _snapshot.SecondaryCommand?.Name ?? string.Empty;
|
||||
|
||||
public CommandItemViewModel? PrimaryCommand => _snapshot.PrimaryCommand;
|
||||
@@ -184,6 +186,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
|
||||
UpdateProperty(nameof(SecondaryCommandName));
|
||||
UpdateProperty(nameof(HasCommands));
|
||||
UpdateProperty(nameof(HasMoreCommands));
|
||||
UpdateProperty(nameof(CanOpenContextMenu));
|
||||
UpdateProperty(nameof(MoreCommands));
|
||||
UpdateProperty(nameof(AllCommands));
|
||||
DoOnUiThread(
|
||||
|
||||
@@ -47,7 +47,20 @@ public partial class ContextMenuViewModel : ObservableObject,
|
||||
public ContextMenuViewModel(IFuzzyMatcherProvider fuzzyMatcherProvider)
|
||||
{
|
||||
_fuzzyMatcherProvider = fuzzyMatcherProvider;
|
||||
WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this);
|
||||
}
|
||||
|
||||
public void HookCommandBar()
|
||||
{
|
||||
var messenger = WeakReferenceMessenger.Default;
|
||||
if (!messenger.IsRegistered<UpdateCommandBarMessage>(this))
|
||||
{
|
||||
messenger.Register<UpdateCommandBarMessage>(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnhookCommandBar()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Unregister<UpdateCommandBarMessage>(this);
|
||||
}
|
||||
|
||||
public void Receive(UpdateCommandBarMessage message)
|
||||
|
||||
@@ -22,6 +22,8 @@ public interface IContextMenuContext : INotifyPropertyChanged
|
||||
|
||||
public bool HasMoreCommands { get; }
|
||||
|
||||
public bool CanOpenContextMenu { get; }
|
||||
|
||||
public IReadOnlyList<IContextItemViewModel> AllCommands { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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(
|
||||
() =>
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user