CmdPal: Replace Tapped events with generic ones (#40640)

## Summary of the Pull Request
Click event on WinUI buttons handle more than just click and is more
versatile that Tapped event. When you tap a Button with a finger or
stylus, or press a left mouse button while the pointer is over it, the
button raises the Click event. If a button has keyboard focus, pressing
the Enter key or the Spacebar key also raises the Click event.

This PR also replaces the right-tapped event on items on the list page
with context menu handling, allowing other input gestures (such as
Shift+F10) to also display the context menu.

And finally, it adds a button to the status messages badge so that the
flyout can be opened using the keyboard.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #40616 
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Tested on desktop with keyboard and mouse (no cats), and SB2 with touch
and pen. Input gestures seem to work as intended.

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>
This commit is contained in:
Jiří Polášek
2025-07-29 11:58:39 +02:00
committed by GitHub
parent 801fad09ba
commit decb947283
7 changed files with 89 additions and 72 deletions

View File

@@ -76,44 +76,44 @@
<Grid <Grid
x:Name="IconRoot" x:Name="IconRoot"
Margin="8,0,0,0" Margin="3,0,-5,0"
Tapped="PageIcon_Tapped"
Visibility="{x:Bind CurrentPageViewModel.IsNested, Mode=OneWay}"> Visibility="{x:Bind CurrentPageViewModel.IsNested, Mode=OneWay}">
<InfoBadge Visibility="{x:Bind CurrentPageViewModel.HasStatusMessage, Mode=OneWay}" Value="{x:Bind CurrentPageViewModel.StatusMessages.Count, Mode=OneWay}" /> <Button
<Grid.ContextFlyout> x:Name="StatusMessagesButton"
<Flyout x:Name="StatusMessagesFlyout" Placement="TopEdgeAlignedLeft"> x:Uid="StatusMessagesButton"
<ItemsRepeater Padding="4"
x:Name="MessagesDropdown" Style="{StaticResource SubtleButtonStyle}"
Margin="-8" Visibility="{x:Bind CurrentPageViewModel.HasStatusMessage, Mode=OneWay}">
ItemsSource="{x:Bind CurrentPageViewModel.StatusMessages, Mode=OneWay}" <InfoBadge Value="{x:Bind CurrentPageViewModel.StatusMessages.Count, Mode=OneWay}" />
Layout="{StaticResource VerticalStackLayout}"> <Button.Flyout>
<ItemsRepeater.ItemTemplate> <Flyout x:Name="StatusMessagesFlyout" Placement="TopEdgeAlignedLeft">
<DataTemplate x:DataType="coreViewModels:StatusMessageViewModel"> <ItemsRepeater
<StackPanel x:Name="MessagesDropdown"
Grid.Row="0" Margin="-8"
Margin="0" ItemsSource="{x:Bind CurrentPageViewModel.StatusMessages, Mode=OneWay}"
HorizontalAlignment="Stretch" Layout="{StaticResource VerticalStackLayout}">
VerticalAlignment="Bottom" <ItemsRepeater.ItemTemplate>
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" <DataTemplate x:DataType="coreViewModels:StatusMessageViewModel">
CornerRadius="0"> <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Bottom">
<InfoBar <InfoBar
CornerRadius="{ThemeResource ControlCornerRadius}" CornerRadius="{ThemeResource ControlCornerRadius}"
IsClosable="False" IsClosable="False"
IsOpen="True" IsOpen="True"
Message="{x:Bind Message, Mode=OneWay}" Message="{x:Bind Message, Mode=OneWay}"
Severity="{x:Bind State, Mode=OneWay, Converter={StaticResource MessageStateToSeverityConverter}}" /> Severity="{x:Bind State, Mode=OneWay, Converter={StaticResource MessageStateToSeverityConverter}}" />
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
</ItemsRepeater.ItemTemplate> </ItemsRepeater.ItemTemplate>
</ItemsRepeater> </ItemsRepeater>
</Flyout> </Flyout>
</Grid.ContextFlyout> </Button.Flyout>
</Button>
</Grid> </Grid>
<Button <Button
x:Name="SettingsIconButton" x:Name="SettingsIconButton"
x:Uid="SettingsButton" x:Uid="SettingsButton"
Click="SettingsIcon_Clicked"
Style="{StaticResource SubtleButtonStyle}" Style="{StaticResource SubtleButtonStyle}"
Tapped="SettingsIcon_Tapped"
Visibility="{x:Bind CurrentPageViewModel.IsNested, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}"> Visibility="{x:Bind CurrentPageViewModel.IsNested, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon <FontIcon
@@ -146,8 +146,8 @@
x:Load="{x:Bind IsLoaded, Mode=OneWay}" x:Load="{x:Bind IsLoaded, Mode=OneWay}"
AutomationProperties.Name="{x:Bind ViewModel.PrimaryCommand.Name, Mode=OneWay}" AutomationProperties.Name="{x:Bind ViewModel.PrimaryCommand.Name, Mode=OneWay}"
Background="Transparent" Background="Transparent"
Click="PrimaryButton_Clicked"
Style="{StaticResource SubtleButtonStyle}" Style="{StaticResource SubtleButtonStyle}"
Tapped="PrimaryButton_Tapped"
Visibility="{x:Bind ViewModel.HasPrimaryCommand, Mode=OneWay}"> Visibility="{x:Bind ViewModel.HasPrimaryCommand, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock <TextBlock
@@ -169,8 +169,8 @@
Padding="6,4,4,4" Padding="6,4,4,4"
x:Load="{x:Bind IsLoaded, Mode=OneWay}" x:Load="{x:Bind IsLoaded, Mode=OneWay}"
AutomationProperties.Name="{x:Bind ViewModel.SecondaryCommand.Name, Mode=OneWay}" AutomationProperties.Name="{x:Bind ViewModel.SecondaryCommand.Name, Mode=OneWay}"
Click="SecondaryButton_Clicked"
Style="{StaticResource SubtleButtonStyle}" Style="{StaticResource SubtleButtonStyle}"
Tapped="SecondaryButton_Tapped"
Visibility="{x:Bind ViewModel.HasSecondaryCommand, Mode=OneWay}"> Visibility="{x:Bind ViewModel.HasSecondaryCommand, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock <TextBlock
@@ -200,8 +200,8 @@
x:Name="MoreCommandsButton" x:Name="MoreCommandsButton"
x:Uid="MoreCommandsButton" x:Uid="MoreCommandsButton"
Padding="4" Padding="4"
Click="MoreCommandsButton_Clicked"
Style="{StaticResource SubtleButtonStyle}" Style="{StaticResource SubtleButtonStyle}"
Tapped="MoreCommandsButton_Tapped"
ToolTipService.ToolTip="Ctrl+K" ToolTipService.ToolTip="Ctrl+K"
Visibility="{x:Bind ViewModel.ShouldShowContextMenu, Mode=OneWay}"> Visibility="{x:Bind ViewModel.ShouldShowContextMenu, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="8">

View File

@@ -114,34 +114,23 @@ public sealed partial class CommandBar : UserControl,
} }
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS has a tendency to delete XAML bound methods over-aggressively")] [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) private void PrimaryButton_Clicked(object sender, RoutedEventArgs e)
{ {
ViewModel.InvokePrimaryCommand(); ViewModel.InvokePrimaryCommand();
} }
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS has a tendency to delete XAML bound methods over-aggressively")] [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) private void SecondaryButton_Clicked(object sender, RoutedEventArgs e)
{ {
ViewModel.InvokeSecondaryCommand(); ViewModel.InvokeSecondaryCommand();
} }
private void PageIcon_Tapped(object sender, TappedRoutedEventArgs e) private void SettingsIcon_Clicked(object sender, RoutedEventArgs 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>(); WeakReferenceMessenger.Default.Send<OpenSettingsMessage>();
e.Handled = true;
} }
private void MoreCommandsButton_Tapped(object sender, TappedRoutedEventArgs e) private void MoreCommandsButton_Clicked(object sender, RoutedEventArgs e)
{ {
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(new OpenContextMenuMessage(null, null, null, ContextMenuFilterLocation.Bottom)); WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(new OpenContextMenuMessage(null, null, null, ContextMenuFilterLocation.Bottom));
} }

