WPF 过滤列表时松散绑定

WPF Loose binding when filtering a list

我有以下用户列表:

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

并且在 XAML 文件中,我将用户绑定到网格控件

<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>

到这里为止一切正常,绑定工作正常。

现在我要绑定特殊用户。例如,姓名长度小于 5 的用户。

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

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

但它不起作用,XAML 网格控件没有更新。

如果我return

return Users;

而不是

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

一切正常,绑定工作正常。

你有什么建议?

谢谢

如果您查看方法 ToObservableCollection 的源代码,您会发现它 returns 一个 新集合 。所以你的绑定现在仍然指向旧对象,你不会看到任何变化。我已经为您创建了一个 WPF 示例的存储库,您可以在此处 运行: https://github.com/toreaurstadboss/WpfFilteredCollection

首先我定义一个用户class来演示:

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

然后我创建了一个 MainWindowViewModel,我添加了 INotifyPropertyChanged 接口来指示 UI 的变化,并且还使用 ICollectionViewSource .这是一个与 CollectionViewSource 一起支持排序和过滤的接口。它是 System.Windows.Data 的一部分,已经存在很长时间了。

MainWindowViewModel 如下所示:

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 代码如下所示,将 DataContext 设置为视图模型的实例或 '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(); 
        }
    }
}

我们还有 UI,它只使用 WPF 中的标准内容(组件)。文本框看起来很糟糕,因为 WPF 缺少适当的 built-in 数字文本框而无需添加行为。

<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>

但我的解决方案使用了 .net 框架的标准部分,因此应该在许多不同的场景中使用。对于大量数据,您可以考虑使用更高级的集合和组件,支持 'virtualization',即仅呈现可见行等。

要测试该解决方案,只需将文本框值从 6 降低到 4,以便只看到 Bob 而看不到 Bob 和 Alice。

请注意,我没有使用可观察集合,而是使用了普通列表。您可以在 SO 的这个答案中使用集合视图源查看可观察集合的示例: Binding a CollectionViewSource to ObservableCollection