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

@@ -19,7 +19,7 @@ namespace Microsoft.CmdPal.Ext.WindowsServices.Helpers;
public static class ServiceHelper
{
public static IEnumerable<ListItem> Search(string search)
public static IEnumerable<ListItem> Search(string search, string filterId)
{
var services = ServiceController.GetServices().OrderBy(s => s.DisplayName);
IEnumerable<ServiceController> serviceList = [];
@@ -44,6 +44,21 @@ public static class ServiceHelper
serviceList = servicesStartsWith.Concat(servicesContains);
}
switch (filterId)
{
case "running":
serviceList = serviceList.Where(w => w.Status == ServiceControllerStatus.Running);
break;
case "stopped":
serviceList = serviceList.Where(w => w.Status == ServiceControllerStatus.Stopped);
break;
case "paused":
serviceList = serviceList.Where(w => w.Status == ServiceControllerStatus.Paused);
break;
case "all":
break;
}
var result = serviceList.Select(s =>
{
var serviceResult = ServiceResult.CreateServiceController(s);

View File

@@ -0,0 +1,26 @@
// 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.
using Microsoft.CmdPal.Ext.WindowsServices;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class ServiceFilters : Filters
{
public ServiceFilters()
{
CurrentFilterId = "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 },
];
}
}

View File

@@ -16,13 +16,19 @@ internal sealed partial class ServicesListPage : DynamicListPage
{
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(0);
public override IListItem[] GetItems()
{
var items = ServiceHelper.Search(SearchText).ToArray();
var items = ServiceHelper.Search(SearchText, Filters.CurrentFilterId).ToArray();
return items;
}