From 603ac55f8aba2bc3de727d3a2991286ea4aadc04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Wed, 11 Feb 2026 13:03:48 +0100 Subject: [PATCH] CmdPal: Prevent item template selectors from modifying containers (#45498) ## Summary of the Pull Request This PR updates the item template selectors for ListView and GridView and prevents them from modifying the container. As a flyby, it introduces an enum for the list item type and centralizes the logic that determines the type to the view model. ## PR Checklist - [x] Closes: #45496 - [ ] **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 ## Detailed Description of the Pull Request / Additional comments ## Validation Steps Performed --- .../CommandViewModel.cs | 2 ++ .../ListItemType.cs | 12 +++++++ .../ListItemViewModel.cs | 22 +++++++----- .../GridItemContainerStyleSelector.cs | 16 ++++++--- .../Converters/GridItemTemplateSelector.cs | 19 +++++++---- .../ListItemContainerStyleSelector.cs | 20 ++++++++--- .../Converters/ListItemTemplateSelector.cs | 34 +++++++------------ .../ExtViews/ListPage.xaml | 9 ++++- .../ExtViews/ListPage.xaml.cs | 5 +-- 9 files changed, 87 insertions(+), 52 deletions(-) create mode 100644 src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemType.cs diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandViewModel.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandViewModel.cs index b2c03411d1..6ec593bfa4 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandViewModel.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandViewModel.cs @@ -11,6 +11,8 @@ public partial class CommandViewModel : ExtensionObjectViewModel { public ExtensionObject Model { get; private set; } = new(null); + public bool IsSet => Model.Unsafe is not null; + protected bool IsInitialized { get; private set; } protected bool IsFastInitialized { get; private set; } diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemType.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemType.cs new file mode 100644 index 0000000000..71e6970056 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemType.cs @@ -0,0 +1,12 @@ +// 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. + +namespace Microsoft.CmdPal.Core.ViewModels; + +public enum ListItemType +{ + Item, + SectionHeader, + Separator, +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemViewModel.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemViewModel.cs index fb4afc49b8..bd3b505dc1 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemViewModel.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemViewModel.cs @@ -24,7 +24,9 @@ public partial class ListItemViewModel : CommandItemViewModel public string Section { get; private set; } = string.Empty; - public bool IsSectionOrSeparator { get; private set; } + public ListItemType Type { get; private set; } + + public bool IsInteractive => Type == ListItemType.Item; public DetailsViewModel? Details { get; private set; } @@ -85,15 +87,17 @@ public partial class ListItemViewModel : CommandItemViewModel UpdateTags(li.Tags); Section = li.Section ?? string.Empty; - IsSectionOrSeparator = IsSeparator(li); - UpdateProperty(nameof(Section), nameof(IsSectionOrSeparator)); + Type = EvaluateType(); + UpdateProperty(nameof(Section), nameof(Type), nameof(IsInteractive)); UpdateAccessibleName(); } - private bool IsSeparator(IListItem item) + private ListItemType EvaluateType() { - return item.Command is null; + return Command.IsSet + ? ListItemType.Item + : string.IsNullOrEmpty(Section) ? ListItemType.Separator : ListItemType.SectionHeader; } public override void SlowInitializeProperties() @@ -140,12 +144,12 @@ public partial class ListItemViewModel : CommandItemViewModel break; case nameof(model.Section): Section = model.Section ?? string.Empty; - IsSectionOrSeparator = IsSeparator(model); - UpdateProperty(nameof(Section), nameof(IsSectionOrSeparator)); + Type = EvaluateType(); + UpdateProperty(nameof(Section), nameof(Type), nameof(IsInteractive)); break; case nameof(model.Command): - IsSectionOrSeparator = IsSeparator(model); - UpdateProperty(nameof(IsSectionOrSeparator)); + Type = EvaluateType(); + UpdateProperty(nameof(Type), nameof(IsInteractive)); break; case nameof(Details): var extensionDetails = model.Details; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemContainerStyleSelector.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemContainerStyleSelector.cs index ae585e7f11..dc8d0d0d77 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemContainerStyleSelector.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemContainerStyleSelector.cs @@ -24,11 +24,19 @@ internal sealed partial class GridItemContainerStyleSelector : StyleSelector protected override Style? SelectStyleCore(object item, DependencyObject container) { - if (item is ListItemViewModel { IsSectionOrSeparator: true } listItem) + if (item is not ListItemViewModel element) { - return string.IsNullOrWhiteSpace(listItem.Title) - ? Separator! - : Section; + return Medium; + } + + switch (element.Type) + { + case ListItemType.Separator: + return Separator; + case ListItemType.SectionHeader: + return Section; + default: + break; } return GridProperties switch diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemTemplateSelector.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemTemplateSelector.cs index f638f3f09e..1e568981c7 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemTemplateSelector.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemTemplateSelector.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.CmdPal.Core.ViewModels; +using Microsoft.CommandPalette.Extensions.Toolkit; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -24,15 +25,19 @@ internal sealed partial class GridItemTemplateSelector : DataTemplateSelector protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject) { - if (item is ListItemViewModel element && element.IsSectionOrSeparator) + if (item is not ListItemViewModel element) { - if (dependencyObject is UIElement li) - { - li.IsTabStop = false; - li.IsHitTestVisible = false; - } + return Medium; + } - return string.IsNullOrWhiteSpace(element.Section) ? Separator : Section; + switch (element.Type) + { + case ListItemType.Separator: + return Separator; + case ListItemType.SectionHeader: + return Section; + default: + break; } return GridProperties switch diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemContainerStyleSelector.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemContainerStyleSelector.cs index 45a785c2b4..dd2d515417 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemContainerStyleSelector.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemContainerStyleSelector.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.CmdPal.Core.ViewModels; +using Microsoft.CommandPalette.Extensions.Toolkit; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -18,11 +19,20 @@ internal sealed partial class ListItemContainerStyleSelector : StyleSelector protected override Style? SelectStyleCore(object item, DependencyObject container) { - return item switch + if (item is not ListItemViewModel element) { - ListItemViewModel { IsSectionOrSeparator: true } listItemViewModel when string.IsNullOrWhiteSpace(listItemViewModel.Title) => Separator!, - ListItemViewModel { IsSectionOrSeparator: true } => Section, - _ => Default, - }; + return Default; + } + + switch (element.Type) + { + case ListItemType.Separator: + return Separator; + case ListItemType.SectionHeader: + return Section; + case ListItemType.Item: + default: + return Default; + } } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemTemplateSelector.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemTemplateSelector.cs index 7fb810f5dc..47e12c148a 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemTemplateSelector.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemTemplateSelector.cs @@ -18,30 +18,20 @@ public sealed partial class ListItemTemplateSelector : DataTemplateSelector protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container) { - DataTemplate? dataTemplate = ListItem; - - if (container is ListViewItem listItem) + if (item is not ListItemViewModel element) { - if (item is ListItemViewModel element) - { - if (container is ListViewItem li && element.IsSectionOrSeparator) - { - li.IsEnabled = false; - li.AllowFocusWhenDisabled = false; - li.AllowFocusOnInteraction = false; - li.IsHitTestVisible = false; - dataTemplate = string.IsNullOrWhiteSpace(element.Section) ? Separator : Section; - } - else - { - listItem.IsEnabled = true; - listItem.AllowFocusWhenDisabled = true; - listItem.AllowFocusOnInteraction = true; - listItem.IsHitTestVisible = true; - } - } + return ListItem; } - return dataTemplate; + switch (element.Type) + { + case ListItemType.Separator: + return Separator; + case ListItemType.SectionHeader: + return Section; + case ListItemType.Item: + default: + return ListItem; + } } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml index 2f670d62b4..022b214c79 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml @@ -206,23 +206,30 @@ x:Key="ListSectionContainerStyle" BasedOn="{StaticResource DefaultListViewItemStyle}" TargetType="ListViewItem"> + + + + -