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.

<!-- Please review the items on the PR checklist before submitting-->
## 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

<!-- 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

Co-authored-by: Niels Laute <niels.laute@live.nl>
This commit is contained in:
Jiří Polášek
2025-10-17 16:48:48 +02:00
committed by GitHub
parent 92d9f1061c
commit 55e974dea4
2 changed files with 41 additions and 15 deletions

View File

@@ -2,8 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Microsoft.CmdPal.Core.ViewModels.Models; using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels; namespace Microsoft.CmdPal.Core.ViewModels;
@@ -13,6 +15,8 @@ public partial class FiltersViewModel : ExtensionObjectViewModel
public string CurrentFilterId { get; private set; } = string.Empty; public string CurrentFilterId { get; private set; } = string.Empty;
public IFilterItemViewModel? CurrentFilter { get; private set; }
public IFilterItemViewModel[] Filters { get; private set; } = []; public IFilterItemViewModel[] Filters { get; private set; } = [];
public bool ShouldShowFilters => Filters.Length > 0; public bool ShouldShowFilters => Filters.Length > 0;
@@ -30,11 +34,13 @@ public partial class FiltersViewModel : ExtensionObjectViewModel
if (_filtersModel.Unsafe is not null) if (_filtersModel.Unsafe is not null)
{ {
var filters = _filtersModel.Unsafe.GetFilters(); var filters = _filtersModel.Unsafe.GetFilters();
Filters = BuildFilters(filters ?? []); var currentFilterId = _filtersModel.Unsafe.CurrentFilterId ?? string.Empty;
UpdateProperty(nameof(Filters), nameof(ShouldShowFilters));
CurrentFilterId = _filtersModel.Unsafe.CurrentFilterId ?? string.Empty; var result = BuildFilters(filters ?? [], currentFilterId);
UpdateProperty(nameof(CurrentFilterId)); Filters = result.Items;
CurrentFilterId = currentFilterId;
CurrentFilter = result.Selected;
UpdateProperty(nameof(Filters), nameof(ShouldShowFilters), nameof(CurrentFilterId), nameof(CurrentFilter));
return; return;
} }
@@ -45,27 +51,48 @@ public partial class FiltersViewModel : ExtensionObjectViewModel
} }
Filters = []; Filters = [];
UpdateProperty(nameof(Filters), nameof(ShouldShowFilters));
CurrentFilterId = string.Empty; 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<IFilterItem, IFilterItemViewModel>(filter => if (filters is null || filters.Length == 0)
{
return ([], null);
}
var items = new List<IFilterItemViewModel>(filters.Length);
FilterItemViewModel? firstFilterItem = null;
FilterItemViewModel? selectedFilterItem = null;
foreach (var filter in filters)
{ {
if (filter is IFilter filterItem) if (filter is IFilter filterItem)
{ {
var filterItemViewModel = new FilterItemViewModel(filterItem!, PageContext); var filterItemViewModel = new FilterItemViewModel(filterItem, PageContext);
filterItemViewModel.InitializeProperties(); filterItemViewModel.InitializeProperties();
return filterItemViewModel;
if (firstFilterItem is null)
{
firstFilterItem = filterItemViewModel;
}
if (selectedFilterItem is null && filterItemViewModel.Id == currentFilterId)
{
selectedFilterItem = filterItemViewModel;
}
items.Add(filterItemViewModel);
} }
else else
{ {
return new SeparatorViewModel(); items.Add(new SeparatorViewModel());
} }
})]; }
return (items.ToArray(), selectedFilterItem ?? firstFilterItem);
} }
public override void SafeCleanup() public override void SafeCleanup()

View File

@@ -74,8 +74,7 @@
ItemsSource="{x:Bind ViewModel.Filters, Mode=OneWay}" ItemsSource="{x:Bind ViewModel.Filters, Mode=OneWay}"
PlaceholderText="Filters" PlaceholderText="Filters"
PreviewKeyDown="FiltersComboBox_PreviewKeyDown" PreviewKeyDown="FiltersComboBox_PreviewKeyDown"
SelectedValue="{x:Bind ViewModel.CurrentFilterId, Mode=OneWay}" SelectedValue="{x:Bind ViewModel.CurrentFilter, Mode=OneWay}"
SelectedValuePath="Id"
SelectionChanged="FiltersComboBox_SelectionChanged" SelectionChanged="FiltersComboBox_SelectionChanged"
Style="{StaticResource ComboBoxStyle}" Style="{StaticResource ComboBoxStyle}"
Visibility="{x:Bind ViewModel.ShouldShowFilters, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}"> Visibility="{x:Bind ViewModel.ShouldShowFilters, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">