Add the list item requested shortcuts back (#38573)
Some checks are pending
Spell checking / Check Spelling (push) Waiting to run
Spell checking / Report (Push) (push) Blocked by required conditions
Spell checking / Report (PR) (push) Blocked by required conditions
Spell checking / Update PR (push) Waiting to run

* [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
This commit is contained in:
Mike Griese
2025-04-17 06:13:11 -05:00
committed by GitHub
parent 6cf73ce839
commit 05218e8af6
14 changed files with 243 additions and 9 deletions

View File

@@ -7,11 +7,15 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.ViewModels.Messages; using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.System;
namespace Microsoft.CmdPal.UI.ViewModels; namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandBarViewModel : ObservableObject, public partial class CommandBarViewModel : ObservableObject,
IRecipient<UpdateCommandBarMessage> IRecipient<UpdateCommandBarMessage>,
IRecipient<UpdateItemKeybindingsMessage>
{ {
public ICommandBarContext? SelectedItem public ICommandBarContext? SelectedItem
{ {
@@ -49,13 +53,18 @@ public partial class CommandBarViewModel : ObservableObject,
[ObservableProperty] [ObservableProperty]
public partial ObservableCollection<CommandContextItemViewModel> ContextCommands { get; set; } = []; public partial ObservableCollection<CommandContextItemViewModel> ContextCommands { get; set; } = [];
private Dictionary<KeyChord, CommandContextItemViewModel>? _contextKeybindings;
public CommandBarViewModel() public CommandBarViewModel()
{ {
WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this); WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this);
WeakReferenceMessenger.Default.Register<UpdateItemKeybindingsMessage>(this);
} }
public void Receive(UpdateCommandBarMessage message) => SelectedItem = message.ViewModel; public void Receive(UpdateCommandBarMessage message) => SelectedItem = message.ViewModel;
public void Receive(UpdateItemKeybindingsMessage message) => _contextKeybindings = message.Keys;
private void SetSelectedItem(ICommandBarContext? value) private void SetSelectedItem(ICommandBarContext? value)
{ {
if (value != null) if (value != null)
@@ -131,4 +140,22 @@ public partial class CommandBarViewModel : ObservableObject,
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(SecondaryCommand.Command.Model, SecondaryCommand.Model)); WeakReferenceMessenger.Default.Send<PerformCommandMessage>(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<PerformCommandMessage>(new(item));
return true;
}
}
return false;
}
} }

View File

@@ -9,12 +9,16 @@ namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context) : CommandItemViewModel(new(contextItem), context) public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context) : CommandItemViewModel(new(contextItem), context)
{ {
private readonly KeyChord nullKeyChord = new(0, 0, 0);
public new ExtensionObject<ICommandContextItem> Model { get; } = new(contextItem); public new ExtensionObject<ICommandContextItem> Model { get; } = new(contextItem);
public bool IsCritical { get; private set; } public bool IsCritical { get; private set; }
public KeyChord? RequestedShortcut { get; private set; } public KeyChord? RequestedShortcut { get; private set; }
public bool HasRequestedShortcut => RequestedShortcut != null && (RequestedShortcut.Value != nullKeyChord);
public override void InitializeProperties() public override void InitializeProperties()
{ {
if (IsInitialized) if (IsInitialized)
@@ -31,6 +35,9 @@ public partial class CommandContextItemViewModel(ICommandContextItem contextItem
} }
IsCritical = contextItem.IsCritical; 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) if (contextItem.RequestedShortcut != null)
{ {
RequestedShortcut = new( RequestedShortcut = new(

View File

@@ -398,6 +398,23 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
base.SafeCleanup(); base.SafeCleanup();
Initialized |= InitializedState.CleanedUp; Initialized |= InitializedState.CleanedUp;
} }
/// <summary>
/// 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
/// </summary>
/// <returns>a dictionary of KeyChord -> Context commands, for all commands
/// that have a shortcut key set.</returns>
internal Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
{
return MoreCommands
.Where(c => c.HasRequestedShortcut)
.ToDictionary(
c => c.RequestedShortcut ?? new KeyChord(0, 0, 0),
c => c);
}
} }
[Flags] [Flags]

View File

@@ -344,6 +344,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
{ {
WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(item)); WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(item));
WeakReferenceMessenger.Default.Send<UpdateItemKeybindingsMessage>(new(item.Keybindings()));
if (ShowDetails && item.HasDetails) if (ShowDetails && item.HasDetails)
{ {
WeakReferenceMessenger.Default.Send<ShowDetailsMessage>(new(item.Details)); WeakReferenceMessenger.Default.Send<ShowDetailsMessage>(new(item.Details));

View File

@@ -51,6 +51,12 @@ public record PerformCommandMessage
Context = context.Unsafe; Context = context.Unsafe;
} }
public PerformCommandMessage(CommandContextItemViewModel contextCommand)
{
Command = contextCommand.Command.Model;
Context = contextCommand.Model.Unsafe;
}
public PerformCommandMessage(ConfirmResultViewModel vm) public PerformCommandMessage(ConfirmResultViewModel vm)
{ {
Command = vm.PrimaryCommand.Model; Command = vm.PrimaryCommand.Model;

View File

@@ -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<KeyChord, CommandContextItemViewModel>? Keys);

View File

@@ -53,14 +53,14 @@
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Bind Title, Mode=OneWay}" /> Text="{x:Bind Title, Mode=OneWay}" />
<!--<TextBlock <TextBlock
Grid.Column="2" Grid.Column="2"
Margin="16,0,0,0" Margin="16,0,0,0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="{ThemeResource MenuFlyoutItemKeyboardAcceleratorTextForeground}" Foreground="{ThemeResource MenuFlyoutItemKeyboardAcceleratorTextForeground}"
Style="{StaticResource CaptionTextBlockStyle}" Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind RequestedShortcut, Mode=OneWay, Converter={StaticResource KeyChordToStringConverter}}" />--> Text="{x:Bind RequestedShortcut, Mode=OneWay, Converter={StaticResource KeyChordToStringConverter}}" />
</Grid> </Grid>
</DataTemplate> </DataTemplate>
@@ -263,6 +263,7 @@
ItemClick="CommandsDropdown_ItemClick" ItemClick="CommandsDropdown_ItemClick"
ItemTemplate="{StaticResource ContextMenuViewModelTemplate}" ItemTemplate="{StaticResource ContextMenuViewModelTemplate}"
ItemsSource="{x:Bind ViewModel.ContextCommands, Mode=OneWay}" ItemsSource="{x:Bind ViewModel.ContextCommands, Mode=OneWay}"
KeyDown="CommandsDropdown_KeyDown"
SelectionMode="None"> SelectionMode="None">
<ListView.ItemContainerStyle> <ListView.ItemContainerStyle>
<Style BasedOn="{StaticResource DefaultListViewItemStyle}" TargetType="ListViewItem"> <Style BasedOn="{StaticResource DefaultListViewItemStyle}" TargetType="ListViewItem">

View File

@@ -6,10 +6,13 @@ using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.ViewModels; using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages; using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.Views; using Microsoft.CmdPal.UI.Views;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Input;
using Windows.System;
using Windows.UI.Core;
namespace Microsoft.CmdPal.UI.Controls; namespace Microsoft.CmdPal.UI.Controls;
@@ -89,4 +92,23 @@ public sealed partial class CommandBar : UserControl,
MoreCommandsButton.Flyout.Hide(); MoreCommandsButton.Flyout.Hide();
} }
} }
private void CommandsDropdown_KeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Handled)
{
return;
}
var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
var altPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);
var shiftPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down);
var winPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.LeftWindows).HasFlag(CoreVirtualKeyStates.Down) ||
InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.RightWindows).HasFlag(CoreVirtualKeyStates.Down);
if (ViewModel?.CheckKeybinding(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key) ?? false)
{
e.Handled = true;
}
}
} }

