From 05218e8af6bfc24709dba8ca184e346b81bb3a36 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 17 Apr 2025 06:13:11 -0500 Subject: [PATCH] Add the list item requested shortcuts back (#38573) * [x] Re-adds the context menu shortcut text * [x] Hooks up the keybindings to the search box so that you can just press the keys while you have an item selected, and do a context command * [x] Hook these keybindings up to the context flyout itself * [x] Adds a sample for testing Solves #38271 --- .../CommandBarViewModel.cs | 29 +++++- .../CommandContextItemViewModel.cs | 7 ++ .../CommandItemViewModel.cs | 17 ++++ .../ListViewModel.cs | 2 + .../Messages/PerformCommandMessage.cs | 6 ++ .../Messages/UpdateItemKeybindingsMessage.cs | 9 ++ .../Controls/CommandBar.xaml | 5 +- .../Controls/CommandBar.xaml.cs | 22 +++++ .../Controls/SearchBar.xaml.cs | 28 +++++- .../Pages/ShellPage.xaml.cs | 4 +- .../SamplePagesExtension/NativeMethods.txt | 3 + .../Pages/SampleListPage.cs | 94 ++++++++++++++++++- .../SamplePagesExtension.csproj | 6 ++ .../KeyChordHelpers.cs | 20 +++- 14 files changed, 243 insertions(+), 9 deletions(-) create mode 100644 src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateItemKeybindingsMessage.cs create mode 100644 src/modules/cmdpal/ext/SamplePagesExtension/NativeMethods.txt diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandBarViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandBarViewModel.cs index 9b5be8a973..420f41f49f 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandBarViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandBarViewModel.cs @@ -7,11 +7,15 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using Microsoft.CmdPal.UI.ViewModels.Messages; +using Microsoft.CommandPalette.Extensions; +using Microsoft.CommandPalette.Extensions.Toolkit; +using Windows.System; namespace Microsoft.CmdPal.UI.ViewModels; public partial class CommandBarViewModel : ObservableObject, - IRecipient + IRecipient, + IRecipient { public ICommandBarContext? SelectedItem { @@ -49,13 +53,18 @@ public partial class CommandBarViewModel : ObservableObject, [ObservableProperty] public partial ObservableCollection ContextCommands { get; set; } = []; + private Dictionary? _contextKeybindings; + public CommandBarViewModel() { WeakReferenceMessenger.Default.Register(this); + WeakReferenceMessenger.Default.Register(this); } public void Receive(UpdateCommandBarMessage message) => SelectedItem = message.ViewModel; + public void Receive(UpdateItemKeybindingsMessage message) => _contextKeybindings = message.Keys; + private void SetSelectedItem(ICommandBarContext? value) { if (value != null) @@ -131,4 +140,22 @@ public partial class CommandBarViewModel : ObservableObject, WeakReferenceMessenger.Default.Send(new(SecondaryCommand.Command.Model, SecondaryCommand.Model)); } } + + public bool CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key) + { + if (_contextKeybindings != null) + { + // Does the pressed key match any of the keybindings? + var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0); + if (_contextKeybindings.TryGetValue(pressedKeyChord, out var item)) + { + // TODO GH #245: This is a bit of a hack, but we need to make sure that the keybindings are updated before we send the message + // so that the correct item is activated. + WeakReferenceMessenger.Default.Send(new(item)); + return true; + } + } + + return false; + } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandContextItemViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandContextItemViewModel.cs index f3475fd964..dfbb1a982a 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandContextItemViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandContextItemViewModel.cs @@ -9,12 +9,16 @@ namespace Microsoft.CmdPal.UI.ViewModels; public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference context) : CommandItemViewModel(new(contextItem), context) { + private readonly KeyChord nullKeyChord = new(0, 0, 0); + public new ExtensionObject Model { get; } = new(contextItem); public bool IsCritical { get; private set; } public KeyChord? RequestedShortcut { get; private set; } + public bool HasRequestedShortcut => RequestedShortcut != null && (RequestedShortcut.Value != nullKeyChord); + public override void InitializeProperties() { if (IsInitialized) @@ -31,6 +35,9 @@ public partial class CommandContextItemViewModel(ICommandContextItem contextItem } IsCritical = contextItem.IsCritical; + + // I actually don't think this will ever actually be null, because + // KeyChord is a struct, which isn't nullable in WinRT if (contextItem.RequestedShortcut != null) { RequestedShortcut = new( diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandItemViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandItemViewModel.cs index 37d223b000..8634b63278 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandItemViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandItemViewModel.cs @@ -398,6 +398,23 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa base.SafeCleanup(); Initialized |= InitializedState.CleanedUp; } + + /// + /// Generates a mapping of key -> command item for this particular item's + /// MoreCommands. (This won't include the primary Command, but it will + /// include the secondary one). This map can be used to quickly check if a + /// shortcut key was pressed + /// + /// a dictionary of KeyChord -> Context commands, for all commands + /// that have a shortcut key set. + internal Dictionary Keybindings() + { + return MoreCommands + .Where(c => c.HasRequestedShortcut) + .ToDictionary( + c => c.RequestedShortcut ?? new KeyChord(0, 0, 0), + c => c); + } } [Flags] diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ListViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ListViewModel.cs index c448745743..b45ea08f54 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ListViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ListViewModel.cs @@ -344,6 +344,8 @@ public partial class ListViewModel : PageViewModel, IDisposable { WeakReferenceMessenger.Default.Send(new(item)); + WeakReferenceMessenger.Default.Send(new(item.Keybindings())); + if (ShowDetails && item.HasDetails) { WeakReferenceMessenger.Default.Send(new(item.Details)); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/PerformCommandMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/PerformCommandMessage.cs index 9bc0c730e8..b0e5e4829a 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/PerformCommandMessage.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/PerformCommandMessage.cs @@ -51,6 +51,12 @@ public record PerformCommandMessage Context = context.Unsafe; } + public PerformCommandMessage(CommandContextItemViewModel contextCommand) + { + Command = contextCommand.Command.Model; + Context = contextCommand.Model.Unsafe; + } + public PerformCommandMessage(ConfirmResultViewModel vm) { Command = vm.PrimaryCommand.Model; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateItemKeybindingsMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateItemKeybindingsMessage.cs new file mode 100644 index 0000000000..2054d3d8fd --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateItemKeybindingsMessage.cs @@ -0,0 +1,9 @@ +// 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 Microsoft.CommandPalette.Extensions; + +namespace Microsoft.CmdPal.UI.ViewModels.Messages; + +public record UpdateItemKeybindingsMessage(Dictionary? Keys); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml index 9f7b4d4071..4a692fcc20 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml @@ -53,14 +53,14 @@ Grid.Column="1" VerticalAlignment="Center" Text="{x:Bind Title, Mode=OneWay}" /> - + Text="{x:Bind RequestedShortcut, Mode=OneWay, Converter={StaticResource KeyChordToStringConverter}}" /> @@ -263,6 +263,7 @@ ItemClick="CommandsDropdown_ItemClick" ItemTemplate="{StaticResource ContextMenuViewModelTemplate}" ItemsSource="{x:Bind ViewModel.ContextCommands, Mode=OneWay}" + KeyDown="CommandsDropdown_KeyDown" SelectionMode="None">