Windows 窗体数据绑定最重要的概念之一是更改通知。 为确保数据源和绑定控件始终具有最新数据,必须为数据绑定添加更改通知。 具体来说,你希望确保绑定控件在对其数据源进行更改时得到通知。 数据源在对控件的绑定属性进行更改时得到通知。
根据数据绑定的类型,有不同类型的更改通知:
简单绑定,其中单个控件属性绑定到对象的单个实例。
基于列表的绑定,它可以包括绑定到列表中项属性的单个控件属性或绑定到对象列表的控件属性。
此外,如果正在创建要用于数据绑定的 Windows 窗体控件,必须将 PropertyNameChanged 模式应用于控件。 将模式应用于控件后,会将对控件的绑定属性的更改传播到数据源。
对于简单绑定,业务对象必须在绑定属性的值更改时提供更改通知。 可以通过为业务对象的每个属性公开一个 PropertyNameChanged 事件来提供更改通知。 同时需要使用?BindingSource?或首选方法将业务对象绑定到控件,在该方法中业务对象实现?INotifyPropertyChanged?接口并在属性值更改时引发?PropertyChanged?事件。 使用实现?INotifyPropertyChanged
?接口的对象时,不必使用?BindingSource
?将对象绑定到控件。 但建议使用?BindingSource
。
Windows 窗体依靠绑定列表来向绑定控件提供提供属性更改和列表更改信息。 属性更改是更改列表项属性值,列表更改是从列表中删除项或向列表添加项。 因此,用于数据绑定的列表必须实现?IBindingList,它提供两种类型的更改通知。?BindingList<T>?是?IBindingList
?的通用实现,旨在与 Windows 窗体数据绑定一起使用。 可以创建一个?BindingList
,其中包含实现?INotifyPropertyChanged?的??业务对象类型,并且该列表将自动将?PropertyChanged?事件转换为?ListChanged?事件。 如果绑定列表不是?IBindingList
,必须使用?BindingSource?组件将对象列表绑定到 Windows 窗体控件。?BindingSource
?组件将提供属性到列表的转换,该转换类似于?BindingList
?的属性到列表的转换。?
最后,在控件端,必须为每个旨在绑定到数据的属性公开一个 PropertyNameChanged?事件。 然后将对控件属性的更改传播到绑定的数据源。?
下面的代码示例演示如何将 PropertyNameChanged 模式应用于自定义控件。 在实现与 Windows 窗体数据绑定引擎一起使用的自定义控件时,请应用该模式。
// This class implements a simple user control
// that demonstrates how to apply the propertyNameChanged pattern.
[ComplexBindingProperties("DataSource", "DataMember")]
public class CustomerControl : UserControl
{
private DataGridView dataGridView1;
private Label label1;
private DateTime lastUpdate = DateTime.Now;
public EventHandler DataSourceChanged;
public object DataSource
{
get
{
return this.dataGridView1.DataSource;
}
set
{
if (DataSource != value)
{
this.dataGridView1.DataSource = value;
OnDataSourceChanged();
}
}
}
public string DataMember
{
get { return this.dataGridView1.DataMember; }
set { this.dataGridView1.DataMember = value; }
}
private void OnDataSourceChanged()
{
if (DataSourceChanged != null)
{
DataSourceChanged(this, new EventArgs());
}
}
public CustomerControl()
{
this.dataGridView1 = new System.Windows.Forms.DataGridView();
this.label1 = new System.Windows.Forms.Label();
this.dataGridView1.ColumnHeadersHeightSizeMode =
System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView1.ImeMode = System.Windows.Forms.ImeMode.Disable;
this.dataGridView1.Location = new System.Drawing.Point(100, 100);
this.dataGridView1.Size = new System.Drawing.Size(500,500);
this.dataGridView1.TabIndex = 1;
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(50, 50);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(76, 13);
this.label1.TabIndex = 2;
this.label1.Text = "Customer List:";
this.Controls.Add(this.label1);
this.Controls.Add(this.dataGridView1);
this.Size = new System.Drawing.Size(450, 250);
}
}
下面的代码示例演示如何实现?INotifyPropertyChanged?接口。 在 Windows 窗体数据绑定中使用的业务对象上实现该接口。 实现时,该接口将业务对象上的属性更改与绑定控件进行通信。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
// Change the namespace to the project name.
namespace binding_control_example
{
// This form demonstrates using a BindingSource to bind
// a list to a DataGridView control. The list does not
// raise change notifications. However the DemoCustomer1 type
// in the list does.
public partial class Form3 : Form
{
// This button causes the value of a list element to be changed.
private Button changeItemBtn = new Button();
// This DataGridView control displays the contents of the list.
private DataGridView customersDataGridView = new DataGridView();
// This BindingSource binds the list to the DataGridView control.
private BindingSource customersBindingSource = new BindingSource();
public Form3()
{
InitializeComponent();
// Set up the "Change Item" button.
this.changeItemBtn.Text = "Change Item";
this.changeItemBtn.Dock = DockStyle.Bottom;
this.changeItemBtn.Height = 100;
//this.changeItemBtn.Click +=
// new EventHandler(changeItemBtn_Click);
this.Controls.Add(this.changeItemBtn);
// Set up the DataGridView.
customersDataGridView.Dock = DockStyle.Top;
this.Controls.Add(customersDataGridView);
this.Size = new Size(400, 200);
}
private void Form3_Load(object sender, EventArgs e)
{
this.Top = 100;
this.Left = 100;
this.Height = 600;
this.Width = 1000;
// Create and populate the list of DemoCustomer objects
// which will supply data to the DataGridView.
BindingList<DemoCustomer1> customerList = new ();
customerList.Add(DemoCustomer1.CreateNewCustomer());
customerList.Add(DemoCustomer1.CreateNewCustomer());
customerList.Add(DemoCustomer1.CreateNewCustomer());
// Bind the list to the BindingSource.
this.customersBindingSource.DataSource = customerList;
// Attach the BindingSource to the DataGridView.
this.customersDataGridView.DataSource =
this.customersBindingSource;
}
// Change the value of the CompanyName property for the first
// item in the list when the "Change Item" button is clicked.
void changeItemBtn_Click(object sender, EventArgs e)
{
// Get a reference to the list from the BindingSource.
BindingList<DemoCustomer1>? customerList =
this.customersBindingSource.DataSource as BindingList<DemoCustomer1>;
// Change the value of the CompanyName property for the
// first item in the list.
customerList[0].CustomerName = "Tailspin Toys";
customerList[0].PhoneNumber = "(708)555-0150";
}
}
// This is a simple customer class that
// implements the IPropertyChange interface.
public class DemoCustomer1 : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Guid idValue = Guid.NewGuid();
private string customerNameValue = String.Empty;
private string phoneNumberValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// The constructor is private to enforce the factory pattern.
private DemoCustomer1()
{
customerNameValue = "Customer";
phoneNumberValue = "(312)555-0100";
}
// This is the public factory method.
public static DemoCustomer1 CreateNewCustomer()
{
return new DemoCustomer1();
}
// This property represents an ID, suitable
// for use as a primary key in a database.
public Guid ID
{
get
{
return this.idValue;
}
}
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
NotifyPropertyChanged();
}
}
}
public string PhoneNumber
{
get
{
return this.phoneNumberValue;
}
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
NotifyPropertyChanged();
}
}
}
}
}
在 Windows 窗体中实现数据绑定期间,多个控件会绑定到同一数据源。 在某些情况下,可能需要采取额外的步骤来确保控件的绑定属性之间,以及它们和数据源之间保持同步。 在两种情况下,需要执行以下步骤:
数据源未实现?IBindingList,因此生成类型为?ItemChanged?的?ListChanged?事件。
数据源实现了?IEditableObject。
在前一种情况下,请使用?BindingSource?将数据源绑定到控件。 在后一种情况下,请使用?BindingSource
?并处理?BindingComplete?事件,然后在相关的?BindingManagerBase?上调用?EndCurrentEdit。