'WPF Loose binding when filtering a list

I have a list of users below:

public ObservableCollection<User> Users
   {
     get;
     private set;
   }

And in the XAML file, I am binding Users to the grid control

<dxg:GridControl x:Name="grid" 
     ItemsSource="{Binding Users}"
     SelectedItem="{Binding CurrentUser}"
     ...
     >
    <dxg:GridControl.Columns>
        <dxg:GridColumn FieldName="Name" Header="name" />
        <dxg:GridColumn FieldName="Mobile" Header="mobile"/>
    </dxg:GridControl.Columns>
</dxg:GridControl>

Until here everything is Ok and the binding works correctly.

Now I want to bind special users. for example, users where the length of their names is less than 5.

private ObservableCollection<TUserItem> usersVisible;
public ObservableCollection<User> UsersVisible
   {
       get
       {
           return Users.Where(u => u.name.Length < 5).ToObservableCollection();
       }

       set
       {
           usersVisible = value;
           OnPropertyChanged();
       }
}

but it doesn't work and the XAML grid control doesn't update.

If I return

return Users;

instead of

return Users.Where(u => u.name.Length < 5).ToObservableCollection();

everything is ok and the binding works correctly.

What's your suggestion?

Thanks



Solution 1:[1]

If you look up the source code of the method ToObservableCollection, you will find out that it returns a new collection. So your binding now points to the old objects still and you will not see any change. I have created a repo of a WPF sample for you that you can run here: https://github.com/toreaurstadboss/WpfFilteredCollection

First I define a User class for demonstration:

namespace WpfFilteredCollection
{
    public class User
    {
        public string Name { get; set; }
        public bool IsAdmin { get; set; }
    }
}

I then create a MainWindowViewModel which I add the INotifyPropertyChanged interface to signal change to the UI and also use ICollectionViewSource. This is an interface which together with CollectionViewSource supports sorting and filtering. It is part of System.Windows.Data and has been around for a long time.

MainWindowViewModel looks like this:

using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Data;

namespace WpfFilteredCollection
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {

        private readonly ICollectionView _usersView;

        public event PropertyChangedEventHandler? PropertyChanged;

        public void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public ICollectionView Users
        {
            get { return _usersView; }
        }

        public MainWindowViewModel()
        {
            IList<User> users = GetUsers();
            _usersView = CollectionViewSource.GetDefaultView(users);
            _usersView.Filter = (object u) => u != null && (u as User)?.Name?.Length <= FilterNameLength;
            FilterNameLength = 6;
        }

        private IList<User> GetUsers()
        {
            return new List<User>
            {
                new User { Name = "Bob", IsAdmin = false },
                new User { Name = "Alice", IsAdmin = false }
            };
        }

        private int _filterNameLength = 0;
        public int FilterNameLength
        {
            get { return _filterNameLength; }
            set
            {
                if (_filterNameLength != value)
                {
                    _filterNameLength = value;
                    RaisePropertyChanged("FilterNameLength");
                    _usersView.Refresh();
                }

            }
        }


    }
}

MainWindow.xaml.cs code behind looks like this, setting DataContext to an instance of the view model or 'VM'.

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 WpfFilteredCollection
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainWindowViewModel(); 
        }
    }
}

And we have the UI, which only uses standard stuff (components) in WPF. The textbox looks terrible, as WPF lacks a proper built-in numeric textbox without having to add behaviors.

<Window x:Class="WpfFilteredCollection.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:WpfFilteredCollection"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition />

        </Grid.RowDefinitions>

        <Label Margin="10" Height="28" Grid.Row="0">Filter on length (name of user): </Label>

        <TextBox Margin="10" Width="100" HorizontalAlignment="Left" Background="AliceBlue" Grid.Row="1" Height="28" Text="{Binding FilterNameLength, UpdateSourceTrigger=PropertyChanged}"></TextBox>

        <ListView Grid.Row="2" Margin="10" ItemsSource="{Binding Users}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Name" Width="300" DisplayMemberBinding="{Binding Name}" />
                    <GridViewColumn Header="IsAdmin" Width="100" DisplayMemberBinding="{Binding IsAdmin}" />
                </GridView>
            </ListView.View>       
        </ListView>

    </Grid>
</Window>

But my solution uses standard parts of the .net framework and should therefore be used in many different scenarios. For larger amounts of data, you might consider using more advanced collections and components, that supports 'virtualization', i.e. rendering only visible rows and so on.

To test out the solution, just alter the textbox value from 6 down to 4 to only see Bob and not Bob and Alice.

UI of filtered collection

Note that I have not used an observable collection, but a plain list. You can see a sample of an observable collection using collection view source in this answer on SO: Binding a CollectionViewSource to ObservableCollection

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tore Aurstad