mirror of
https://github.com/microsoft/PowerToys
synced 2025-08-22 10:07:37 +00:00
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:
parent
1a798e03cd
commit
69dc1d5e18
@ -189,7 +189,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new SeparatorContextItemViewModel() as IContextItemViewModel;
|
return new SeparatorViewModel() as IContextItemViewModel;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
@ -350,7 +350,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new SeparatorContextItemViewModel() as IContextItemViewModel;
|
return new SeparatorViewModel() as IContextItemViewModel;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
@ -119,7 +119,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new SeparatorContextItemViewModel();
|
return new SeparatorViewModel();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
@ -178,7 +178,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new SeparatorContextItemViewModel();
|
return new SeparatorViewModel();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
// 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.Core.ViewModels.Models;
|
||||||
|
using Microsoft.CommandPalette.Extensions;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||||
|
|
||||||
|
public partial class FilterItemViewModel : ExtensionObjectViewModel, IFilterItemViewModel
|
||||||
|
{
|
||||||
|
private ExtensionObject<IFilter> _model;
|
||||||
|
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public IconInfoViewModel Icon { get; set; } = new(null);
|
||||||
|
|
||||||
|
internal InitializedState Initialized { get; private set; } = InitializedState.Uninitialized;
|
||||||
|
|
||||||
|
protected bool IsInitialized => IsInErrorState || Initialized.HasFlag(InitializedState.Initialized);
|
||||||
|
|
||||||
|
public bool IsInErrorState => Initialized.HasFlag(InitializedState.Error);
|
||||||
|
|
||||||
|
public FilterItemViewModel(IFilter filter, WeakReference<IPageContext> context)
|
||||||
|
: base(context)
|
||||||
|
{
|
||||||
|
_model = new(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void InitializeProperties()
|
||||||
|
{
|
||||||
|
if (IsInitialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var filter = _model.Unsafe;
|
||||||
|
if (filter == null)
|
||||||
|
{
|
||||||
|
return; // throw?
|
||||||
|
}
|
||||||
|
|
||||||
|
Id = filter.Id;
|
||||||
|
Name = filter.Name;
|
||||||
|
Icon = new(filter.Icon);
|
||||||
|
if (Icon is not null)
|
||||||
|
{
|
||||||
|
Icon.InitializeProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateProperty(nameof(Id));
|
||||||
|
UpdateProperty(nameof(Name));
|
||||||
|
UpdateProperty(nameof(Icon));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
// 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 CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Microsoft.CmdPal.Core.ViewModels.Models;
|
||||||
|
using Microsoft.CommandPalette.Extensions;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||||
|
|
||||||
|
public partial class FiltersViewModel : ExtensionObjectViewModel
|
||||||
|
{
|
||||||
|
private readonly ExtensionObject<IFilters> _filtersModel = new(null);
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial string CurrentFilterId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(ShouldShowFilters))]
|
||||||
|
public partial IFilterItemViewModel[] Filters { get; set; } = [];
|
||||||
|
|
||||||
|
public bool ShouldShowFilters => Filters.Length > 0;
|
||||||
|
|
||||||
|
public FiltersViewModel(ExtensionObject<IFilters> filters, WeakReference<IPageContext> context)
|
||||||
|
: base(context)
|
||||||
|
{
|
||||||
|
_filtersModel = filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void InitializeProperties()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_filtersModel.Unsafe is not null)
|
||||||
|
{
|
||||||
|
var filters = _filtersModel.Unsafe.GetFilters();
|
||||||
|
Filters = filters.Select<IFilterItem, IFilterItemViewModel>(filter =>
|
||||||
|
{
|
||||||
|
var filterItem = filter as IFilter;
|
||||||
|
if (filterItem != null)
|
||||||
|
{
|
||||||
|
var filterVM = new FilterItemViewModel(filterItem!, PageContext);
|
||||||
|
filterVM.InitializeProperties();
|
||||||
|
|
||||||
|
return filterVM;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new SeparatorViewModel();
|
||||||
|
}
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
CurrentFilterId = _filtersModel.Unsafe.CurrentFilterId;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ShowException(ex, _filtersModel.Unsafe?.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Filters = [];
|
||||||
|
CurrentFilterId = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SafeCleanup()
|
||||||
|
{
|
||||||
|
base.SafeCleanup();
|
||||||
|
|
||||||
|
foreach (var filter in Filters)
|
||||||
|
{
|
||||||
|
if (filter is FilterItemViewModel filterVM)
|
||||||
|
{
|
||||||
|
filterVM.SafeCleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Filters = [];
|
||||||
|
}
|
||||||
|
}
|
@ -2,12 +2,7 @@
|
|||||||
// 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;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||||
|
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
// 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 interface IFilterItemViewModel
|
||||||
|
{
|
||||||
|
}
|
@ -26,6 +26,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial ObservableCollection<ListItemViewModel> FilteredItems { get; set; } = [];
|
public partial ObservableCollection<ListItemViewModel> FilteredItems { get; set; } = [];
|
||||||
|
|
||||||
|
public FiltersViewModel? Filters { get; set; }
|
||||||
|
|
||||||
private ObservableCollection<ListItemViewModel> Items { get; set; } = [];
|
private ObservableCollection<ListItemViewModel> Items { get; set; } = [];
|
||||||
|
|
||||||
private readonly ExtensionObject<IListPage> _model;
|
private readonly ExtensionObject<IListPage> _model;
|
||||||
@ -86,7 +88,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
// TODO: Does this need to hop to a _different_ thread, so that we don't block the extension while we're fetching?
|
// TODO: Does this need to hop to a _different_ thread, so that we don't block the extension while we're fetching?
|
||||||
private void Model_ItemsChanged(object sender, IItemsChangedEventArgs args) => FetchItems();
|
private void Model_ItemsChanged(object sender, IItemsChangedEventArgs args) => FetchItems();
|
||||||
|
|
||||||
protected override void OnFilterUpdated(string filter)
|
protected override void OnSearchTextBoxUpdated(string searchTextBox)
|
||||||
{
|
{
|
||||||
//// TODO: Just temp testing, need to think about where we want to filter, as AdvancedCollectionView in View could be done, but then grouping need CollectionViewSource, maybe we do grouping in view
|
//// TODO: Just temp testing, need to think about where we want to filter, as AdvancedCollectionView in View could be done, but then grouping need CollectionViewSource, maybe we do grouping in view
|
||||||
//// and manage filtering below, but we should be smarter about this and understand caching and other requirements...
|
//// and manage filtering below, but we should be smarter about this and understand caching and other requirements...
|
||||||
@ -104,7 +106,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
{
|
{
|
||||||
if (_model.Unsafe is IDynamicListPage dynamic)
|
if (_model.Unsafe is IDynamicListPage dynamic)
|
||||||
{
|
{
|
||||||
dynamic.SearchText = filter;
|
dynamic.SearchText = searchTextBox;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -127,6 +129,26 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateCurrentFilter(string currentFilterId)
|
||||||
|
{
|
||||||
|
// We're getting called on the UI thread.
|
||||||
|
// Hop off to a BG thread to update the extension.
|
||||||
|
_ = Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_model.Unsafe is IListPage listPage)
|
||||||
|
{
|
||||||
|
listPage.Filters?.CurrentFilterId = currentFilterId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ShowException(ex, _model?.Unsafe?.Name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//// Run on background thread, from InitializeAsync or Model_ItemsChanged
|
//// Run on background thread, from InitializeAsync or Model_ItemsChanged
|
||||||
private void FetchItems()
|
private void FetchItems()
|
||||||
{
|
{
|
||||||
@ -305,7 +327,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
/// Apply our current filter text to the list of items, and update
|
/// Apply our current filter text to the list of items, and update
|
||||||
/// FilteredItems to match the results.
|
/// FilteredItems to match the results.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ApplyFilterUnderLock() => ListHelpers.InPlaceUpdateList(FilteredItems, FilterList(Items, Filter));
|
private void ApplyFilterUnderLock() => ListHelpers.InPlaceUpdateList(FilteredItems, FilterList(Items, SearchTextBox));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper to generate a weighting for a given list item, based on title,
|
/// Helper to generate a weighting for a given list item, based on title,
|
||||||
@ -507,6 +529,10 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
EmptyContent = new(new(model.EmptyContent), PageContext);
|
EmptyContent = new(new(model.EmptyContent), PageContext);
|
||||||
EmptyContent.SlowInitializeProperties();
|
EmptyContent.SlowInitializeProperties();
|
||||||
|
|
||||||
|
Filters = new(new(model.Filters), PageContext);
|
||||||
|
Filters.InitializeProperties();
|
||||||
|
UpdateProperty(nameof(Filters));
|
||||||
|
|
||||||
FetchItems();
|
FetchItems();
|
||||||
model.ItemsChanged += Model_ItemsChanged;
|
model.ItemsChanged += Model_ItemsChanged;
|
||||||
}
|
}
|
||||||
@ -578,6 +604,10 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
EmptyContent = new(new(model.EmptyContent), PageContext);
|
EmptyContent = new(new(model.EmptyContent), PageContext);
|
||||||
EmptyContent.SlowInitializeProperties();
|
EmptyContent.SlowInitializeProperties();
|
||||||
break;
|
break;
|
||||||
|
case nameof(Filters):
|
||||||
|
Filters = new(new(model.Filters), PageContext);
|
||||||
|
Filters.InitializeProperties();
|
||||||
|
break;
|
||||||
case nameof(IsLoading):
|
case nameof(IsLoading):
|
||||||
UpdateEmptyContent();
|
UpdateEmptyContent();
|
||||||
break;
|
break;
|
||||||
@ -641,6 +671,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
FilteredItems.Clear();
|
FilteredItems.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Filters?.SafeCleanup();
|
||||||
|
|
||||||
var model = _model.Unsafe;
|
var model = _model.Unsafe;
|
||||||
if (model is not null)
|
if (model is not null)
|
||||||
{
|
{
|
||||||
|
@ -32,7 +32,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
|
|||||||
// This is set from the SearchBar
|
// This is set from the SearchBar
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[NotifyPropertyChangedFor(nameof(ShowSuggestion))]
|
[NotifyPropertyChangedFor(nameof(ShowSuggestion))]
|
||||||
public partial string Filter { get; set; } = string.Empty;
|
public partial string SearchTextBox { get; set; } = string.Empty;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public virtual partial string PlaceholderText { get; private set; } = "Type here to search...";
|
public virtual partial string PlaceholderText { get; private set; } = "Type here to search...";
|
||||||
@ -41,7 +41,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
|
|||||||
[NotifyPropertyChangedFor(nameof(ShowSuggestion))]
|
[NotifyPropertyChangedFor(nameof(ShowSuggestion))]
|
||||||
public virtual partial string TextToSuggest { get; protected set; } = string.Empty;
|
public virtual partial string TextToSuggest { get; protected set; } = string.Empty;
|
||||||
|
|
||||||
public bool ShowSuggestion => !string.IsNullOrEmpty(TextToSuggest) && TextToSuggest != Filter;
|
public bool ShowSuggestion => !string.IsNullOrEmpty(TextToSuggest) && TextToSuggest != SearchTextBox;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial AppExtensionHost ExtensionHost { get; private set; }
|
public partial AppExtensionHost ExtensionHost { get; private set; }
|
||||||
@ -167,9 +167,9 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnFilterChanged(string oldValue, string newValue) => OnFilterUpdated(newValue);
|
partial void OnSearchTextBoxChanged(string oldValue, string newValue) => OnSearchTextBoxUpdated(newValue);
|
||||||
|
|
||||||
protected virtual void OnFilterUpdated(string filter)
|
protected virtual void OnSearchTextBoxUpdated(string searchTextBox)
|
||||||
{
|
{
|
||||||
// The base page has no notion of data, so we do nothing here...
|
// The base page has no notion of data, so we do nothing here...
|
||||||
// subclasses should override.
|
// subclasses should override.
|
||||||
|
@ -9,6 +9,10 @@ using Microsoft.CommandPalette.Extensions;
|
|||||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||||
|
|
||||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||||
public partial class SeparatorContextItemViewModel() : IContextItemViewModel, ISeparatorContextItem
|
public partial class SeparatorViewModel() :
|
||||||
|
IContextItemViewModel,
|
||||||
|
IFilterItemViewModel,
|
||||||
|
ISeparatorContextItem,
|
||||||
|
ISeparatorFilterItem
|
||||||
{
|
{
|
||||||
}
|
}
|
@ -108,7 +108,7 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
<!-- Template for context item separators -->
|
<!-- Template for context item separators -->
|
||||||
<DataTemplate x:Key="SeparatorContextMenuViewModelTemplate" x:DataType="coreViewModels:SeparatorContextItemViewModel">
|
<DataTemplate x:Key="SeparatorContextMenuViewModelTemplate" x:DataType="coreViewModels:SeparatorViewModel">
|
||||||
<Rectangle
|
<Rectangle
|
||||||
Height="1"
|
Height="1"
|
||||||
Margin="-16,-12,-12,-12"
|
Margin="-16,-12,-12,-12"
|
||||||
|
@ -270,7 +270,7 @@ public sealed partial class ContextMenu : UserControl,
|
|||||||
|
|
||||||
private bool IsSeparator(object item)
|
private bool IsSeparator(object item)
|
||||||
{
|
{
|
||||||
return item is SeparatorContextItemViewModel;
|
return item is SeparatorViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateUiForStackChange()
|
private void UpdateUiForStackChange()
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<UserControl
|
||||||
|
x:Class="Microsoft.CmdPal.UI.Controls.FiltersDropDown"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:cmdpalUI="using:Microsoft.CmdPal.UI"
|
||||||
|
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||||
|
xmlns:coreViewModels="using:Microsoft.CmdPal.Core.ViewModels"
|
||||||
|
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
Background="Transparent"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||||
|
|
||||||
|
<cmdpalUI:FilterTemplateSelector
|
||||||
|
x:Key="FilterTemplateSelector"
|
||||||
|
Default="{StaticResource FilterItemViewModelTemplate}"
|
||||||
|
Separator="{StaticResource SeparatorViewModelTemplate}" />
|
||||||
|
|
||||||
|
<Style
|
||||||
|
x:Name="ComboBoxStyle"
|
||||||
|
BasedOn="{StaticResource DefaultComboBoxStyle}"
|
||||||
|
TargetType="ComboBox">
|
||||||
|
<Style.Setters>
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Setter Property="Margin" Value="0,0,12,0" />
|
||||||
|
<Setter Property="Padding" Value="16,4" />
|
||||||
|
</Style.Setters>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Template for the filter items -->
|
||||||
|
<DataTemplate x:Key="FilterItemViewModelTemplate" x:DataType="coreViewModels:FilterItemViewModel">
|
||||||
|
<Grid AutomationProperties.Name="{x:Bind Name, Mode=OneWay}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="32" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<cpcontrols:IconBox
|
||||||
|
Width="16"
|
||||||
|
Margin="4,0"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
SourceKey="{x:Bind Icon}"
|
||||||
|
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{x:Bind Name}" />
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<!-- Template for separators -->
|
||||||
|
<DataTemplate x:Key="SeparatorViewModelTemplate" x:DataType="coreViewModels:SeparatorViewModel">
|
||||||
|
<Rectangle
|
||||||
|
Height="1"
|
||||||
|
Margin="-16,-12,-12,-12"
|
||||||
|
Fill="{ThemeResource MenuFlyoutSeparatorThemeBrush}" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<ComboBox
|
||||||
|
Name="FiltersComboBox"
|
||||||
|
x:Uid="FiltersComboBox"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
ItemTemplateSelector="{StaticResource FilterTemplateSelector}"
|
||||||
|
ItemsSource="{x:Bind ViewModel.Filters, Mode=OneWay}"
|
||||||
|
PlaceholderText="Filters"
|
||||||
|
PreviewKeyDown="FiltersComboBox_PreviewKeyDown"
|
||||||
|
SelectionChanged="FiltersComboBox_SelectionChanged"
|
||||||
|
Style="{StaticResource ComboBoxStyle}"
|
||||||
|
Visibility="{x:Bind ViewModel.ShouldShowFilters, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<ComboBox.ItemContainerStyle>
|
||||||
|
<Style BasedOn="{StaticResource DefaultComboBoxItemStyle}" TargetType="ComboBoxItem">
|
||||||
|
<Setter Property="MinHeight" Value="0" />
|
||||||
|
<Setter Property="Padding" Value="12,8" />
|
||||||
|
</Style>
|
||||||
|
</ComboBox.ItemContainerStyle>
|
||||||
|
<ComboBox.ItemContainerTransitions>
|
||||||
|
<TransitionCollection />
|
||||||
|
</ComboBox.ItemContainerTransitions>
|
||||||
|
</ComboBox>
|
||||||
|
</UserControl>
|
@ -0,0 +1,189 @@
|
|||||||
|
// 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.Core.ViewModels;
|
||||||
|
using Microsoft.CmdPal.UI.Views;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Input;
|
||||||
|
using Windows.System;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.UI.Controls;
|
||||||
|
|
||||||
|
public sealed partial class FiltersDropDown : UserControl,
|
||||||
|
ICurrentPageAware
|
||||||
|
{
|
||||||
|
public PageViewModel? CurrentPageViewModel
|
||||||
|
{
|
||||||
|
get => (PageViewModel?)GetValue(CurrentPageViewModelProperty);
|
||||||
|
set => SetValue(CurrentPageViewModelProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty CurrentPageViewModelProperty =
|
||||||
|
DependencyProperty.Register(nameof(CurrentPageViewModel), typeof(PageViewModel), typeof(FiltersDropDown), new PropertyMetadata(null, OnCurrentPageViewModelChanged));
|
||||||
|
|
||||||
|
private static void OnCurrentPageViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var @this = (FiltersDropDown)d;
|
||||||
|
|
||||||
|
if (@this != null
|
||||||
|
&& e.OldValue is PageViewModel old)
|
||||||
|
{
|
||||||
|
old.PropertyChanged -= @this.Page_PropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this new page does not implement ListViewModel or if
|
||||||
|
// it doesn't contain Filters, we need to clear any filters
|
||||||
|
// that may have been set.
|
||||||
|
if (@this != null)
|
||||||
|
{
|
||||||
|
if (e.NewValue is ListViewModel listViewModel)
|
||||||
|
{
|
||||||
|
@this.ViewModel = listViewModel.Filters;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@this.ViewModel = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@this != null
|
||||||
|
&& e.NewValue is PageViewModel page)
|
||||||
|
{
|
||||||
|
page.PropertyChanged += @this.Page_PropertyChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FiltersViewModel? ViewModel
|
||||||
|
{
|
||||||
|
get => (FiltersViewModel?)GetValue(ViewModelProperty);
|
||||||
|
set => SetValue(ViewModelProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty ViewModelProperty =
|
||||||
|
DependencyProperty.Register(nameof(ViewModel), typeof(FiltersViewModel), typeof(FiltersDropDown), new PropertyMetadata(null));
|
||||||
|
|
||||||
|
public FiltersDropDown()
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to handle the case when a ListPage's `Filters` may have changed
|
||||||
|
private void Page_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var property = e.PropertyName;
|
||||||
|
|
||||||
|
if (CurrentPageViewModel is ListViewModel list)
|
||||||
|
{
|
||||||
|
if (property == nameof(ListViewModel.Filters))
|
||||||
|
{
|
||||||
|
ViewModel = list.Filters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FiltersComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (CurrentPageViewModel is ListViewModel listViewModel &&
|
||||||
|
FiltersComboBox.SelectedItem is FilterItemViewModel filterItem)
|
||||||
|
{
|
||||||
|
listViewModel.UpdateCurrentFilter(filterItem.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FiltersComboBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == VirtualKey.Up)
|
||||||
|
{
|
||||||
|
NavigateUp();
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
else if (e.Key == VirtualKey.Down)
|
||||||
|
{
|
||||||
|
NavigateDown();
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NavigateUp()
|
||||||
|
{
|
||||||
|
var newIndex = FiltersComboBox.SelectedIndex;
|
||||||
|
|
||||||
|
if (FiltersComboBox.SelectedIndex > 0)
|
||||||
|
{
|
||||||
|
newIndex--;
|
||||||
|
|
||||||
|
while (
|
||||||
|
newIndex >= 0 &&
|
||||||
|
IsSeparator(FiltersComboBox.Items[newIndex]) &&
|
||||||
|
newIndex != FiltersComboBox.SelectedIndex)
|
||||||
|
{
|
||||||
|
newIndex--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newIndex < 0)
|
||||||
|
{
|
||||||
|
newIndex = FiltersComboBox.Items.Count - 1;
|
||||||
|
|
||||||
|
while (
|
||||||
|
newIndex >= 0 &&
|
||||||
|
IsSeparator(FiltersComboBox.Items[newIndex]) &&
|
||||||
|
newIndex != FiltersComboBox.SelectedIndex)
|
||||||
|
{
|
||||||
|
newIndex--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newIndex = FiltersComboBox.Items.Count - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FiltersComboBox.SelectedIndex = newIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NavigateDown()
|
||||||
|
{
|
||||||
|
var newIndex = FiltersComboBox.SelectedIndex;
|
||||||
|
|
||||||
|
if (FiltersComboBox.SelectedIndex == FiltersComboBox.Items.Count - 1)
|
||||||
|
{
|
||||||
|
newIndex = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newIndex++;
|
||||||
|
|
||||||
|
while (
|
||||||
|
newIndex < FiltersComboBox.Items.Count &&
|
||||||
|
IsSeparator(FiltersComboBox.Items[newIndex]) &&
|
||||||
|
newIndex != FiltersComboBox.SelectedIndex)
|
||||||
|
{
|
||||||
|
newIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newIndex >= FiltersComboBox.Items.Count)
|
||||||
|
{
|
||||||
|
newIndex = 0;
|
||||||
|
|
||||||
|
while (
|
||||||
|
newIndex < FiltersComboBox.Items.Count &&
|
||||||
|
IsSeparator(FiltersComboBox.Items[newIndex]) &&
|
||||||
|
newIndex != FiltersComboBox.SelectedIndex)
|
||||||
|
{
|
||||||
|
newIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FiltersComboBox.SelectedIndex = newIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsSeparator(object item)
|
||||||
|
{
|
||||||
|
return item is SeparatorViewModel;
|
||||||
|
}
|
||||||
|
}
|
@ -62,7 +62,7 @@ public sealed partial class SearchBar : UserControl,
|
|||||||
{
|
{
|
||||||
// TODO: In some cases we probably want commands to clear a filter
|
// TODO: In some cases we probably want commands to clear a filter
|
||||||
// somewhere in the process, so we need to figure out when that is.
|
// somewhere in the process, so we need to figure out when that is.
|
||||||
@this.FilterBox.Text = page.Filter;
|
@this.FilterBox.Text = page.SearchTextBox;
|
||||||
@this.FilterBox.Select(@this.FilterBox.Text.Length, 0);
|
@this.FilterBox.Select(@this.FilterBox.Text.Length, 0);
|
||||||
|
|
||||||
page.PropertyChanged += @this.Page_PropertyChanged;
|
page.PropertyChanged += @this.Page_PropertyChanged;
|
||||||
@ -87,7 +87,7 @@ public sealed partial class SearchBar : UserControl,
|
|||||||
|
|
||||||
if (CurrentPageViewModel is not null)
|
if (CurrentPageViewModel is not null)
|
||||||
{
|
{
|
||||||
CurrentPageViewModel.Filter = string.Empty;
|
CurrentPageViewModel.SearchTextBox = string.Empty;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -145,7 +145,7 @@ public sealed partial class SearchBar : UserControl,
|
|||||||
// hack TODO GH #245
|
// hack TODO GH #245
|
||||||
if (CurrentPageViewModel is not null)
|
if (CurrentPageViewModel is not null)
|
||||||
{
|
{
|
||||||
CurrentPageViewModel.Filter = FilterBox.Text;
|
CurrentPageViewModel.SearchTextBox = FilterBox.Text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ public sealed partial class SearchBar : UserControl,
|
|||||||
// hack TODO GH #245
|
// hack TODO GH #245
|
||||||
if (CurrentPageViewModel is not null)
|
if (CurrentPageViewModel is not null)
|
||||||
{
|
{
|
||||||
CurrentPageViewModel.Filter = FilterBox.Text;
|
CurrentPageViewModel.SearchTextBox = FilterBox.Text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -320,7 +320,7 @@ public sealed partial class SearchBar : UserControl,
|
|||||||
// Actually plumb Filtering to the view model
|
// Actually plumb Filtering to the view model
|
||||||
if (CurrentPageViewModel is not null)
|
if (CurrentPageViewModel is not null)
|
||||||
{
|
{
|
||||||
CurrentPageViewModel.Filter = FilterBox.Text;
|
CurrentPageViewModel.SearchTextBox = FilterBox.Text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ internal sealed partial class ContextItemTemplateSelector : DataTemplateSelector
|
|||||||
{
|
{
|
||||||
li.IsEnabled = true;
|
li.IsEnabled = true;
|
||||||
|
|
||||||
if (item is SeparatorContextItemViewModel)
|
if (item is SeparatorViewModel)
|
||||||
{
|
{
|
||||||
li.IsEnabled = false;
|
li.IsEnabled = false;
|
||||||
li.AllowFocusWhenDisabled = false;
|
li.AllowFocusWhenDisabled = false;
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
// 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.Core.ViewModels;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.UI;
|
||||||
|
|
||||||
|
internal sealed partial class FilterTemplateSelector : DataTemplateSelector
|
||||||
|
{
|
||||||
|
public DataTemplate? Default { get; set; }
|
||||||
|
|
||||||
|
public DataTemplate? Separator { get; set; }
|
||||||
|
|
||||||
|
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject)
|
||||||
|
{
|
||||||
|
DataTemplate? dataTemplate = Default;
|
||||||
|
|
||||||
|
if (dependencyObject is ComboBoxItem comboBoxItem)
|
||||||
|
{
|
||||||
|
comboBoxItem.IsEnabled = true;
|
||||||
|
|
||||||
|
if (item is SeparatorViewModel)
|
||||||
|
{
|
||||||
|
comboBoxItem.IsEnabled = false;
|
||||||
|
comboBoxItem.AllowFocusWhenDisabled = false;
|
||||||
|
comboBoxItem.AllowFocusOnInteraction = false;
|
||||||
|
dataTemplate = Separator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataTemplate;
|
||||||
|
}
|
||||||
|
}
|
@ -176,6 +176,7 @@
|
|||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- Back button -->
|
<!-- Back button -->
|
||||||
@ -320,6 +321,18 @@
|
|||||||
</TransitionCollection>
|
</TransitionCollection>
|
||||||
</Grid.Transitions>
|
</Grid.Transitions>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<!-- Filter: wrapped in a grid to enable RepositionThemeTransitions -->
|
||||||
|
<Grid Grid.Column="2" HorizontalAlignment="Right">
|
||||||
|
<cpcontrols:FiltersDropDown
|
||||||
|
x:Name="FiltersDropDown"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
CurrentPageViewModel="{x:Bind ViewModel.CurrentPage, Mode=OneWay}" />
|
||||||
|
<Grid.Transitions>
|
||||||
|
<TransitionCollection>
|
||||||
|
<RepositionThemeTransition />
|
||||||
|
</TransitionCollection>
|
||||||
|
</Grid.Transitions>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
|
@ -131,7 +131,7 @@ internal sealed partial class AppListItem : ListItem
|
|||||||
var newCommands = new List<IContextItem>();
|
var newCommands = new List<IContextItem>();
|
||||||
newCommands.AddRange(commands);
|
newCommands.AddRange(commands);
|
||||||
|
|
||||||
newCommands.Add(new SeparatorContextItem());
|
newCommands.Add(new Separator());
|
||||||
|
|
||||||
// 0x50 = P
|
// 0x50 = P
|
||||||
// Full key chord would be Ctrl+P
|
// Full key chord would be Ctrl+P
|
||||||
|
@ -19,7 +19,7 @@ namespace Microsoft.CmdPal.Ext.WindowsServices.Helpers;
|
|||||||
|
|
||||||
public static class ServiceHelper
|
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);
|
var services = ServiceController.GetServices().OrderBy(s => s.DisplayName);
|
||||||
IEnumerable<ServiceController> serviceList = [];
|
IEnumerable<ServiceController> serviceList = [];
|
||||||
@ -44,6 +44,21 @@ public static class ServiceHelper
|
|||||||
serviceList = servicesStartsWith.Concat(servicesContains);
|
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 result = serviceList.Select(s =>
|
||||||
{
|
{
|
||||||
var serviceResult = ServiceResult.CreateServiceController(s);
|
var serviceResult = ServiceResult.CreateServiceController(s);
|
||||||
|
@ -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 },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -16,13 +16,19 @@ internal sealed partial class ServicesListPage : DynamicListPage
|
|||||||
{
|
{
|
||||||
Icon = Icons.ServicesIcon;
|
Icon = Icons.ServicesIcon;
|
||||||
Name = "Windows Services";
|
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 void UpdateSearchText(string oldSearch, string newSearch) => RaiseItemsChanged(0);
|
||||||
|
|
||||||
public override IListItem[] GetItems()
|
public override IListItem[] GetItems()
|
||||||
{
|
{
|
||||||
var items = ServiceHelper.Search(SearchText).ToArray();
|
var items = ServiceHelper.Search(SearchText, Filters.CurrentFilterId).ToArray();
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices.WindowsRuntime;
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
@ -16,9 +17,14 @@ internal sealed partial class SampleDynamicListPage : DynamicListPage
|
|||||||
Icon = new IconInfo(string.Empty);
|
Icon = new IconInfo(string.Empty);
|
||||||
Name = "Dynamic List";
|
Name = "Dynamic List";
|
||||||
IsLoading = true;
|
IsLoading = true;
|
||||||
|
var filters = new SampleFilters();
|
||||||
|
filters.PropChanged += Filters_PropChanged;
|
||||||
|
Filters = filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateSearchText(string oldSearch, string newSearch) => RaiseItemsChanged(newSearch.Length);
|
private void Filters_PropChanged(object sender, IPropChangedEventArgs args) => RaiseItemsChanged();
|
||||||
|
|
||||||
|
public override void UpdateSearchText(string oldSearch, string newSearch) => RaiseItemsChanged();
|
||||||
|
|
||||||
public override IListItem[] GetItems()
|
public override IListItem[] GetItems()
|
||||||
{
|
{
|
||||||
@ -28,6 +34,23 @@ internal sealed partial class SampleDynamicListPage : DynamicListPage
|
|||||||
items = [new ListItem(new NoOpCommand()) { Title = "Start typing in the search box" }];
|
items = [new ListItem(new NoOpCommand()) { Title = "Start typing in the search box" }];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Filters.CurrentFilterId))
|
||||||
|
{
|
||||||
|
switch (Filters.CurrentFilterId)
|
||||||
|
{
|
||||||
|
case "mod2":
|
||||||
|
items = items.Where((item, index) => (index + 1) % 2 == 0).ToArray();
|
||||||
|
break;
|
||||||
|
case "mod3":
|
||||||
|
items = items.Where((item, index) => (index + 1) % 3 == 0).ToArray();
|
||||||
|
break;
|
||||||
|
case "all":
|
||||||
|
default:
|
||||||
|
// No filtering
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (items.Length > 0)
|
if (items.Length > 0)
|
||||||
{
|
{
|
||||||
items[0].Subtitle = "Notice how the number of items changes for this page when you type in the filter box";
|
items[0].Subtitle = "Notice how the number of items changes for this page when you type in the filter box";
|
||||||
@ -36,3 +59,18 @@ internal sealed partial class SampleDynamicListPage : DynamicListPage
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning disable SA1402 // File may only contain a single type
|
||||||
|
public partial class SampleFilters : Filters
|
||||||
|
#pragma warning restore SA1402 // File may only contain a single type
|
||||||
|
{
|
||||||
|
public override IFilterItem[] GetFilters()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
[
|
||||||
|
new Filter() { Id = "all", Name = "All" },
|
||||||
|
new Filter() { Id = "mod2", Name = "Every 2nd", Icon = new IconInfo("2") },
|
||||||
|
new Filter() { Id = "mod3", Name = "Every 3rd", Icon = new IconInfo("3") },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -81,7 +81,7 @@ internal sealed partial class SampleListPage : ListPage
|
|||||||
Title = "I'm a second command",
|
Title = "I'm a second command",
|
||||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.Number1),
|
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.Number1),
|
||||||
},
|
},
|
||||||
new SeparatorContextItem(),
|
new Separator(),
|
||||||
new CommandContextItem(
|
new CommandContextItem(
|
||||||
new ToastCommand("Third command invoked", MessageState.Error) { Name = "Do 3", Icon = new IconInfo("\uF148") }) // dial 3
|
new ToastCommand("Third command invoked", MessageState.Error) { Name = "Do 3", Icon = new IconInfo("\uF148") }) // dial 3
|
||||||
{
|
{
|
||||||
|
@ -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();
|
||||||
|
}
|
@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
public partial class SeparatorContextItem : ISeparatorContextItem
|
public partial class Separator : ISeparatorContextItem, ISeparatorFilterItem
|
||||||
{
|
{
|
||||||
}
|
}
|
@ -122,7 +122,7 @@ namespace Microsoft.CommandPalette.Extensions
|
|||||||
interface ISeparatorFilterItem requires IFilterItem {}
|
interface ISeparatorFilterItem requires IFilterItem {}
|
||||||
|
|
||||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||||
interface IFilter requires IFilterItem {
|
interface IFilter requires INotifyPropChanged, IFilterItem {
|
||||||
String Id { get; };
|
String Id { get; };
|
||||||
String Name { get; };
|
String Name { get; };
|
||||||
IIconInfo Icon { get; };
|
IIconInfo Icon { get; };
|
||||||
@ -131,7 +131,7 @@ namespace Microsoft.CommandPalette.Extensions
|
|||||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||||
interface IFilters {
|
interface IFilters {
|
||||||
String CurrentFilterId { get; set; };
|
String CurrentFilterId { get; set; };
|
||||||
IFilterItem[] Filters();
|
IFilterItem[] GetFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Color
|
struct Color
|
||||||
|
Loading…
x
Reference in New Issue
Block a user