View File

@@ -8,6 +8,8 @@ using CommunityToolkit.WinUI;
using Microsoft.CmdPal.UI.ViewModels; using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages; using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.Views; using Microsoft.CmdPal.UI.Views;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.UI.Dispatching; using Microsoft.UI.Dispatching;
using Microsoft.UI.Input; using Microsoft.UI.Input;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
@@ -21,6 +23,7 @@ namespace Microsoft.CmdPal.UI.Controls;
public sealed partial class SearchBar : UserControl, public sealed partial class SearchBar : UserControl,
IRecipient<GoHomeMessage>, IRecipient<GoHomeMessage>,
IRecipient<FocusSearchBoxMessage>, IRecipient<FocusSearchBoxMessage>,
IRecipient<UpdateItemKeybindingsMessage>,
ICurrentPageAware ICurrentPageAware
{ {
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread(); private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
@@ -31,6 +34,8 @@ public sealed partial class SearchBar : UserControl,
private readonly DispatcherQueueTimer _debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); private readonly DispatcherQueueTimer _debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer();
private bool _isBackspaceHeld; private bool _isBackspaceHeld;
private Dictionary<KeyChord, CommandContextItemViewModel>? _keyBindings;
public PageViewModel? CurrentPageViewModel public PageViewModel? CurrentPageViewModel
{ {
get => (PageViewModel?)GetValue(CurrentPageViewModelProperty); get => (PageViewModel?)GetValue(CurrentPageViewModelProperty);
@@ -69,6 +74,7 @@ public sealed partial class SearchBar : UserControl,
this.InitializeComponent(); this.InitializeComponent();
WeakReferenceMessenger.Default.Register<GoHomeMessage>(this); WeakReferenceMessenger.Default.Register<GoHomeMessage>(this);
WeakReferenceMessenger.Default.Register<FocusSearchBoxMessage>(this); WeakReferenceMessenger.Default.Register<FocusSearchBoxMessage>(this);
WeakReferenceMessenger.Default.Register<UpdateItemKeybindingsMessage>(this);
} }
public void ClearSearch() public void ClearSearch()
@@ -105,7 +111,9 @@ public sealed partial class SearchBar : UserControl,
var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down); var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
var altPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down); var altPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);
var shiftPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down);
var winPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.LeftWindows).HasFlag(CoreVirtualKeyStates.Down) ||
InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.RightWindows).HasFlag(CoreVirtualKeyStates.Down);
if (ctrlPressed && e.Key == VirtualKey.Enter) if (ctrlPressed && e.Key == VirtualKey.Enter)
{ {
// ctrl+enter // ctrl+enter
@@ -164,6 +172,19 @@ public sealed partial class SearchBar : UserControl,
{ {
WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new()); WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
} }
if (_keyBindings != null)
{
// Does the pressed key match any of the keybindings?
var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrlPressed, altPressed, shiftPressed, winPressed, (int)e.Key, 0);
if (_keyBindings.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<PerformCommandMessage>(new(item));
e.Handled = true;
}
}
} }
private void FilterBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e) private void FilterBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
@@ -282,4 +303,9 @@ public sealed partial class SearchBar : UserControl,
public void Receive(GoHomeMessage message) => ClearSearch(); public void Receive(GoHomeMessage message) => ClearSearch();
public void Receive(FocusSearchBoxMessage message) => this.Focus(Microsoft.UI.Xaml.FocusState.Programmatic); public void Receive(FocusSearchBoxMessage message) => this.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
public void Receive(UpdateItemKeybindingsMessage message)
{
_keyBindings = message.Keys;
}
} }

