/
githubmirror
/
PowerToys
Обзор
Документация
Войти
/
githubmirror
/
PowerToys
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandBarViewModel.cs
190 строк
6 KB
Jiří Polášek
CmdPal: Fix Dock context menu following active item in Command Bar (#46420)
28 мар 2026, 02:38
Не верифицирован
28 мар 2026, 02:38
f686155
Код
Авторство
О чём код?
// 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 CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.WinUI; using Microsoft.CmdPal.UI.ViewModels.Messages; using Microsoft.CommandPalette.Extensions.Toolkit; using Windows.System; using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue; using DispatcherQueueTimer = Microsoft.UI.Dispatching.DispatcherQueueTimer; namespace Microsoft.CmdPal.UI.ViewModels; public sealed partial class CommandBarViewModel : ObservableObject, IRecipient<UpdateCommandBarMessage> { private readonly DispatcherQueueTimer _debounceTimer; private volatile ICommandBarContext? _pendingSelectedItem; public ICommandBarContext? SelectedItem { get; set { // TODO: verify if we can safely return early // if (ReferenceEquals(field, value)) // { // return; // } if (field is not null) { field.PropertyChanged -= SelectedItemPropertyChanged; } field = value; if (field is not null) { PrimaryCommand = field.PrimaryCommand; field.PropertyChanged += SelectedItemPropertyChanged; } else { PrimaryCommand = null; } UpdateContextItems(); OnPropertyChanged(); } } [ObservableProperty] [NotifyPropertyChangedFor(nameof(HasPrimaryCommand))] public partial CommandItemViewModel? PrimaryCommand { get; set; } // TODO: PrimaryCommand.ShouldBeVisible is not observed, if it changes the bar won't refresh; // but at this moment CommandItemViewModel won't raise INPC for ShouldBeVisible anyway. public bool HasPrimaryCommand => PrimaryCommand is not null && PrimaryCommand.ShouldBeVisible; [ObservableProperty] [NotifyPropertyChangedFor(nameof(HasSecondaryCommand))] public partial CommandItemViewModel? SecondaryCommand { get; set; } public bool HasSecondaryCommand => SecondaryCommand is not null; [ObservableProperty] public partial bool ShouldShowContextMenu { get; set; } = false; [ObservableProperty] public partial PageViewModel? CurrentPage { get; set; } public CommandBarViewModel() { var dispatcherQueue = DispatcherQueue.GetForCurrentThread(); if (dispatcherQueue is null) { throw new InvalidOperationException("DispatcherQueue is not available for the current thread."); } _debounceTimer = dispatcherQueue.CreateTimer(); WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this); } public void Receive(UpdateCommandBarMessage message) { _pendingSelectedItem = message.ViewModel; // immediate: false is intentional — the timer tick always fires on the // dispatcher queue thread, which guarantees ApplyPendingSelectedItem // runs on the UI thread even if Receive is called from a background // thread. Using immediate: true would invoke the delegate synchronously // on the calling thread, bypassing the dispatcher. _debounceTimer.Debounce(ApplyPendingSelectedItem, TimeSpan.FromMilliseconds(50)); } private void ApplyPendingSelectedItem() { SelectedItem = _pendingSelectedItem; } private void SelectedItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) { switch (e.PropertyName) { case nameof(SelectedItem.CanOpenContextMenu): case nameof(SelectedItem.SecondaryCommand): UpdateContextItems(); break; } } private void UpdateContextItems() { if (SelectedItem is null) { SecondaryCommand = null; ShouldShowContextMenu = false; return; } SecondaryCommand = SelectedItem.SecondaryCommand; ShouldShowContextMenu = SelectedItem.CanOpenContextMenu; OnPropertyChanged(nameof(HasSecondaryCommand)); OnPropertyChanged(nameof(SecondaryCommand)); OnPropertyChanged(nameof(ShouldShowContextMenu)); } // InvokeItemCommand is what this will be in Xaml due to source generator // this comes in when an item in the list is tapped // [RelayCommand] public ContextKeybindingResult InvokeItem(CommandContextItemViewModel item) => PerformCommand(item); // this comes in when the primary button is tapped public void InvokePrimaryCommand() { PerformCommand(PrimaryCommand); } // this comes in when the secondary button is tapped public void InvokeSecondaryCommand() { PerformCommand(SecondaryCommand); } public ContextKeybindingResult CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key) { var keybindings = SelectedItem?.Keybindings(); if (keybindings is not null) { // Does the pressed key match any of the keybindings? var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0); if (keybindings.TryGetValue(pressedKeyChord, out var matchedItem)) { return matchedItem is not null ? PerformCommand(matchedItem) : ContextKeybindingResult.Unhandled; } } return ContextKeybindingResult.Unhandled; } private ContextKeybindingResult PerformCommand(CommandItemViewModel? command) { if (command is null) { return ContextKeybindingResult.Unhandled; } WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command.Command.Model, command.Model)); if (command.HasMoreCommands) { return ContextKeybindingResult.KeepOpen; } else { return ContextKeybindingResult.Hide; } } } public enum ContextKeybindingResult { Unhandled, Hide, KeepOpen, }