Why I cannot Filter my data by Dynamic Data?

95 views Asked by At

I want to filter out the data containing letter 1, 2, 3.

Here is my code.

XAML:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid VerticalAlignment="Top">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="auto"></ColumnDefinition>
                <ColumnDefinition Width="auto"></ColumnDefinition>
                <ColumnDefinition Width="auto"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Button Click="Button_Click">1</Button>
            <Button Grid.Column="1" Click="Button_Click_1">2</Button>
            <Button Grid.Column="2" Click="Button_Click_2">3</Button>
        </Grid>
        <StackPanel Grid.Row="1">
            <ListBox x:Name="LB">

            </ListBox>
        </StackPanel>
    </Grid>
</Window>

Code-behind:

using DynamicData.Binding;
using DynamicData;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            for (int i = 0; i <= 100; i++)
            {
                SourceList.Add(i.ToString());
            }
            SourceList.Connect().Bind(this.BindableList).Subscribe();
            LB.ItemsSource = BindableList;
        }
        private readonly SourceList<string> SourceList = new SourceList<string>();
        public IObservableCollection<string> BindableList { get; } = new ObservableCollectionExtended<string>();

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            SourceList.Connect().Filter(_ => _.Contains("1")).Bind(this.BindableList).Subscribe();
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            SourceList.Connect().Filter(_ => _.Contains("2")).Bind(this.BindableList).Subscribe();
        }

        private void Button_Click_2(object sender, RoutedEventArgs e)
        {
            SourceList.Connect().Filter(_ => _.Contains("3")).Bind(this.BindableList).Subscribe();
        }
    }
}

It seems it only sorts it but does not filter it. The data I don't need is still in the ListBox.

PS: I dislike ReactiveUI and I don't want to use Dynamic Data with this.

What's wrong with my code?

2

There are 2 answers

0
Davorin Mestric On BEST ANSWER

In the code you gave, all new copies of the SourceList pipelines bind to the same ObservableCollection, so the strings just pile on to it, and it seems that the last one bound gets to dump its items first.

Dynamic Data library .Filter operator accepts an IObservable of filter predicates, so you can do this, when connecting:

 SourceList
     .Connect()
     .Filter(_filter)
     .Bind(this.BindableList)
     .Subscribe();
  ...
 private BehaviorSubject<Func<string, bool>> _filter = new(x => true);

And, then just push another filter on your button events:

```
private void Button_Click(object sender, RoutedEventArgs e)
 {
     _filter.OnNext(_ => _.Contains("1"));
 }
```
2
BionicCode On

You should filter the ObservableCollectionExtended directly because it is the source of the ListBox.
Use the static CollectionViewSource.GetDefaultView method to obtain the collection's default view.
In WPF the binding engine will observe the default ICollectionView of the source collection. The ItemsControl will also work on the ICollectionView.
Therefore filtering, sorting and grouping in the view is usually done using the ICollectionView.

In your case, simply assign a filter predicate of type Predicate<object> to the ICollectionView.Filter property:

private void Button_Click(object sender, RoutedEventArgs e)
{
  ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.BindableList);
  collectionView.Filter = item => (items as string).Contains("1");
}