View File

@@ -187,6 +187,8 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null)); WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null));
WeakReferenceMessenger.Default.Send<UpdateItemKeybindingsMessage>(new(null));
var isMainPage = command is MainListPage; var isMainPage = command is MainListPage;
// Construct our ViewModel of the appropriate type and pass it the UI Thread context. // Construct our ViewModel of the appropriate type and pass it the UI Thread context.
@@ -427,8 +429,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
} }
_settingsWindow.Activate(); _settingsWindow.Activate();
WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null));
}); });
} }

View File

@@ -0,0 +1,3 @@
GetForegroundWindow
GetWindowTextLength
GetWindowText

View File

@@ -4,6 +4,8 @@
using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit; using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.System;
using Windows.Win32;
namespace SamplePagesExtension; namespace SamplePagesExtension;
@@ -65,6 +67,68 @@ internal sealed partial class SampleListPage : ListPage
Subtitle = "and I'll take you to a page with markdown content", Subtitle = "and I'll take you to a page with markdown content",
Tags = [new Tag("Sample Tag")], Tags = [new Tag("Sample Tag")],
}, },
new ListItem(
new AnonymousCommand(() =>
{
var t = new ToastStatusMessage(new StatusMessage()
{
Message = "Primary command invoked",
State = MessageState.Info,
});
t.Show();
})
{
Result = CommandResult.KeepOpen(),
Icon = new IconInfo("\uE712"),
})
{
Title = "You can add context menu items too. Press Ctrl+k",
Subtitle = "Try pressing Ctrl+1 with me selected",
Icon = new IconInfo("\uE712"),
MoreCommands = [
new CommandContextItem(
new AnonymousCommand(() =>
{
var t = new ToastStatusMessage(new StatusMessage()
{
Message = "Secondary command invoked",
State = MessageState.Warning,
});
t.Show();
})
{
Name = "Secondary command",
Icon = new IconInfo("\uF147"), // Dial 2
Result = CommandResult.KeepOpen(),
})
{
Title = "I'm a second command",
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.Number1),
},
new CommandContextItem(
new AnonymousCommand(() =>
{
var t = new ToastStatusMessage(new StatusMessage()
{
Message = "Third command invoked",
State = MessageState.Error,
});
t.Show();
})
{
Name = "Do it",
Icon = new IconInfo("\uF148"), // dial 3
Result = CommandResult.KeepOpen(),
})
{
Title = "A third command too",
Icon = new IconInfo("\uF148"),
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.Number2),
}
],
},
new ListItem(new SendMessageCommand()) new ListItem(new SendMessageCommand())
{ {
Title = "I send lots of messages", Title = "I send lots of messages",
@@ -91,7 +155,35 @@ internal sealed partial class SampleListPage : ListPage
}) })
{ {
Title = "Confirm twice before doing something", Title = "Confirm twice before doing something",
} },
new ListItem(
new AnonymousCommand(() =>
{
var fg = PInvoke.GetForegroundWindow();
var bufferSize = PInvoke.GetWindowTextLength(fg) + 1;
unsafe
{
fixed (char* windowNameChars = new char[bufferSize])
{
if (PInvoke.GetWindowText(fg, windowNameChars, bufferSize) == 0)
{
var emptyToast = new ToastStatusMessage(new StatusMessage() { Message = "FG Window didn't have a title", State = MessageState.Warning });
emptyToast.Show();
}
var windowName = new string(windowNameChars);
var nameToast = new ToastStatusMessage(new StatusMessage() { Message = $"FG Window is {windowName}", State = MessageState.Success });
nameToast.Show();
}
}
})
{
Result = CommandResult.KeepOpen(),
})
{
Title = "Get the name of the Foreground window",
},
]; ];
} }
} }

