【转】WPF 中双向绑定通知机制之OBSERVABLECOLLECTION使用
WPF 中双向绑定通知机制之ObservableCollection使用
msdn中 ObservableCollection<T> 类 表示一个动态数据集合,在添加项、移除项或刷新整个列表时,此集合将提供通知。
在许多情况下,所使用的数据是对象的集合。 例如,数据绑定中的一个常见方案是使用 ItemsControl(如 ListBox、ListView 或 TreeView)来显示记录的集合。
可以枚举实现 IEnumerable 接口的任何集合。 但是,若要设置动态绑定,以便集合中的插入或删除操作可以自动更新 UI,则该集合必须实现 INotifyCollectionChanged 接口。 此接口公开 CollectionChanged 事件,只要基础集合发生更改,都应该引发该事件。
WPF 提供 ObservableCollection<T> 类,它是实现 INotifyCollectionChanged 接口的数据集合的内置实现。
还有许多情况,我们所使用的数据只是单纯的字段或者属性,此时我们需要为这些字段或属性实现INotifyPropertyChanged接口,实现了该接口,只要字段或属性的发生了改变,就会提供通知机制。
ObservableCollection<T>实现
前台xmal
<Window x:Class="WpfApplication1.WindowObservable" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window8" Height="356" Width="471"> <Grid> <StackPanel Height="295" HorizontalAlignment="Left" Margin="10,10,0,0" Name="stackPanel1" VerticalAlignment="Top" Width="427"> <TextBlock Height="23" Name="textBlock1" Text="学员编号:" /> <TextBox Height="23" Name="txtStudentId" Width="301" HorizontalAlignment="Left"/> <TextBlock Height="23" Name="textBlock2" Text="学员列表:" /> <ListBox Height="156" Name="lbStudent" Width="305" HorizontalAlignment="Left"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Name="stackPanel2" Orientation="Horizontal"> <TextBlock Text="{Binding Id,Mode=TwoWay}" Margin="5" Background="Beige"/> <TextBlock Text="{Binding Name,Mode=TwoWay}" Margin="5"/> <TextBlock Text="{Binding Age,Mode=TwoWay}" Margin="5"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Button Content="Button" Height="23" Name="button1" Width="75" HorizontalAlignment="Left" Click="button1_Click" /> </StackPanel> </Grid> </Window>
后台cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; 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.Shapes; using System.Collections.ObjectModel; using System.ComponentModel; namespace WpfApplication1 { public partial class WindowObservable : Window { ObservableCollection<Students> infos = new ObservableCollection<Students>() { new Students(){ Id=1, Age=11, Name="Tom"}, new Students(){ Id=2, Age=12, Name="Darren"}, new Students(){ Id=3, Age=13, Name="Jacky"}, new Students(){ Id=4, Age=14, Name="Andy"} }; public WindowObservable() { InitializeComponent(); this.lbStudent.ItemsSource = infos; this.txtStudentId.SetBinding(TextBox.TextProperty, new Binding("SelectedItem.Id") { Source = lbStudent }); } private void button1_Click(object sender, RoutedEventArgs e) { infos[1] = new Students() { Id = 4, Age = 14, Name = "这是一个集合改变" }; infos[2].Name = "这是一个属性改变"; } public class Students { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } } } }
在这个例子中我们将Students数据对象用ObservableCollection<T>来修饰。这样当我们点击click的时候我们看到。当我们点击后只有student整个对象的改变引发了后台通知机制。
INotifyPropertyChanged实现
INotifyPropertyChanged会向客户端发出某一属性值已更改的通知。当元素属性值改变时,会通知后台model
前台代码不变,我们让后台Students Model实现INotifyPropertyChanged接口。
using System; using System.Collections.Generic; using System.Linq; using System.Text; 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.Shapes; using System.Collections.ObjectModel; using System.ComponentModel; namespace WpfApplication1 { public partial class WindowObservable : Window { ObservableCollection<Students> infos = new ObservableCollection<Students>() { new Students(){ Id=1, Age=11, Name="Tom"}, new Students(){ Id=2, Age=12, Name="Darren"}, new Students(){ Id=3, Age=13, Name="Jacky"}, new Students(){ Id=4, Age=14, Name="Andy"} }; public WindowObservable() { InitializeComponent(); this.lbStudent.ItemsSource = infos; this.txtStudentId.SetBinding(TextBox.TextProperty, new Binding("SelectedItem.Id") { Source = lbStudent }); } private void button1_Click(object sender, RoutedEventArgs e) { infos[1] = new Students() { Id = 4, Age = 14, Name = "这是一个集合改变" }; infos[2].Name = "这是一个属性改变"; } public class Students : INotifyPropertyChanged { string _name; public int Id { get; set; } public string Name { get { return _name; } set { _name = value; OnPropertyChanged("Name"); } } public int Age { get; set; } protected internal virtual void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } public event PropertyChangedEventHandler PropertyChanged; } } }
此时我们再 运行代码会发现
使用DataContext为页面对象设置上下文
不管是集合还是对象都发生了改变。至此。我们的整个后台通知就能完美监视任何对象变动。
但是现在还有一个问题。我们如果在点击事件里面给infos赋值一个新的集合数据。如下
private void button1_Click(object sender, RoutedEventArgs e) { infos[1] = new Students() { Id = 4, Age = 14, Name = "这是一个集合改变" }; infos[2].Name = "这是一个属性改变";
infos = new ObservableCollection<Students>() { new Students(){ Id=1, Age=11, Name="这是改变后的集合"}, new Students(){ Id=2, Age=12, Name="这是改变后的集合"}, new Students(){ Id=3, Age=13, Name="这是改变后的集合"}, new Students(){ Id=4, Age=14, Name="这是改变后的集合"} };
}
会发现数据并没有变更。这是为什么?我们明明实现了ObservableCollection<T>类型啊。这是因为infos这个集合的地址变更并没有实现通知机制。当我们new一个对象赋值给infos时候,infos的地址指向变更了。所以集合里面数据的变化,infos变更后的数据就参与绑定了。我们这时候可以通过DataContext实现数据数据项的变更通知。我们添加一个ViewModel类,实现INotifyPropertyChanged接口
public class ViewModel : INotifyPropertyChanged { private ObservableCollection<Students> studentList; public ObservableCollection<Students> StudentList { get { return this.studentList; } set { if (this.studentList != value) { this.studentList = value; OnPropertyChanged("StudentList"); } } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = this.PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } }
windowsObservable类修改为如下
public partial class WindowObservable : Window { ViewModel viewModel = new ViewModel(); public WindowObservable() { InitializeComponent(); viewModel.StudentList = new ObservableCollection<Students>() { new Students(){ Id=1, Age=11, Name="Tom"}, new Students(){ Id=2, Age=12, Name="Darren"}, new Students(){ Id=3, Age=13, Name="Jacky"}, new Students(){ Id=4, Age=14, Name="Andy"} }; this.lbStudent.DataContext = viewModel; } private void button1_Click(object sender, RoutedEventArgs e) { viewModel.StudentList[1] = new Students() { Id = 4, Age = 14, Name = "这是一个集合改变" }; viewModel.StudentList = new ObservableCollection<Students>() { new Students(){ Id=19, Age=111, Name="这是变化后的几何"}, new Students(){ Id=29, Age=121, Name="这是变化后的几何"}, new Students(){ Id=39, Age=131, Name="这是变化后的几何"}, new Students(){ Id=49, Age=141, Name="这是变化后的几何"} }; viewModel.StudentList[2].Name = "这是一个属性改变"; } }
我们给xaml listbox设置如下绑定
ItemsSource="{Binding StudentList, Mode=TwoWay}"
运行程序我们发现集合的改变也会被通知到前台。