View File

@@ -113,13 +113,14 @@
<ListView <ListView
x:Name="ItemsList" x:Name="ItemsList"
Padding="0,2,0,0" Padding="0,2,0,0"
ContextCanceled="ItemsList_OnContextCanceled"
ContextRequested="ItemsList_OnContextRequested"
DoubleTapped="ItemsList_DoubleTapped" DoubleTapped="ItemsList_DoubleTapped"
IsDoubleTapEnabled="True" IsDoubleTapEnabled="True"
IsItemClickEnabled="True" IsItemClickEnabled="True"
ItemClick="ItemsList_ItemClick" ItemClick="ItemsList_ItemClick"
ItemTemplate="{StaticResource ListItemViewModelTemplate}" ItemTemplate="{StaticResource ListItemViewModelTemplate}"
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}" ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
RightTapped="ItemsList_RightTapped"
SelectionChanged="ItemsList_SelectionChanged"> SelectionChanged="ItemsList_SelectionChanged">
<ListView.ItemContainerTransitions> <ListView.ItemContainerTransitions>
<TransitionCollection /> <TransitionCollection />

View File

@@ -316,30 +316,51 @@ public sealed partial class ListPage : Page,
return null; return null;
} }
private void ItemsList_RightTapped(object sender, RightTappedRoutedEventArgs e) private void ItemsList_OnContextRequested(UIElement sender, ContextRequestedEventArgs e)
{ {
if (e.OriginalSource is FrameworkElement element && var (item, element) = e.OriginalSource switch
element.DataContext is ListItemViewModel item)
{ {
if (ItemsList.SelectedItem != item) // caused by keyboard shortcut (e.g. Context menu key or Shift+F10)
{ ListViewItem listViewItem => (ItemsList.ItemFromContainer(listViewItem) as ListItemViewModel, listViewItem),
ItemsList.SelectedItem = item;
}
ViewModel?.UpdateSelectedItemCommand.Execute(item); // caused by right-click on the ListViewItem
FrameworkElement { DataContext: ListItemViewModel itemViewModel } frameworkElement => (itemViewModel, frameworkElement),
var pos = e.GetPosition(element); _ => (null, null),
};
_ = DispatcherQueue.TryEnqueue( if (item == null || element == null)
() => {
{ return;
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(
new OpenContextMenuMessage(
element,
Microsoft.UI.Xaml.Controls.Primitives.FlyoutPlacementMode.BottomEdgeAlignedLeft,
pos,
ContextMenuFilterLocation.Top));
});
} }
if (ItemsList.SelectedItem != item)
{
ItemsList.SelectedItem = item;
}
ViewModel?.UpdateSelectedItemCommand.Execute(item);
if (!e.TryGetPosition(element, out var pos))
{
pos = new(0, element.ActualHeight);
}
_ = DispatcherQueue.TryEnqueue(
() =>
{
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(
new OpenContextMenuMessage(
element,
Microsoft.UI.Xaml.Controls.Primitives.FlyoutPlacementMode.BottomEdgeAlignedLeft,
pos,
ContextMenuFilterLocation.Top));
});
e.Handled = true;
}
private void ItemsList_OnContextCanceled(UIElement sender, RoutedEventArgs e)
{
_ = DispatcherQueue.TryEnqueue(() => WeakReferenceMessenger.Default.Send<CloseContextMenuMessage>());
} }
} }