View File

@@ -33,6 +33,12 @@
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" /> <ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<!-- <!--
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
Tools extension to be activated for this project even if the Windows App SDK Nuget Tools extension to be activated for this project even if the Windows App SDK Nuget

View File

@@ -2,14 +2,19 @@
// 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 Windows.Foundation;
using Windows.System; using Windows.System;
namespace Microsoft.CommandPalette.Extensions.Toolkit; namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class KeyChordHelpers public partial class KeyChordHelpers
{ {
public static KeyChord FromModifiers(bool ctrl, bool alt, bool shift, bool win, int vkey, int scanCode) public static KeyChord FromModifiers(
bool ctrl = false,
bool alt = false,
bool shift = false,
bool win = false,
int vkey = 0,
int scanCode = 0)
{ {
var modifiers = (ctrl ? VirtualKeyModifiers.Control : VirtualKeyModifiers.None) var modifiers = (ctrl ? VirtualKeyModifiers.Control : VirtualKeyModifiers.None)
| (alt ? VirtualKeyModifiers.Menu : VirtualKeyModifiers.None) | (alt ? VirtualKeyModifiers.Menu : VirtualKeyModifiers.None)
@@ -18,4 +23,15 @@ public partial class KeyChordHelpers
; ;
return new(modifiers, vkey, scanCode); return new(modifiers, vkey, scanCode);
} }
public static KeyChord FromModifiers(
bool ctrl = false,
bool alt = false,
bool shift = false,
bool win = false,
VirtualKey vkey = VirtualKey.None,
int scanCode = 0)
{
return FromModifiers(ctrl, alt, shift, win, (int)vkey, scanCode);
}
} }