diff --git a/docs/docs/filtering.md b/docs/docs/filtering.md index 123fd979..cf9ccc6b 100644 --- a/docs/docs/filtering.md +++ b/docs/docs/filtering.md @@ -46,6 +46,14 @@ Show how many rows match each filter value: ``` +## Applying filters as they change + +By default the filter flyout applies a column's filter when OK is pressed. To apply it as soon as an item is checked or unchecked, or the search text changes, turn on `ApplyColumnFilterImmediately`. The flyout then drops its OK and Cancel buttons, and closing it keeps the filter: + +```xml + +``` + ## Filtering programmatically Add filter descriptions to `TableView.FilterDescriptions`: diff --git a/src/Controls/TableViewFilterItemsControl.xaml.cs b/src/Controls/TableViewFilterItemsControl.xaml.cs index 16d553c9..91f3e987 100644 --- a/src/Controls/TableViewFilterItemsControl.xaml.cs +++ b/src/Controls/TableViewFilterItemsControl.xaml.cs @@ -16,6 +16,8 @@ namespace WinUI.TableView.Controls; public partial class TableViewFilterItemsControl : UserControl { private bool _canSetState = true; + private bool _isClearingSearchBox; + private bool _isUpdatingSelectAllCheckBox; /// /// Initializes a new instance of the class. @@ -49,12 +51,32 @@ internal async void Initialize() /// internal void ClearSearchBox() { + // The flyout clears its search when it closes; that isn't the user changing the filter, so it mustn't + // undo a filter that was applied from the search. + _isClearingSearchBox = true; searchBox?.Text = string.Empty; + _isClearingSearchBox = false; } private void OnSearchBoxTextChanged(object sender, TextChangedEventArgs e) { FilterItems = TableView?.FilterHandler?.GetFilterItems(ColumnHeader?.Column!, searchBox!.Text); + + if (!_isClearingSearchBox) + { + ApplyFilterIfImmediate(); + } + } + + /// + /// Applies the filter as it stands, as OK would, when is on. + /// + private void ApplyFilterIfImmediate() + { + if (TableView?.ApplyColumnFilterImmediately is true) + { + ColumnHeader?.ApplyFilter(); + } } /// @@ -76,7 +98,14 @@ private void OnSearchBoxKeyDown(object sender, KeyRoutedEventArgs e) /// private void OnSelectAllCheckBoxCheckChanged(object sender, RoutedEventArgs e) { + // The checkbox also follows the items (SetSelectAllCheckBoxState); only a click on it changes the filter. + if (_isUpdatingSelectAllCheckBox) + { + return; + } + SetFilterItemsState(selectAllCheckBox.IsChecked is true); + ApplyFilterIfImmediate(); } /// @@ -90,9 +119,11 @@ internal void SetSelectAllCheckBoxState() } + _isUpdatingSelectAllCheckBox = true; selectAllCheckBox.IsChecked = FilterItems?.All(x => x.IsSelected) ?? false ? true : FilterItems?.All(x => !x.IsSelected) ?? false ? false : null; + _isUpdatingSelectAllCheckBox = false; } /// @@ -145,6 +176,12 @@ private void DetachPropertyChangedHandlers() private void OnFilterItemPropertyChanged(object? sender, PropertyChangedEventArgs e) { SetSelectAllCheckBoxState(); + + // SetFilterItemsState changes every item at once and applies once itself. + if (_canSetState && e.PropertyName is nameof(TableViewFilterItem.IsSelected)) + { + ApplyFilterIfImmediate(); + } } /// diff --git a/src/TableView.Properties.cs b/src/TableView.Properties.cs index f99856fa..46fa9b17 100644 --- a/src/TableView.Properties.cs +++ b/src/TableView.Properties.cs @@ -308,6 +308,11 @@ public partial class TableView /// public static readonly DependencyProperty ShowFilterItemsCountProperty = DependencyProperty.Register(nameof(ShowFilterItemsCount), typeof(bool), typeof(TableView), new PropertyMetadata(false)); + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ApplyColumnFilterImmediatelyProperty = DependencyProperty.Register(nameof(ApplyColumnFilterImmediately), typeof(bool), typeof(TableView), new PropertyMetadata(false)); + /// /// Identifies the dependency property. @@ -412,6 +417,17 @@ public bool ShowFilterItemsCount set => SetValue(ShowFilterItemsCountProperty, value); } + /// + /// Gets or sets a value that indicates whether a column's filter applies as soon as it changes in the filter + /// flyout (an item is checked or unchecked, or the search text changes) instead of when OK is pressed. + /// The flyout then has no OK and Cancel buttons; closing it keeps the filter. + /// + public bool ApplyColumnFilterImmediately + { + get => (bool)GetValue(ApplyColumnFilterImmediatelyProperty); + set => SetValue(ApplyColumnFilterImmediatelyProperty, value); + } + /// /// Gets or sets a value indicating whether the drag selection rectangle is shown during cell drag selection. /// diff --git a/src/TableViewFilterMenuFlyout.cs b/src/TableViewFilterMenuFlyout.cs index c4b43e99..103e382f 100644 --- a/src/TableViewFilterMenuFlyout.cs +++ b/src/TableViewFilterMenuFlyout.cs @@ -12,6 +12,7 @@ namespace WinUI.TableView; public partial class TableViewFilterMenuFlyout : Flyout { private TableViewFilterItemsControl? _filterItemsControl; + private FrameworkElement? _buttonsPanel; private readonly StandardUICommand _okCommand = new() { Label = TableViewLocalizedStrings.Ok }; private readonly StandardUICommand _cancelCommand = new() { Label = TableViewLocalizedStrings.Cancel }; @@ -44,6 +45,15 @@ protected override Control CreatePresenter() private void OnOpening(object? sender, object e) { _filterItemsControl?.Initialize(); + UpdateButtonsVisibility(); + } + + /// + /// Hides OK and Cancel when the filter applies as it changes: there's nothing left for them to do. + /// + private void UpdateButtonsVisibility() + { + _buttonsPanel?.Visibility = TableView?.ApplyColumnFilterImmediately is true ? Visibility.Collapsed : Visibility.Visible; } /// @@ -71,6 +81,8 @@ private void OnPresenterLoaded(object sender, RoutedEventArgs e) okButton?.Command = _okCommand; cancelButton?.Command = _cancelCommand; + _buttonsPanel = okButton?.Parent as FrameworkElement; + UpdateButtonsVisibility(); presenter.Loaded -= OnPresenterLoaded; _filterItemsControl?.Initialize();