CmdPal: Prevent PgUp/PgDown from selecting non-internactive items (#46439)

## Summary of the Pull Request

This PR prevents paging (<kbd>PgUp</kbd>/<kbd>PgDown</kbd>) in the item
list from selecting non-interactive items (such as separators or section
headers).

It adds `FindSelectableIndex` and `FindSelectableIndexForPageNavigation`
helper methods, which locate the next interactive item in the given
direction. These methods are used to guard paging navigation and to
consolidate related logic elsewhere.

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

- [x] Closes: #46283 
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **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
This commit is contained in:
Jiří Polášek
2026-03-24 20:15:44 +01:00
committed by GitHub
parent 84ce86c573
commit e2f611a7fc

View File

@@ -156,21 +156,7 @@ public sealed partial class ListPage : Page,
/// </summary> /// </summary>
private int GetFirstSelectableIndex() private int GetFirstSelectableIndex()
{ {
var items = ItemView.Items; return FindSelectableIndex(0, 1);
if (items is null || items.Count == 0)
{
return -1;
}
for (var i = 0; i < items.Count; i++)
{
if (!IsSeparator(items[i]))
{
return i;
}
}
return -1;
} }
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS is too aggressive at pruning methods bound in XAML")] [System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS is too aggressive at pruning methods bound in XAML")]
@@ -604,9 +590,44 @@ public sealed partial class ListPage : Page,
? Math.Min(itemCount - 1, currentIndex + Math.Max(1, itemsPerPage)) ? Math.Min(itemCount - 1, currentIndex + Math.Max(1, itemsPerPage))
: Math.Max(0, currentIndex - Math.Max(1, itemsPerPage)); : Math.Max(0, currentIndex - Math.Max(1, itemsPerPage));
targetIndex = FindSelectableIndexForPageNavigation(targetIndex, isPageDown);
if (targetIndex == -1)
{
return null;
}
return (currentIndex, targetIndex); return (currentIndex, targetIndex);
} }
private int FindSelectableIndex(int startIndex, int step)
{
var items = ItemView.Items;
var count = items.Count;
if (count == 0 || step == 0)
{
return -1;
}
startIndex = Math.Clamp(startIndex, 0, count - 1);
for (var i = startIndex; i >= 0 && i < count; i += step)
{
if (!IsSeparator(items[i]))
{
return i;
}
}
return -1;
}
private int FindSelectableIndexForPageNavigation(int targetIndex, bool isPageDown)
{
var step = isPageDown ? 1 : -1;
var index = FindSelectableIndex(targetIndex, step);
return index != -1 ? index : FindSelectableIndex(targetIndex - step, -step);
}
private static void OnViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) private static void OnViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ {
if (d is ListPage @this) if (d is ListPage @this)
@@ -1045,40 +1066,25 @@ public sealed partial class ListPage : Page,
/// </summary> /// </summary>
private void NavigateUp() private void NavigateUp()
{ {
var newIndex = ItemView.SelectedIndex; if (ItemView.Items.Count == 0)
if (ItemView.SelectedIndex > 0)
{ {
newIndex--; return;
while (
newIndex >= 0 &&
IsSeparator(ItemView.Items[newIndex]) &&
newIndex != ItemView.SelectedIndex)
{
newIndex--;
} }
if (newIndex < 0) var newIndex = ItemView.SelectedIndex > 0
{ ? FindSelectableIndex(ItemView.SelectedIndex - 1, -1)
newIndex = ItemView.Items.Count - 1; : -1;
while ( if (newIndex == -1)
newIndex >= 0 &&
IsSeparator(ItemView.Items[newIndex]) &&
newIndex != ItemView.SelectedIndex)
{ {
newIndex--; newIndex = FindSelectableIndex(ItemView.Items.Count - 1, -1);
}
}
}
else
{
newIndex = ItemView.Items.Count - 1;
} }
if (newIndex != -1)
{
ItemView.SelectedIndex = newIndex; ItemView.SelectedIndex = newIndex;
} }
}
private void Items_DragItemsStarting(object sender, DragItemsStartingEventArgs e) private void Items_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
{ {
@@ -1168,51 +1174,25 @@ public sealed partial class ListPage : Page,
/// </summary> /// </summary>
private void NavigateDown() private void NavigateDown()
{ {
var newIndex = ItemView.SelectedIndex; if (ItemView.Items.Count == 0)
if (ItemView.SelectedIndex == ItemView.Items.Count - 1)
{
newIndex = 0;
while (
newIndex < ItemView.Items.Count &&
IsSeparator(ItemView.Items[newIndex]))
{
newIndex++;
}
if (newIndex >= ItemView.Items.Count)
{ {
return; return;
} }
}
else
{
newIndex++;
while ( var newIndex = ItemView.SelectedIndex < ItemView.Items.Count - 1
newIndex < ItemView.Items.Count && ? FindSelectableIndex(ItemView.SelectedIndex + 1, 1)
IsSeparator(ItemView.Items[newIndex]) && : -1;
newIndex != ItemView.SelectedIndex)
if (newIndex == -1)
{ {
newIndex++; newIndex = FindSelectableIndex(0, 1);
} }
if (newIndex >= ItemView.Items.Count) if (newIndex != -1)
{ {
newIndex = 0;
while (
newIndex < ItemView.Items.Count &&
IsSeparator(ItemView.Items[newIndex]) &&
newIndex != ItemView.SelectedIndex)
{
newIndex++;
}
}
}
ItemView.SelectedIndex = newIndex; ItemView.SelectedIndex = newIndex;
} }
}
private bool IsSeparator(object? item) => item is ListItemViewModel li && !li.IsInteractive; private bool IsSeparator(object? item) => item is ListItemViewModel li && !li.IsInteractive;