mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +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.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using CommunityToolkit.WinUI;
|
using CommunityToolkit.WinUI;
|
||||||
@@ -106,7 +105,8 @@ public sealed partial class CommandBarViewModel : ObservableObject,
|
|||||||
{
|
{
|
||||||
switch (e.PropertyName)
|
switch (e.PropertyName)
|
||||||
{
|
{
|
||||||
case nameof(SelectedItem.HasMoreCommands):
|
case nameof(SelectedItem.CanOpenContextMenu):
|
||||||
|
case nameof(SelectedItem.SecondaryCommand):
|
||||||
UpdateContextItems();
|
UpdateContextItems();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -122,9 +122,7 @@ public sealed partial class CommandBarViewModel : ObservableObject,
|
|||||||
}
|
}
|
||||||
|
|
||||||
SecondaryCommand = SelectedItem.SecondaryCommand;
|
SecondaryCommand = SelectedItem.SecondaryCommand;
|
||||||
var moreCommands = SelectedItem.MoreCommands;
|
ShouldShowContextMenu = SelectedItem.CanOpenContextMenu;
|
||||||
|
|
||||||
ShouldShowContextMenu = moreCommands.Count > 1 && SelectedItem.HasMoreCommands;
|
|
||||||
|
|
||||||
OnPropertyChanged(nameof(HasSecondaryCommand));
|
OnPropertyChanged(nameof(HasSecondaryCommand));
|
||||||
OnPropertyChanged(nameof(SecondaryCommand));
|
OnPropertyChanged(nameof(SecondaryCommand));
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
|
|
||||||
public CommandItemViewModel? SecondaryCommand => _secondaryMoreCommand;
|
public CommandItemViewModel? SecondaryCommand => _secondaryMoreCommand;
|
||||||
|
|
||||||
|
public bool CanOpenContextMenu => AllCommands.Any(item => item is CommandItemViewModel command && command.ShouldBeVisible);
|
||||||
|
|
||||||
public bool ShouldBeVisible => !string.IsNullOrEmpty(Name);
|
public bool ShouldBeVisible => !string.IsNullOrEmpty(Name);
|
||||||
|
|
||||||
public bool HasTitle => !string.IsNullOrEmpty(Title);
|
public bool HasTitle => !string.IsNullOrEmpty(Title);
|
||||||
@@ -224,6 +226,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
UpdateProperty(nameof(MoreCommands));
|
UpdateProperty(nameof(MoreCommands));
|
||||||
UpdateProperty(nameof(AllCommands));
|
UpdateProperty(nameof(AllCommands));
|
||||||
UpdateProperty(nameof(SecondaryCommand), nameof(SecondaryCommandName), nameof(HasMoreCommands));
|
UpdateProperty(nameof(SecondaryCommand), nameof(SecondaryCommandName), nameof(HasMoreCommands));
|
||||||
|
UpdateProperty(nameof(CanOpenContextMenu));
|
||||||
UpdateProperty(nameof(IsSelectedInitialized));
|
UpdateProperty(nameof(IsSelectedInitialized));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,6 +339,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
UpdateProperty(nameof(Title));
|
UpdateProperty(nameof(Title));
|
||||||
UpdateProperty(nameof(Icon));
|
UpdateProperty(nameof(Icon));
|
||||||
UpdateProperty(nameof(HasText));
|
UpdateProperty(nameof(HasText));
|
||||||
|
UpdateProperty(nameof(CanOpenContextMenu));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nameof(Title):
|
case nameof(Title):
|
||||||
@@ -367,7 +371,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
|
|
||||||
case nameof(model.MoreCommands):
|
case nameof(model.MoreCommands):
|
||||||
BuildAndInitMoreCommands();
|
BuildAndInitMoreCommands();
|
||||||
UpdateProperty(nameof(SecondaryCommand), nameof(SecondaryCommandName), nameof(HasMoreCommands), nameof(AllCommands));
|
UpdateProperty(nameof(SecondaryCommand), nameof(SecondaryCommandName), nameof(HasMoreCommands), nameof(AllCommands), nameof(CanOpenContextMenu));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case nameof(DataPackage):
|
case nameof(DataPackage):
|
||||||
@@ -395,6 +399,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
_itemTitle = model.Title;
|
_itemTitle = model.Title;
|
||||||
_titleCache.Invalidate();
|
_titleCache.Invalidate();
|
||||||
UpdateProperty(nameof(Title), nameof(Name));
|
UpdateProperty(nameof(Title), nameof(Name));
|
||||||
|
UpdateProperty(nameof(CanOpenContextMenu));
|
||||||
|
|
||||||
if (_defaultCommandContextItemViewModel is not null)
|
if (_defaultCommandContextItemViewModel is not null)
|
||||||
{
|
{
|
||||||
@@ -532,6 +537,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
UpdateProperty(nameof(SecondaryCommand));
|
UpdateProperty(nameof(SecondaryCommand));
|
||||||
UpdateProperty(nameof(SecondaryCommandName));
|
UpdateProperty(nameof(SecondaryCommandName));
|
||||||
UpdateProperty(nameof(HasMoreCommands));
|
UpdateProperty(nameof(HasMoreCommands));
|
||||||
|
UpdateProperty(nameof(CanOpenContextMenu));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
|
|||||||
|
|
||||||
public bool HasMoreCommands => _snapshot.SecondaryCommand is not null;
|
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 string SecondaryCommandName => _snapshot.SecondaryCommand?.Name ?? string.Empty;
|
||||||
|
|
||||||
public CommandItemViewModel? PrimaryCommand => _snapshot.PrimaryCommand;
|
public CommandItemViewModel? PrimaryCommand => _snapshot.PrimaryCommand;
|
||||||
@@ -184,6 +186,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
|
|||||||
UpdateProperty(nameof(SecondaryCommandName));
|
UpdateProperty(nameof(SecondaryCommandName));
|
||||||
UpdateProperty(nameof(HasCommands));
|
UpdateProperty(nameof(HasCommands));
|
||||||
UpdateProperty(nameof(HasMoreCommands));
|
UpdateProperty(nameof(HasMoreCommands));
|
||||||
|
UpdateProperty(nameof(CanOpenContextMenu));
|
||||||
UpdateProperty(nameof(MoreCommands));
|
UpdateProperty(nameof(MoreCommands));
|
||||||
UpdateProperty(nameof(AllCommands));
|
UpdateProperty(nameof(AllCommands));
|
||||||
DoOnUiThread(
|
DoOnUiThread(
|
||||||
|
|||||||
@@ -47,7 +47,20 @@ public partial class ContextMenuViewModel : ObservableObject,
|
|||||||
public ContextMenuViewModel(IFuzzyMatcherProvider fuzzyMatcherProvider)
|
public ContextMenuViewModel(IFuzzyMatcherProvider fuzzyMatcherProvider)
|
||||||
{
|
{
|
||||||
_fuzzyMatcherProvider = 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)
|
public void Receive(UpdateCommandBarMessage message)
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ public interface IContextMenuContext : INotifyPropertyChanged
|
|||||||
|
|
||||||
public bool HasMoreCommands { get; }
|
public bool HasMoreCommands { get; }
|
||||||
|
|
||||||
|
public bool CanOpenContextMenu { get; }
|
||||||
|
|
||||||
public IReadOnlyList<IContextItemViewModel> AllCommands { get; }
|
public IReadOnlyList<IContextItemViewModel> AllCommands { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ public sealed partial class CommandBar : UserControl,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContextControl.PrepareForOpen(message.ContextMenuFilterLocation);
|
||||||
|
|
||||||
_ = DispatcherQueue.TryEnqueue(
|
_ = DispatcherQueue.TryEnqueue(
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
@@ -67,6 +69,13 @@ public sealed partial class CommandBar : UserControl,
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// This is invoked from a specific element
|
// This is invoked from a specific element
|
||||||
|
if (!(ContextControl.ViewModel.SelectedItem?.CanOpenContextMenu ?? false))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextControl.PrepareForOpen(message.ContextMenuFilterLocation);
|
||||||
|
|
||||||
_ = DispatcherQueue.TryEnqueue(
|
_ = DispatcherQueue.TryEnqueue(
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,19 +18,32 @@ using Windows.System;
|
|||||||
namespace Microsoft.CmdPal.UI.Controls;
|
namespace Microsoft.CmdPal.UI.Controls;
|
||||||
|
|
||||||
public sealed partial class ContextMenu : UserControl,
|
public sealed partial class ContextMenu : UserControl,
|
||||||
IRecipient<OpenContextMenuMessage>,
|
|
||||||
IRecipient<UpdateCommandBarMessage>,
|
IRecipient<UpdateCommandBarMessage>,
|
||||||
IRecipient<TryCommandKeybindingMessage>
|
IRecipient<TryCommandKeybindingMessage>
|
||||||
{
|
{
|
||||||
public static readonly DependencyProperty ShowFilterBoxProperty =
|
public static readonly DependencyProperty ShowFilterBoxProperty =
|
||||||
DependencyProperty.Register(nameof(ShowFilterBox), typeof(bool), typeof(ContextMenu), new PropertyMetadata(true));
|
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
|
public bool ShowFilterBox
|
||||||
{
|
{
|
||||||
get => (bool)GetValue(ShowFilterBoxProperty);
|
get => (bool)GetValue(ShowFilterBoxProperty);
|
||||||
set => SetValue(ShowFilterBoxProperty, value);
|
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 ContextMenuViewModel ViewModel { get; }
|
||||||
|
|
||||||
public ContextMenu()
|
public ContextMenu()
|
||||||
@@ -40,15 +53,57 @@ public sealed partial class ContextMenu : UserControl,
|
|||||||
ViewModel = new ContextMenuViewModel(App.Current.Services.GetRequiredService<IFuzzyMatcherProvider>());
|
ViewModel = new ContextMenuViewModel(App.Current.Services.GetRequiredService<IFuzzyMatcherProvider>());
|
||||||
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||||
|
|
||||||
// RegisterAll isn't AOT compatible
|
if (SubscribeToCommandBar)
|
||||||
WeakReferenceMessenger.Default.Register<OpenContextMenuMessage>(this);
|
{
|
||||||
WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this);
|
HookCommandBar();
|
||||||
WeakReferenceMessenger.Default.Register<TryCommandKeybindingMessage>(this);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
ViewModel.ResetContextMenu();
|
||||||
|
|
||||||
UpdateUiForStackChange();
|
UpdateUiForStackChange();
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
Opened="ContextMenuFlyout_Opened"
|
Opened="ContextMenuFlyout_Opened"
|
||||||
ShouldConstrainToRootBounds="False"
|
ShouldConstrainToRootBounds="False"
|
||||||
SystemBackdrop="{ThemeResource AcrylicBackgroundFillColorDefaultBackdrop}">
|
SystemBackdrop="{ThemeResource AcrylicBackgroundFillColorDefaultBackdrop}">
|
||||||
<cpcontrols:ContextMenu x:Name="ContextControl" />
|
<cpcontrols:ContextMenu x:Name="ContextControl" SubscribeToCommandBar="False" />
|
||||||
</Flyout>
|
</Flyout>
|
||||||
|
|
||||||
<!-- Edit mode context menu for dock bands -->
|
<!-- Edit mode context menu for dock bands -->
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Collections.Specialized;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
|
using Microsoft.CmdPal.UI.Messages;
|
||||||
using Microsoft.CmdPal.UI.ViewModels;
|
using Microsoft.CmdPal.UI.ViewModels;
|
||||||
using Microsoft.CmdPal.UI.ViewModels.Dock;
|
using Microsoft.CmdPal.UI.ViewModels.Dock;
|
||||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
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
|
// Stores the band that was right-clicked for edit mode context menu
|
||||||
private DockBandViewModel? _editModeContextBand;
|
private DockBandViewModel? _editModeContextBand;
|
||||||
|
|
||||||
@@ -297,10 +305,11 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal mode - show the command context menu
|
// Normal mode - show the command context menu
|
||||||
if (item.HasMoreCommands)
|
if (item.CanOpenContextMenu)
|
||||||
{
|
{
|
||||||
ContextControl.ViewModel.SelectedItem = item;
|
ContextControl.ViewModel.SelectedItem = item;
|
||||||
ContextControl.ShowFilterBox = true;
|
ContextControl.ShowFilterBox = true;
|
||||||
|
ContextControl.PrepareForOpen(GetDockContextMenuFilterLocation());
|
||||||
PreparePopupForShow(ContextMenuFlyout, dockItem);
|
PreparePopupForShow(ContextMenuFlyout, dockItem);
|
||||||
ContextMenuFlyout.ShowAt(
|
ContextMenuFlyout.ShowAt(
|
||||||
dockItem,
|
dockItem,
|
||||||
@@ -390,6 +399,7 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
|
|||||||
{
|
{
|
||||||
ContextControl.ViewModel.SelectedItem = item;
|
ContextControl.ViewModel.SelectedItem = item;
|
||||||
ContextControl.ShowFilterBox = false;
|
ContextControl.ShowFilterBox = false;
|
||||||
|
ContextControl.PrepareForOpen(GetDockContextMenuFilterLocation());
|
||||||
PreparePopupForShow(ContextMenuFlyout, RootGrid);
|
PreparePopupForShow(ContextMenuFlyout, RootGrid);
|
||||||
ContextMenuFlyout.ShowAt(
|
ContextMenuFlyout.ShowAt(
|
||||||
this.RootGrid,
|
this.RootGrid,
|
||||||
|
|||||||
Reference in New Issue
Block a user