From 55e974dea47b670784117a26f0630c72701ee239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Fri, 17 Oct 2025 16:48:48 +0200 Subject: [PATCH] CmdPal: Fix FiltersViewModel binding (#42467) ## Summary of the Pull Request This PR resolves crashes on pages with filters, such as Windows Terminal profiles or Windows Services, when compiled with trimming/AOT. It removes runtime binding from the FiltersDropDown control, effectively preventing crashes caused by trimming/AOT dropping binding metadata for FilterItemViewModel. ## PR Checklist - [x] Closes: #42428 - [x] Closes: #42482 - [x] Related to: #42458 - [ ] **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 Co-authored-by: Niels Laute --- .../FiltersViewModel.cs | 53 ++++++++++++++----- .../Controls/FiltersDropDown.xaml | 3 +- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/FiltersViewModel.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/FiltersViewModel.cs index 191b0a5ec5..ba0d828dc3 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/FiltersViewModel.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/FiltersViewModel.cs @@ -2,8 +2,10 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using Microsoft.CmdPal.Core.ViewModels.Models; using Microsoft.CommandPalette.Extensions; +using Microsoft.CommandPalette.Extensions.Toolkit; namespace Microsoft.CmdPal.Core.ViewModels; @@ -13,6 +15,8 @@ public partial class FiltersViewModel : ExtensionObjectViewModel public string CurrentFilterId { get; private set; } = string.Empty; + public IFilterItemViewModel? CurrentFilter { get; private set; } + public IFilterItemViewModel[] Filters { get; private set; } = []; public bool ShouldShowFilters => Filters.Length > 0; @@ -30,11 +34,13 @@ public partial class FiltersViewModel : ExtensionObjectViewModel if (_filtersModel.Unsafe is not null) { var filters = _filtersModel.Unsafe.GetFilters(); - Filters = BuildFilters(filters ?? []); - UpdateProperty(nameof(Filters), nameof(ShouldShowFilters)); + var currentFilterId = _filtersModel.Unsafe.CurrentFilterId ?? string.Empty; - CurrentFilterId = _filtersModel.Unsafe.CurrentFilterId ?? string.Empty; - UpdateProperty(nameof(CurrentFilterId)); + var result = BuildFilters(filters ?? [], currentFilterId); + Filters = result.Items; + CurrentFilterId = currentFilterId; + CurrentFilter = result.Selected; + UpdateProperty(nameof(Filters), nameof(ShouldShowFilters), nameof(CurrentFilterId), nameof(CurrentFilter)); return; } @@ -45,27 +51,48 @@ public partial class FiltersViewModel : ExtensionObjectViewModel } Filters = []; - UpdateProperty(nameof(Filters), nameof(ShouldShowFilters)); - CurrentFilterId = string.Empty; - UpdateProperty(nameof(CurrentFilterId)); + CurrentFilter = null; + UpdateProperty(nameof(Filters), nameof(ShouldShowFilters), nameof(CurrentFilterId), nameof(CurrentFilter)); } - private IFilterItemViewModel[] BuildFilters(IFilterItem[] filters) + private (IFilterItemViewModel[] Items, IFilterItemViewModel? Selected) BuildFilters(IFilterItem[] filters, string currentFilterId) { - return [..filters.Select(filter => + if (filters is null || filters.Length == 0) + { + return ([], null); + } + + var items = new List(filters.Length); + FilterItemViewModel? firstFilterItem = null; + FilterItemViewModel? selectedFilterItem = null; + + foreach (var filter in filters) { if (filter is IFilter filterItem) { - var filterItemViewModel = new FilterItemViewModel(filterItem!, PageContext); + var filterItemViewModel = new FilterItemViewModel(filterItem, PageContext); filterItemViewModel.InitializeProperties(); - return filterItemViewModel; + + if (firstFilterItem is null) + { + firstFilterItem = filterItemViewModel; + } + + if (selectedFilterItem is null && filterItemViewModel.Id == currentFilterId) + { + selectedFilterItem = filterItemViewModel; + } + + items.Add(filterItemViewModel); } else { - return new SeparatorViewModel(); + items.Add(new SeparatorViewModel()); } - })]; + } + + return (items.ToArray(), selectedFilterItem ?? firstFilterItem); } public override void SafeCleanup() diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/FiltersDropDown.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/FiltersDropDown.xaml index ee05ea61e8..36a14965a1 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/FiltersDropDown.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/FiltersDropDown.xaml @@ -74,8 +74,7 @@ ItemsSource="{x:Bind ViewModel.Filters, Mode=OneWay}" PlaceholderText="Filters" PreviewKeyDown="FiltersComboBox_PreviewKeyDown" - SelectedValue="{x:Bind ViewModel.CurrentFilterId, Mode=OneWay}" - SelectedValuePath="Id" + SelectedValue="{x:Bind ViewModel.CurrentFilter, Mode=OneWay}" SelectionChanged="FiltersComboBox_SelectionChanged" Style="{StaticResource ComboBoxStyle}" Visibility="{x:Bind ViewModel.ShouldShowFilters, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">