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
我有以下用户列表:
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