Context menu cleanup (#40584)
Some checks failed
Spell checking / Check Spelling (push) Has been cancelled
Spell checking / Update PR (push) Has been cancelled
Spell checking / Report (Push) (push) Has been cancelled
Spell checking / Report (PR) (push) Has been cancelled

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
This commit is contained in:
Michael Jolley
2025-07-16 06:25:24 -05:00
committed by GitHub
parent d5b15026ae
commit 6642c805b7
6 changed files with 106 additions and 58 deletions

View File

@@ -149,6 +149,7 @@ public partial class CommandBarViewModel : ObservableObject,
if (command.HasMoreCommands)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command.Command.Model, command.Model));
return ContextKeybindingResult.KeepOpen;
}
else

View File

@@ -22,15 +22,8 @@ public partial class ContextMenuViewModel : ObservableObject,
get => field;
set
{
if (field != null)
{
field.PropertyChanged -= SelectedItemPropertyChanged;
}
field = value;
SetSelectedItem(value);
OnPropertyChanged(nameof(SelectedItem));
UpdateContextItems();
}
}
@@ -68,33 +61,6 @@ public partial class ContextMenuViewModel : ObservableObject,
OnPropertyChanged(nameof(FilterOnTop));
}
private void SetSelectedItem(ICommandBarContext? value)
{
if (value != null)
{
value.PropertyChanged += SelectedItemPropertyChanged;
}
else
{
if (SelectedItem != null)
{
SelectedItem.PropertyChanged -= SelectedItemPropertyChanged;
}
}
UpdateContextItems();
}
private void SelectedItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(SelectedItem.HasMoreCommands):
UpdateContextItems();
break;
}
}
public void UpdateContextItems()
{
if (SelectedItem != null)

View File

@@ -6,13 +6,11 @@ using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Views;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Windows.System;
using Windows.UI.Core;
namespace Microsoft.CmdPal.UI.Controls;

View File

@@ -20,6 +20,7 @@
<UserControl.Resources>
<ResourceDictionary>
<cmdpalUI:KeyChordToStringConverter x:Key="KeyChordToStringConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<cmdpalUI:ContextItemTemplateSelector
x:Key="ContextItemTemplateSelector"
@@ -43,9 +44,18 @@
SourceKey="{x:Bind Icon}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
<TextBlock
x:Name="TitleTextBlock"
Grid.Column="1"
MaxWidth="200"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{x:Bind Title}" />
Text="{x:Bind Title}"
TextTrimming="WordEllipsis"
TextWrapping="NoWrap">
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind Title}" Visibility="{Binding IsTextTrimmed, ElementName=TitleTextBlock, Converter={StaticResource BoolToVisibilityConverter}}" />
</ToolTipService.ToolTip>
</TextBlock>
<TextBlock
Grid.Column="2"
Margin="16,0,0,0"
@@ -74,10 +84,19 @@
SourceKey="{x:Bind Icon}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
<TextBlock
x:Name="TitleTextBlock"
Grid.Column="1"
MaxWidth="200"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Style="{StaticResource ContextItemTitleTextBlockCriticalStyle}"
Text="{x:Bind Title}" />
Text="{x:Bind Title}"
TextTrimming="WordEllipsis"
TextWrapping="NoWrap">
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind Title}" Visibility="{Binding IsTextTrimmed, ElementName=TitleTextBlock, Converter={StaticResource BoolToVisibilityConverter}}" />
</ToolTipService.ToolTip>
</TextBlock>
<TextBlock
Grid.Column="2"
Margin="16,0,0,0"
@@ -129,6 +148,7 @@
x:Name="ContextFilterBox"
x:Uid="ContextFilterBox"
Margin="4"
IsTextScaleFactorEnabled="True"
KeyDown="ContextFilterBox_KeyDown"
PreviewKeyDown="ContextFilterBox_PreviewKeyDown"
TextChanged="ContextFilterBox_TextChanged" />

View File

@@ -178,34 +178,97 @@ public sealed partial class ContextMenu : UserControl,
{
if (e.Key == VirtualKey.Up)
{
// navigate previous
if (CommandsDropdown.SelectedIndex > 0)
{
CommandsDropdown.SelectedIndex--;
}
else
{
CommandsDropdown.SelectedIndex = CommandsDropdown.Items.Count - 1;
}
NavigateUp();
e.Handled = true;
}
else if (e.Key == VirtualKey.Down)
{
// navigate next
if (CommandsDropdown.SelectedIndex < CommandsDropdown.Items.Count - 1)
{
CommandsDropdown.SelectedIndex++;
}
else
{
CommandsDropdown.SelectedIndex = 0;
}
NavigateDown();
e.Handled = true;
}
}
private void NavigateUp()
{
var newIndex = CommandsDropdown.SelectedIndex;
if (CommandsDropdown.SelectedIndex > 0)
{
newIndex--;
while (
newIndex >= 0 &&
IsSeparator(CommandsDropdown.Items[newIndex]) &&
newIndex != CommandsDropdown.SelectedIndex)
{
newIndex--;
}
if (newIndex < 0)
{
newIndex = CommandsDropdown.Items.Count - 1;
while (
newIndex >= 0 &&
IsSeparator(CommandsDropdown.Items[newIndex]) &&
newIndex != CommandsDropdown.SelectedIndex)
{
newIndex--;
}
}
}
else
{
newIndex = CommandsDropdown.Items.Count - 1;
}
CommandsDropdown.SelectedIndex = newIndex;
}
private void NavigateDown()
{
var newIndex = CommandsDropdown.SelectedIndex;
if (CommandsDropdown.SelectedIndex == CommandsDropdown.Items.Count - 1)
{
newIndex = 0;
}
else
{
newIndex++;
while (
newIndex < CommandsDropdown.Items.Count &&
IsSeparator(CommandsDropdown.Items[newIndex]) &&
newIndex != CommandsDropdown.SelectedIndex)
{
newIndex++;
}
if (newIndex >= CommandsDropdown.Items.Count)
{
newIndex = 0;
while (
newIndex < CommandsDropdown.Items.Count &&
IsSeparator(CommandsDropdown.Items[newIndex]) &&
newIndex != CommandsDropdown.SelectedIndex)
{
newIndex++;
}
}
}
CommandsDropdown.SelectedIndex = newIndex;
}
private bool IsSeparator(object item)
{
return item is SeparatorContextItemViewModel;
}
private void UpdateUiForStackChange()
{
ContextFilterBox.Text = string.Empty;

View File

@@ -99,7 +99,7 @@ internal sealed partial class SampleListPage : ListPage
new CommandContextItem(
new ToastCommand("Nested B invoked") { Name = "Do it", Icon = new IconInfo("B") })
{
Title = "Nested B...",
Title = "Nested B with a really, really long title that should be trimmed",
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.B),
MoreCommands = [
new CommandContextItem(