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();