WPF 之 ObservableCollection 动态数据集合的使用(二)
一、 ObservableCollection 动态数据集合
在许多情况下,我们使用的数据是对象的集合。 例如,数据绑定中的常见方案是使用 ItemsControl ListBox 、 ListView 或等 TreeView 来显示记录集合。
但是,若要设置动态绑定,以便集合中的插入或删除操作 UI 自动更新,该集合必须实现 INotifyCollectionChanged 接口。 此接口需要公开 CollectionChanged 事件,即每当基础集合发生更改时应引发的事件。
WPF 提供 ObservableCollection 类,该类是实现接口的数据集合的内置实现 INotifyCollectionChanged 。它表示一个动态数据集合,它可在添加、删除项目或刷新整个列表时提供通知。
二、ObservableCollection 与 UI 的绑定
例如,我们实现一个如下需求:
(1)显示一个数据集,内容包括学生的姓名、年龄、所属班级信息;(2)能够对该数据集进行增加、删除、移动、清空等操作。具体 UI 如下图所示:
我们先声明一个 Model,用于保存学生信息,再声明一个 ViewModel ,用于 Model 与 UI 之间进行信息同步(具体绑定步骤见 WPF 之 INotifyPropertyChanged 接口的使用 (一)):
1 /// <summary>
2 /// Model
3 /// </summary>
4 public class Student
5 {
6 public string Name { get; set; }
7 public long Age { get; set; }
8 public string Grades { get; set; }
9 }
10
11 /// <summary>
12 /// ViewModel
13 /// </summary>
14 public class Window2VM:NotifyProperty
15 {
16 private ObservableCollection<Student> _nameCollection;
17
18 public ObservableCollection<Student> NamesCollection
19 {
20 get => _nameCollection;
21 set => SetProperty(ref _nameCollection, value);
22 }
23
24 public Window2VM()
25 {
26 NamesCollection = new ObservableCollection<Student>()
27 {
28 new Student(){Name = "dwayne",Age = 12,Grades = "7年级·3班",},
29 new Student(){Name = "john",Age = 15,Grades = "8年级·5班",},
30 new Student(){Name = "linq",Age = 14,Grades = "8年级·1班",},
31 new Student(){Name = "cool",Age = 11,Grades = "7年级·6班",},
32 new Student(){Name = "team",Age = 16,Grades = "9年级·3班",},
33 };
34 }
35 }
然后把该 ViewModel 绑定到 XAML 上
1 <Window x:Class="UI.Window2"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6 xmlns:local="clr-namespace:UI"
7 mc:Ignorable="d"
8 d:DataContext="{d:DesignInstance local:Window2VM}"
9 Title="Window2" Height="400" Width="500">
10 <StackPanel>
11 <UniformGrid Columns="2" Height="120">
12 <Button Margin="5" Content="Add" Click="ButtonAdd_OnClick"></Button>
13 <Button Margin="5" Content="Remove" Click="ButtonRemove_OnClick"></Button>
14 <Button Margin="5" Content="Move" Click="ButtonMove_OnClick"></Button>
15 <Button Margin="5" Content="Clear" Click="ButtonClear_OnClick"></Button>
16 </UniformGrid>
17 <DataGrid Margin="5" Height="200" ColumnWidth="150" ItemsSource="{Binding NamesCollection}"></DataGrid>
18 </StackPanel>
19 </Window>
增删操作代码:
1 /// <summary>
2 /// Window2.xaml 的交互逻辑
3 /// </summary>
4 public partial class Window2 : Window
5 {
6 private Window2VM vm;
7 public Window2()
8 {
9 InitializeComponent();
10
11 this.DataContext=vm=new Window2VM();
12 }
13
14
15 private void ButtonAdd_OnClick(object sender, RoutedEventArgs e)
16 {
17
18 vm.NamesCollection.Add(new Student()
19 {
20 Name=$"Test-{new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds()}",
21 Age = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds()/1000,
22 Grades = $"{new Random().Next(7,10)}年级·{new Random().Next(1, 10)}班",
23 });
24 }
25
26 private void ButtonRemove_OnClick(object sender, RoutedEventArgs e)
27 {
28 vm.NamesCollection.RemoveAt(0);
29 }
30
31 private void ButtonMove_OnClick(object sender, RoutedEventArgs e)
32 {
33 vm.NamesCollection.Move(vm.NamesCollection.Count-1,0);
34 }
35
36 private void ButtonClear_OnClick(object sender, RoutedEventArgs e)
37 {
38 vm.NamesCollection.Clear();
39 }
40 }
三、ObservableCollection 的 CollectionChanged 事件
如果我们增加一个搜素功能,即输入学生姓名或年龄或班级,自动显示相关的集合,如下图所示:
我们可以一个集合用来保存学生信息,一个集合用来显示,同时当保存学生信息的集合发生变化时,显示信息的集合要同步变化,这就需要用到ObservableCollection 的 CollectionChanged 事件,具体实现如下:
1 /// <summary>
2 /// ViewModel
3 /// </summary>
4 public class Window2VM:NotifyProperty
5 {
6 private ObservableCollection<Student> _displayName;
7 private string _searchContent;
8
9 /// <summary>
10 /// 保存学生信息的集合
11 /// </summary>
12 public ObservableCollection<Student> NamesCollection { get; set; }
13
14 /// <summary>
15 /// 展示学生信息的集合
16 /// </summary>
17 public ObservableCollection<Student> DisplayName
18 {
19 get => _displayName;
20 set => SetProperty(ref _displayName, value);
21 }
22
23 /// <summary>
24 /// 搜索内容
25 /// </summary>
26 public string SearchContent
27 {
28 get => _searchContent;
29 set => SetProperty(ref _searchContent, value);
30 }
31
32 public Window2VM()
33 {
34 _searchContent = "";
35 _displayName = new ObservableCollection<Student>();
36 NamesCollection = new ObservableCollection<Student>();
37
38 // 同步集合
39 NamesCollection.CollectionChanged += (sender, e) =>
40 {
41 {
42 switch (e.Action)
43 {
44 case NotifyCollectionChangedAction.Add:
45 var items = (object[])(e.NewItems.SyncRoot);
46 foreach (var item in items)
47 {
48 _displayName.Add(item as Student);
49 }
50 break;
51 case NotifyCollectionChangedAction.Remove:
52 _displayName.RemoveAt(e.OldStartingIndex);
53 break;
54 case NotifyCollectionChangedAction.Reset:
55 _displayName.Clear();
56 break;
57 case NotifyCollectionChangedAction.Move:
58 _displayName.Move(e.OldStartingIndex, e.NewStartingIndex);
59 break;
60 case NotifyCollectionChangedAction.Replace:
61 break;
62 }
63 };
64 };
65 NamesCollection.Add(new Student() { Name = "dwayne", Age = 12, Grades = "7年级·3班", });
66 NamesCollection.Add(new Student() { Name = "john", Age = 15, Grades = "8年级·5班", });
67 NamesCollection.Add(new Student() { Name = "linq", Age = 14, Grades = "8年级·1班", });
68 NamesCollection.Add(new Student() { Name = "cool", Age = 11, Grades = "7年级·6班", });
69 NamesCollection.Add(new Student() { Name = "team", Age = 16, Grades = "9年级·3班", });
70 }
71 }
搜索功能,我们可以优化一下,当搜索文本框内容发生变化时,显示集合自动发生变更,先添加 XAML代码如下:
1 <StackPanel Orientation="Horizontal" Height="50">
2 <TextBox Margin="5" Width="360" BorderBrush="Black" VerticalContentAlignment="Center" Text="{Binding Path=SearchContent,UpdateSourceTrigger=PropertyChanged}" TextChanged="TextBoxBase_OnTextChanged"></TextBox>
3 <Button Margin="5" Width="100" Content="Search" Click="ButtonSearch_OnClick"></Button>
4 </StackPanel>
后台代码如下:
1 private void ButtonSearch_OnClick(object sender, RoutedEventArgs e)
2 {
3 SearchCommand();
4 }
5
6 private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
7 {
8 SearchCommand();
9 }
10
11 private void SearchCommand()
12 {
13 vm.DisplayName = new ObservableCollection<Student>(vm.NamesCollection.Where(student =>
14 (student.Name.Contains(vm.SearchContent) || student.Age.ToString().Contains(vm.SearchContent) ||
15 student.Grades.Contains(vm.SearchContent))).ToList());
16 }