View File

@@ -225,11 +225,11 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5" ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5"
Click="BackButton_Clicked"
Content="{ui:FontIcon Glyph=&#xE76B;, Content="{ui:FontIcon Glyph=&#xE76B;,
FontSize=14}" FontSize=14}"
FontSize="16" FontSize="16"
Style="{StaticResource SubtleButtonStyle}" Style="{StaticResource SubtleButtonStyle}"
Tapped="BackButton_Tapped"
Visibility="{x:Bind ViewModel.CurrentPage.IsNested, Mode=OneWay}"> Visibility="{x:Bind ViewModel.CurrentPage.IsNested, Mode=OneWay}">
<animations:Implicit.ShowAnimations> <animations:Implicit.ShowAnimations>
<animations:OpacityAnimation <animations:OpacityAnimation

View File

@@ -413,7 +413,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
} }
} }
private void BackButton_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e) => WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new()); private void BackButton_Clicked(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) => WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
private void RootFrame_Navigated(object sender, Microsoft.UI.Xaml.Navigation.NavigationEventArgs e) private void RootFrame_Navigated(object sender, Microsoft.UI.Xaml.Navigation.NavigationEventArgs e)
{ {

View File

@@ -428,4 +428,10 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<data name="Settings_ExtensionPage_Alias_ToggleSwitch.OffContent" xml:space="preserve"> <data name="Settings_ExtensionPage_Alias_ToggleSwitch.OffContent" xml:space="preserve">
<value>Indirect</value> <value>Indirect</value>
</data> </data>
<data name="StatusMessagesButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Show status messages</value>
</data>
<data name="StatusMessagesButton.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Show status messages</value>
</data>
</root> </root>