CmdPal: Filters for DynamicListPage? Yes, please. (#40783)

Closes: #40382

## To-do list

- [x] Add support for "single-select" filters to DynamicListPage
- [x] Filters can contain icons
- [x] Filter list can contain separators
- [x] Update Windows Services built-in extension to support filtering by
all, started, stopped, and pending services
- [x] Update SampleExtension dynamic list sample to filter.

## Example of filters in use

```C#
internal sealed partial class ServicesListPage : DynamicListPage
{
    public ServicesListPage()
    {
        Icon = Icons.ServicesIcon;
        Name = "Windows Services";

        var filters = new ServiceFilters();
        filters.PropChanged += Filters_PropChanged;
        Filters = filters;
    }

    private void Filters_PropChanged(object sender, IPropChangedEventArgs args) => RaiseItemsChanged();

    public override void UpdateSearchText(string oldSearch, string newSearch) => RaiseItemsChanged();

    public override IListItem[] GetItems()
    {
       // ServiceHelper.Search knows how to filter based on the CurrentFilterIds provided
        var items = ServiceHelper.Search(SearchText, Filters.CurrentFilterIds).ToArray();

        return items;
    }
}

public partial class ServiceFilters : Filters
{
    public ServiceFilters()
    {
        // This would be a default selection. Not providing this will cause the filter
        // control to display the "Filter" placeholder text.
        CurrentFilterIds = ["all"];
    }

    public override IFilterItem[] GetFilters()
    {
        return [
            new Filter() { Id = "all", Name = "All Services" },
            new Separator(),
            new Filter() { Id = "running", Name = "Running", Icon = Icons.GreenCircleIcon },
            new Filter() { Id = "stopped", Name = "Stopped", Icon = Icons.RedCircleIcon },
            new Filter() { Id = "paused", Name = "Paused", Icon = Icons.PauseIcon },
        ];
    }
}
```

## Current example of behavior


https://github.com/user-attachments/assets/2e325763-ad3a-4445-bbe2-a840df08d0b3

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>
This commit is contained in:
Michael Jolley
2025-08-21 05:40:09 -05:00
committed by GitHub
parent 1a798e03cd
commit 69dc1d5e18
26 changed files with 646 additions and 33 deletions

View File

@@ -0,0 +1,23 @@
// 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.CommandPalette.Extensions.Toolkit;
public abstract partial class Filters : BaseObservable, IFilters
{
public string CurrentFilterId
{
get => field;
set
{
field = value;
OnPropertyChanged(nameof(CurrentFilterId));
}
}
= string.Empty;
// This method should be overridden in derived classes to provide the actual filters.
public abstract IFilterItem[] GetFilters();
}

View File

@@ -4,6 +4,6 @@
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class SeparatorContextItem : ISeparatorContextItem
public partial class Separator : ISeparatorContextItem, ISeparatorFilterItem
{
}

View File

@@ -122,7 +122,7 @@ namespace Microsoft.CommandPalette.Extensions
interface ISeparatorFilterItem requires IFilterItem {}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IFilter requires IFilterItem {
interface IFilter requires INotifyPropChanged, IFilterItem {
String Id { get; };
String Name { get; };
IIconInfo Icon { get; };
@@ -131,7 +131,7 @@ namespace Microsoft.CommandPalette.Extensions
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IFilters {
String CurrentFilterId { get; set; };
IFilterItem[] Filters();
IFilterItem[] GetFilters();
}
struct Color