diff --git a/src/ColumnFilterHandler.cs b/src/ColumnFilterHandler.cs index a6cfa0a..033b57d 100644 --- a/src/ColumnFilterHandler.cs +++ b/src/ColumnFilterHandler.cs @@ -30,26 +30,25 @@ public virtual IList GetFilterItems(TableViewColumn column, column.TableView.FilterDescriptions.Where( x => x is not ColumnFilterDescription columnFilter || columnFilter.Column != column)); - var filterValues = new SortedSet(); - - foreach (var item in collectionView) - { - var value = column.GetCellContent(item); - filterValues.Add(IsBlank(value) ? null : value); - } - - return [.. filterValues.Select(value => - { - value ??= TableViewLocalizedStrings.BlankFilterValue; - var isSelected = !column.IsFiltered || !string.IsNullOrEmpty(searchText) || - (column.IsFiltered && SelectedValues[column].Contains(value)); - - return string.IsNullOrEmpty(searchText) - || value?.ToString()?.Contains(searchText, StringComparison.OrdinalIgnoreCase) == true - ? new TableViewFilterItem(isSelected, value) - : null; - - }).OfType()]; + return [.. collectionView.Select(column.GetCellContent) + .Select(x => IsBlank(x) ? null : x) + .GroupBy(x => x) + .OrderBy(x => x.Key) + .Select(x => + { + var value = x.Key; + value ??= TableViewLocalizedStrings.BlankFilterValue; + var isSelected = !column.IsFiltered || !string.IsNullOrEmpty(searchText) || + (column.IsFiltered && SelectedValues[column].Contains(value)); + + return string.IsNullOrEmpty(searchText) + || value?.ToString()?.Contains(searchText, StringComparison.OrdinalIgnoreCase) == true + ? new TableViewFilterItem(isSelected, value, x.Count()) + : null; + + }) + .OfType() + .OrderByDescending(x => _tableView.ShowFilterItemsCount ? x.Count : 0)]; } return []; diff --git a/src/TableView.Properties.cs b/src/TableView.Properties.cs index e592e84..2f98d60 100644 --- a/src/TableView.Properties.cs +++ b/src/TableView.Properties.cs @@ -260,6 +260,11 @@ public partial class TableView /// public static readonly DependencyProperty ConditionalCellStylesProperty = DependencyProperty.Register(nameof(ConditionalCellStyles), typeof(IList), typeof(TableView), new PropertyMetadata(default)); + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ShowFilterItemsCountProperty = DependencyProperty.Register(nameof(ShowFilterItemsCount), typeof(bool), typeof(TableView), new PropertyMetadata(false)); + /// /// Gets or sets a value indicating whether opening the column filter over header right-click is enabled. /// @@ -316,6 +321,15 @@ public IList ConditionalCellStyles set => SetValue(ConditionalCellStylesProperty, value); } + /// + /// Gets or sets a value that indicates whether the TableView displays items count next to each filter item in filter flyout. + /// + public bool ShowFilterItemsCount + { + get => (bool)GetValue(ShowFilterItemsCountProperty); + set => SetValue(ShowFilterItemsCountProperty, value); + } + /// /// Gets or sets the selection start cell slot. /// diff --git a/src/TableViewColumnHeader.FilterItem.cs b/src/TableViewColumnHeader.FilterItem.cs index 78c3493..fcbf87d 100644 --- a/src/TableViewColumnHeader.FilterItem.cs +++ b/src/TableViewColumnHeader.FilterItem.cs @@ -5,7 +5,7 @@ namespace WinUI.TableView; /// /// Represents a filter item used in the options flyout of a TableViewColumnHeader. /// -public partial class TableViewFilterItem : INotifyPropertyChanged +public partial class TableViewFilterItem : INotifyPropertyChanged { /// public event PropertyChangedEventHandler? PropertyChanged; @@ -17,10 +17,12 @@ public partial class TableViewFilterItem : INotifyPropertyChanged /// /// Indicates whether the filter item is selected. /// The value of the filter item. - public TableViewFilterItem(bool isSelected, object value) + /// The count of occurrences for the filter item. + public TableViewFilterItem(bool isSelected, object value, int count = 1) { IsSelected = isSelected; Value = value; + Count = count; } /// @@ -40,4 +42,9 @@ public bool IsSelected /// Gets the value of the filter item. /// public object Value { get; } + + /// + /// Gets or sets the count of occurrences for the filter item. + /// + public int Count { get; set; } } diff --git a/src/TableViewColumnHeader.OptionsFlyoutViewModel.cs b/src/TableViewColumnHeader.OptionsFlyoutViewModel.cs index 462803e..09e0f3f 100644 --- a/src/TableViewColumnHeader.OptionsFlyoutViewModel.cs +++ b/src/TableViewColumnHeader.OptionsFlyoutViewModel.cs @@ -12,7 +12,7 @@ public partial class TableViewColumnHeader /// /// ViewModel for the options flyout in the TableViewColumnHeader. /// - private partial class OptionsFlyoutViewModel : INotifyPropertyChanged + internal partial class OptionsFlyoutViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; private IList _filterItems = []; @@ -114,7 +114,7 @@ public IList FilterItems DetachPropertyChangedHandlers(); _filterItems = value; - AttachPropertyChangedHandlers(); + AttachPropertyChangedHandlers(); SetSelectAllCheckBoxState(); OnPropertyChanged(); } @@ -128,7 +128,7 @@ public IList FilterItems /// /// Sets the state of the select all checkbox. /// - private void SetSelectAllCheckBoxState() + internal void SetSelectAllCheckBoxState() { if (ColumnHeader._selectAllCheckBox is null || !_canSetState) { diff --git a/src/TableViewColumnHeader.cs b/src/TableViewColumnHeader.cs index 93d36d6..f5a3856 100644 --- a/src/TableViewColumnHeader.cs +++ b/src/TableViewColumnHeader.cs @@ -46,6 +46,7 @@ public partial class TableViewColumnHeader : ContentControl private double _reorderStartingPosition; private bool _reorderStarted; private RenderTargetBitmap? _dragVisuals; + private ListView? _filterItemsList; /// /// Initializes a new instance of the TableViewColumnHeader class. @@ -228,8 +229,23 @@ protected override void OnApplyTemplate() _optionsButton.Tapped += OnOptionsButtonTaped; _optionsButton.DataContext = _optionsFlyoutViewModel = new OptionsFlyoutViewModel(_tableView, this); - var menuItem = _optionsFlyout.Items.FirstOrDefault(x => x.Name == "ItemsCheckFlyoutItem"); - menuItem?.ApplyTemplate(); + if (_optionsFlyout.Items.FirstOrDefault(x => x.Name == "ItemsCheckFlyoutItem") is { } menuItem) + { + menuItem.Loaded += OnItemsCheckFlyoutItemLoaded; + } + + SetFilterButtonVisibility(); + EnsureGridLines(); + } + + /// + /// Handles the Loaded event for the items check flyout item. + /// + private void OnItemsCheckFlyoutItemLoaded(object sender, RoutedEventArgs e) + { + if (sender is not MenuFlyoutItem menuItem) return; + + menuItem.Loaded -= OnItemsCheckFlyoutItemLoaded; if (menuItem?.FindDescendant(x => x.Name == "SelectAllCheckBox") is { } checkBox) { @@ -237,15 +253,9 @@ protected override void OnApplyTemplate() _selectAllCheckBox.Content = TableViewLocalizedStrings.SelectAllParenthesized; _selectAllCheckBox.Checked += OnSelectAllCheckBoxChecked; _selectAllCheckBox.Unchecked += OnSelectAllCheckBoxUnchecked; + _optionsFlyoutViewModel.SetSelectAllCheckBoxState(); } -#if !WINDOWS - if (menuItem?.FindDescendant(x => x.Name is "FilterItemsList") is { } filterItemsList) - { - filterItemsList.Margin = new Thickness(12, 0, 0, 0); - } -#endif - if (menuItem?.FindDescendant(x => x.Name == "SearchBox") is { } searchBox) { _searchBox = searchBox; @@ -260,8 +270,7 @@ protected override void OnApplyTemplate() menuItem.PreviewKeyUp += static (_, e) => e.Handled = e.Key is VirtualKey.Space; } - SetFilterButtonVisibility(); - EnsureGridLines(); + _filterItemsList = menuItem?.FindDescendant(x => x.Name is "FilterItemsList"); } /// @@ -321,6 +330,11 @@ private async void OnOptionsFlyoutOpening(object? sender, object e) await Task.Delay(100); await FocusManager.TryFocusAsync(_searchBox, FocusState.Programmatic); } + + if (_filterItemsList is not null && _optionsFlyoutViewModel.FilterItems.Count > 0) + { + _filterItemsList.ScrollIntoView(_optionsFlyoutViewModel.FilterItems[0]); + } } /// diff --git a/src/Themes/TableViewColumnHeader.xaml b/src/Themes/TableViewColumnHeader.xaml index 2235df5..09199f7 100644 --- a/src/Themes/TableViewColumnHeader.xaml +++ b/src/Themes/TableViewColumnHeader.xaml @@ -1,13 +1,18 @@ + xmlns:not_win="http://uno.ui/not_win" + xmlns:converters="using:WinUI.TableView.Converters" + mc:Ignorable="not_win"> + + - - + + + + + + + + + + + + + + +