Files
PowerToys/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml.cs
Michael Jolley 6642c805b7 Context menu cleanup (#40584)
Addressing items in #40583

- [x] When navigating the context menu with the up/down keys, separators
should not be selectable.
- [x] [For context items with a super long title, we need to trim those
with an ellipsis. Ideally, we'd show a tooltip for just those
items.](https://github.com/microsoft/PowerToys/issues/40313)
- [x] [Context menu search bar text size doesn't update after changing
system text size](https://github.com/microsoft/PowerToys/issues/39648)
- [x] Weird "kick out" on first context menu item
- [x] [Primary button doesn't work if the command has more items (fix
regression)](https://github.com/microsoft/PowerToys/issues/40624)

Example of long context menu item titles with tooltips: (@niels9001,
look okay?)


https://github.com/user-attachments/assets/fc0a4034-9c22-48ee-a3f0-44fcc2f294a6

closes #40624
2025-07-16 06:25:24 -05:00

155 lines
5.3 KiB
C#

// 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.Messaging;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Views;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Windows.System;
namespace Microsoft.CmdPal.UI.Controls;
public sealed partial class CommandBar : UserControl,
IRecipient<OpenContextMenuMessage>,
IRecipient<CloseContextMenuMessage>,
IRecipient<TryCommandKeybindingMessage>,
ICurrentPageAware
{
public CommandBarViewModel ViewModel { get; } = new();
public PageViewModel? CurrentPageViewModel
{
get => (PageViewModel?)GetValue(CurrentPageViewModelProperty);
set => SetValue(CurrentPageViewModelProperty, value);
}
// Using a DependencyProperty as the backing store for CurrentPage. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentPageViewModelProperty =
DependencyProperty.Register(nameof(CurrentPageViewModel), typeof(PageViewModel), typeof(CommandBar), new PropertyMetadata(null));
public CommandBar()
{
this.InitializeComponent();
// RegisterAll isn't AOT compatible
WeakReferenceMessenger.Default.Register<OpenContextMenuMessage>(this);
WeakReferenceMessenger.Default.Register<CloseContextMenuMessage>(this);
WeakReferenceMessenger.Default.Register<TryCommandKeybindingMessage>(this);
}
public void Receive(OpenContextMenuMessage message)
{
if (!ViewModel.ShouldShowContextMenu)
{
return;
}
if (message.Element == null)
{
_ = DispatcherQueue.TryEnqueue(
() =>
{
ContextMenuFlyout.ShowAt(
MoreCommandsButton,
new FlyoutShowOptions()
{
ShowMode = FlyoutShowMode.Standard,
Placement = FlyoutPlacementMode.TopEdgeAlignedRight,
});
});
}
else
{
_ = DispatcherQueue.TryEnqueue(
() =>
{
ContextMenuFlyout.ShowAt(
message.Element!,
new FlyoutShowOptions()
{
ShowMode = FlyoutShowMode.Standard,
Placement = (FlyoutPlacementMode)message.FlyoutPlacementMode!,
Position = message.Point,
});
});
}
}
public void Receive(CloseContextMenuMessage message)
{
if (ContextMenuFlyout.IsOpen)
{
ContextMenuFlyout.Hide();
}
}
public void Receive(TryCommandKeybindingMessage msg)
{
if (!ViewModel.ShouldShowContextMenu)
{
return;
}
var result = ViewModel?.CheckKeybinding(msg.Ctrl, msg.Alt, msg.Shift, msg.Win, msg.Key);
if (result == ContextKeybindingResult.Hide)
{
msg.Handled = true;
}
else if (result == ContextKeybindingResult.KeepOpen)
{
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(new OpenContextMenuMessage(null, null, null, ContextMenuFilterLocation.Bottom));
msg.Handled = true;
}
else if (result == ContextKeybindingResult.Unhandled)
{
msg.Handled = false;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS has a tendency to delete XAML bound methods over-aggressively")]
private void PrimaryButton_Tapped(object sender, TappedRoutedEventArgs e)
{
ViewModel.InvokePrimaryCommand();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS has a tendency to delete XAML bound methods over-aggressively")]
private void SecondaryButton_Tapped(object sender, TappedRoutedEventArgs e)
{
ViewModel.InvokeSecondaryCommand();
}
private void PageIcon_Tapped(object sender, TappedRoutedEventArgs e)
{
if (CurrentPageViewModel?.StatusMessages.Count > 0)
{
StatusMessagesFlyout.ShowAt(
placementTarget: IconRoot,
showOptions: new FlyoutShowOptions() { ShowMode = FlyoutShowMode.Standard });
}
}
private void SettingsIcon_Tapped(object sender, TappedRoutedEventArgs e)
{
WeakReferenceMessenger.Default.Send<OpenSettingsMessage>();
e.Handled = true;
}
private void MoreCommandsButton_Tapped(object sender, TappedRoutedEventArgs e)
{
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(new OpenContextMenuMessage(null, null, null, ContextMenuFilterLocation.Bottom));
}
private void ContextMenuFlyout_Opened(object sender, object e)
{
// We need to wait until our flyout is opened to try and toss focus
// at its search box. The control isn't in the UI tree before that
ContextControl.FocusSearchBox();
}
}