从一个
Windows
窗体的角度来看,“数据绑定”是一种把数据绑定到一种用户界面元素(控件)的通用机制。在
Windows
窗体中有两种数据绑定类型:简单绑定和复杂绑定。
简单绑定
简单绑定是将一个用户界面元素(控件)的属性绑定到一个类型(对象)实例上的某个属性的方法。例如,如果一个开发者有一个
Customer
类型的实例,那么他就可以把
Customer
的“
Name
”属性绑定到一个
TextBox
的“
Text
”属性上。“绑定”了这
2
个属性之后,对
TextBox
的
Text
属性的更改将“传播”到
Customer
的
Name
属性,而对
Customer
的
Name
属性的更改同样会“传播”到
TextBox
的
Text
属性。
Windows
窗体的简单数据绑定支持绑定到任何
public
或者
internal
级别的
.NET Framework
属性。
例子:对一个业务对象的简单数据绑定
/*******************************************************************
* 设置(使用VS的窗体设计器):
*
* 添加3个 TextBox到窗体Form(textBox1, textBox2 和textBox3)
* 添加下面的代码到Form.Load事件处理方法
******************************************************************/
/*******************************************************************
* 创建一个 customer 实例(使用下面的 Customer 类型)
******************************************************************/
Customer
cust =
new
Customer
(0,
"Mr. Zero"
, 10.0M);
/*******************************************************************
* 绑定textBox1, textBox2 和textBox3
******************************************************************/
this
.textBox1.DataBindings.Add(
"Text"
, cust,
"ID"
,
true
);
this
.textBox2.DataBindings.Add(
"Text"
, cust,
"Name"
,
true
);
this
.textBox2.DataBindings.Add(
"Text"
, cust,
"Rate"
,
true
);
Customer
业务对象的定义
:
/*******************************************************************
* 设置(使用 Visual Studio 窗体设计器):
*
* 添加一个新的 C# 类文件到你的项目 并且把它命名为 “Customer.cs”
* 用下面的那个Customer类取代自动生成的Customer类代码
******************************************************************/
public
class Customer
{
/* 私有变量 */
private int _id;
private string _name;
private Decimal _rate;
/* 构造函数 */
public Customer()
{
this.ID = -1;
this.Name = string.Empty;
this.Rate = 0.0M;
}
public Customer(int id, string name, Decimal rate)
{
this.ID = id;
this.Name = name;
this.Rate = rate;
}
/* 公共属性 */
public int ID
{
get { return _id; }
set { _id = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public Decimal Rate
{
get { return _rate; }
set { _rate = value; }
}
}
复杂数据绑定
复杂数据绑定是把一个基于列表的用户界面元素(比如
ComboBox
、
Grid
)绑定到一个数据实例列表(比如
DataTable
)的方法。和简单数据绑定一样,复杂数据绑定通常也是用户界面元素发生改变时传播到数据列表,数据列表发生改变时传播到用户界面元素。
Windows
窗体复杂数据绑定支持绑定到那些支持
IList
接口(或者是
IEnumerable
接口,如果使用的是
BindingSource
组件的话)的数据列表。
例子:复杂数据绑定
(VS 2005)
/*******************************************************************
* 设置 (使用 Visual Studio 窗体设计器):
*
* 添加一个DataGridVie到窗体Form (dataGridView1)
* 添加下面的代码到Form.Load事件响应方法
******************************************************************/
/*******************************************************************
* 创建一个Customer列表. 这个列表实例被命名为 blc.
* 注意: Customer 有这些属性: ID, Name and Rate
******************************************************************/
BindingList
<Customer> blc = new BindingList<Customer>();
blc.Add(new Customer(0, "Mr. Zero", 10.0M));
blc.Add(new Customer(1, "Mrs. One", 15.0M));
blc.Add(new Customer(2, "Dr. Two", 20.0M));
/*******************************************************************
* 利用复杂数据绑定将DataGridView绑定到Customer列表
* 绑定(customer业务类型在上面有显示).
******************************************************************/
this
.dataGridView1.DataSource = blc;
针对列表的简单数据绑定
简单数据绑定有
2
种形式:属性对属性的绑定(前面描述过的那种绑定);属性对“一个列表中的某一项的属性”的绑定。属性对“一个列表中的某一项的属性”的绑定形式上和属性对属性的绑定是一样的,除了数据源是一个条目
(Item)
列表而非单个条目
(Item)
(比如,是
BindingList<Customer>
而不是
Customer
)。当使用简单数据绑定绑定到一个列表时,
Windows
窗体数据绑定运行时
(runtime)j
将用户界面元素属性绑定到列表中的某一项上的一个属性。默认情况下,运行时
(runtime)
将绑定到列表中的第一项(例如,绑定
TextBox
的
Text
属性到
Customers[0].Name
),如果同一个列表被绑定到另外一个使用复杂数据绑定的控件上(正如下面的例子),
Windows
窗体运行时
(runtime)
将自动根据复杂数据绑定控件中的当前选中项同步简单数据绑定控件中的属性
.
,例如,一个开发者可以用简单数据绑定把一个
TextBox
的
Text
属性绑定到一个
Customer
列表,然后他们可以用复杂数据绑定把同一个列表绑定到一个
Grid
控件。当他们这么做了之后,随着他们在
Grid
中选择不同的条目,
TextBox
的
Text
属性就会自动的重新绑定到
Grid
的当前选中条目(比如,
TextBox
将会显示当前选中的
Customer
的
Name
)。在
Windows
窗体中,保持不同的绑定项目的同步性被称作“
Currency Management
”(字面翻译就是“流通管理”),核心的
Windows
窗体“
Currentcy Management
”引擎被一个叫做“
CurrencyManager
”的类型所实现。
例子:简单数据绑定到一个列表
/*******************************************************************
*设置 (使用 Visual Studio 窗体设计器)
*
* 添加一个DataGridVie到窗体Form (dataGridView1)
* 添加3个 TextBox到窗体Form(textBox1, textBox2 和textBox3)
* 添加下面的代码到Form.Load事件响应方法
******************************************************************/
/*******************************************************************
*创建一个Customer列表. 这个列表实例被命名为 blc
*注意: Customer 有这些属性: ID, Name and Rate
******************************************************************/
BindingList
<Customer> blc = new BindingList<Customer>();
blc.Add(new Customer(0, "Mr. Zero", 10.0M));
blc.Add(new Customer(1, "Mrs. One", 15.0M));
blc.Add(new Customer(2, "Dr. Two", 20.0M));
/*******************************************************************
*
利用复杂数据绑定将DataGridView绑定到Customer列表
* 绑定(customer业务类型在上面有显示).
******************************************************************/
this
.dataGridView1.DataSource = blc;
/*******************************************************************
* 绑定业务对象列表到TextBoxe. 这里使用简单数据绑定
* 绑定到一个列表中的某一项的属性上
******************************************************************/
this
.textBox1.DataBindings.Add("Text", blc, "ID", true);
this
.textBox2.DataBindings.Add("Text", blc, "Name", true);
this
.textBox3.DataBindings.Add("Text", blc, "Rate", true);
Windows
窗体数据绑定引擎不仅可以保证属性和列表的同步,它还提供一些常用的服务来帮助简化这个处理过程。数据绑定引擎提供以下服务:
类型转换
(Type Conversion)
如果需要的话,
Windows
窗体将执行类型转换作为绑定处理的一部分。例如,如果一个业务对象的整型
(int)
类型的属性(比如
Customer.ID
)被绑定到一个控件的字符串
(string)
类型的属性(比如
TextBox.Text
)上时,数据绑定运行时将把整型值和字符串值互相转换。
Windows
窗体利用
TypeConverter
、
IFormattable
和
IConvertible
来执行类型转换。
格式化
Windows
窗体绑定支持利用
.NET Framework
格式化字符串来格式化目标数据。比如,当绑定一个业务对象的
Decimal
类型的属性(比如
Order.Total
)到一个控件的字符串类型的属性(比如
TextBox.Text
),一个格式化字符串(比如“
c
”)可以被指定,因此这个值可以被显示为本地化的货币显示形式(比如¥
1.10
),
Windows
窗体利用
IFormattable
来进行字符串格式化。
例子:格式化
/*******************************************************************
*设置 (使用 Visual Studio 窗体设计器):
*
* 添加 2个TextBoxe 到 Form (textBox1 和textBox2)
* 添加下面的代码到Form.Load事件响应方法
******************************************************************/
/*******************************************************************
* 数据源设置
*
* 创建一个叫做Numbers的表,并添加3列
* ID: int
* Name: string
* Cost: Decimal
******************************************************************/
DataTable
_dt;
_dt =
new
DataTable
(
"Numbers"
);
_dt.Columns.Add(
"ID"
,
typeof
(
int
));
_dt.Columns.Add(
"Name"
,
typeof
(
string
));
_dt.Columns.Add(
"Cost"
,
typeof
(
decimal
));
_dt.Rows.Add(0,
"Zero"
, 10.0M);
_dt.Rows.Add(1,
"One"
, 11.1M);
_dt.Rows.Add(2,
"Two"
, 12.2M);
/*******************************************************************
* 绑定 TextBox.Text (string) 到Numbers.ID (integer)
* 绑定运行时将处理int和string之间的类型转换
******************************************************************/
this
.textBox1.DataBindings.Add(
"Text"
, _dt,
"ID"
,
true
);
/*******************************************************************
* 绑定 TextBox.Text (string) 到Numbers.Cost
* 绑定运行时将把值显示成货币形式(¥value.00)
*
* 注意:这里手工创建了 Binding 并将它添加到控件的DataBinding集合
* 而不是使用DataBindings.Add 的重载方法.
******************************************************************/
Binding
cb =
new
Binding
(
"Text"
, _dt,
"Cost"
,
true
);
/* .NET Framework 货币格式化字符串 */
cb.FormatString =
"c"
;
/* 添加绑定到TextBox */
this
.textBox2.DataBindings.Add(cb);
错误处理
Windows
窗体数据绑定整合了
Windows
窗体
ErrorProvider
控件。当使用
ErrorProvider
并且如果一个简单数据绑定操作失败的话,
ErrorProvider
控件将在用户界面元素上提供一个代表发生错误的可视反馈(一个错误图标)。
ErrorProvider
控件将查找在绑定期间发生的异常,另外,会在支持
IDataErrorInfo
接口的数据源上报告
IDataErrorInfo
信息
例子:错误处理
/*******************************************************************
*设置 (使用 Visual Studio 窗体设计器):
*
* 添加2个 TextBoxe到Form (textBox1 和 textBox2)
* 添加一个ErrorProvider到 Form (errorProvider1)
* 添加下面的代码到Form.Load事件响应方法
******************************************************************/
/*******************************************************************
* 数据源设置:
*
*创建一个叫做Numbers的表,并添加3列
* ID: int
* Name: string
* Cost: Decimal
******************************************************************/
DataTable
_dt;
_dt =
new
DataTable
(
"Numbers"
);
_dt.Columns.Add(
"ID"
,
typeof
(
int
));
_dt.Columns.Add(
"Name"
,
typeof
(
string
));
_dt.Columns.Add(
"Cost"
,
typeof
(
decimal
));
_dt.Rows.Add(0,
"Zero"
, 10.0M);
_dt.Rows.Add(1,
"One"
, 11.1M);
_dt.Rows.Add(2,
"Two"
, 12.2M);
/*******************************************************************
* 设置 ErrorProvider:
*
* 把它绑定到TextBox使用的同一个数据源上.
******************************************************************/
this
.errorProvider1.DataSource = _dt;
/*******************************************************************
*绑定 TextBox.Text (string) 到Numbers.ID (integer)
* 绑定运行时将处理int和string之间的类型转换
******************************************************************/
this
.textBox1.DataBindings.Add(
"Text"
, _dt,
"ID"
,
true
);
/*******************************************************************
*绑定 TextBox.Text (string) 到Numbers.Cost
* 绑定运行时将把值显示成货币形式(¥value.00)
*
* 注意:这里手工创建了 Binding 并将它添加到控件的DataBinding集合
* 而不是使用DataBindings.Add 的重载方法
******************************************************************/
Binding
cb =
new
Binding
(
"Text"
, _dt,
"Cost"
,
true
);
/* .NET Framework货币格式化字符串*/
cb.FormatString =
"c"
;
/*添加绑定到TextBox */
this
.textBox2.DataBindings.Add(cb);
Currency Management
和
BindingContext
Currency Management
Windows
窗体数据绑定提供的最重要的服务之一是
Currency Management
。在
Windows
窗体数据绑定的上下文中,
Currency
只是为一个列表维护和同步“当前项”。在
Windows
窗体中,
Currency Management
通过一个叫做“
CurrencyManager
”的类型提供,这个
CurrencyManager
是一个抽象类“
BindingManagerBase
”的子类。
CurrencyManager
提供一组事件,包括“
CurrentChanged
”和“
PositionChanged
”,控件和开发者通过这组事件都能够监视到“
Currency
”中的改变。此外,
CurrencyManager
还提供一些属性像“
Position
”和“
Current
”,来允许控件和开发者设置和取回当前项(位置)。
Windows
窗体数据绑定的一个关键方面是,
Currency
不是被像
DataGrid
这样的控件所维护,而是由被多个控件共享的一个
CurrencyManager
的一个实例来维护。
Binding Context
当使用简单列表绑定(就是上面说过的“针对列表的简单数据绑定”)的时候,简单数据绑定的控件需要同步它的数据源中的“当前项”。为了做到这点,绑定需要得到它关联的
CurrencyManager
的“当前”项。绑定通过它的控件的“
BindingContext
”属性得到它的数据源的
CurrencyManager
,一个控件典型的是从它的父窗体中得到它的
BindingContext
。“
BindingContext
”是一个单窗体的
CurrencyManager
缓存,更确切地说,
BindingContext
是一个
BindingManagerBase
实例的缓存,而
BindingManagerBase
时
CurrencyManager
的基类。
举个例子,假设一个开发者有一个
Customer
列表,绑定到一个
DataGrid
控件(
DataGrid.DataSource
设置到一个
Customer
列表)。此外,开发者还有一个简单控件,比如一个
TextBox
,绑定到同一个列表(
TextBox
利用简单列表绑定把它的
Text
绑定到
Customer
列表中的
Name
属性),当在
DataGrid
中点击一项时,
DataGrid
将使被点击的那想成为当前选中项。
DataGrid
怎么做到的呢?
DataGrid
首先访问它的
BindingContext
属性获取它的数据源(列表)的
BindingManagerBase(CurrencyManager)
,“
BindingContext
”返回缓存的
BindingManagerBase
(如果不存在的话,会创建一个),然后
DataGrid
会使用
BindingManagerBase
的
API
来改变“当前”项(它通过设置
CurrencyManager
的
Position
属性来达到目的)。当一个简单数据绑定被构造出来之后,控件将得到与它的数据源关联的
BindingManagerBase(CurrencyManager)
,它将监听
BindingManagerBase
上的
change
事件,并且在
BindingManagerBase
的“当前项”改变时同步更新它的绑定属性(比如
Text
属性)。正确的同步的关键是
DataGrid
和
TextBox
两者必须要使用相同的
CurrencyManager(BindingManagerBase)
。如果他们使用的是不同的
CurrencyManager
,那么当
DataGrid
中的条目发生改变时简单绑定的属性将无法正确更新。
正如前面提到的,控件(和开发者)能够利用一个控件的
BindingContext
属性得到一个与数据源关联的
BindingManagerBase
。当从
BindingContext
请求一个
BindingManagerBase
的时候,
BindingContext
将首先查到它的缓存看是否有被请求的
BindingManagerBase
。如果缓存中不存在这个
BindingManagerBase
,
BindingContext
将创建并返回一个新的(并且把它加入缓存)。
BindingContext
典型情况下对于每个窗体是全局的(子控件的代理他们父窗体的
BindingContext
),因此典型情况下
BindingManagerBase(CurrencyManagers)
被一个窗体上所有的控件所共享。
BindingContext
有
2
种形式:
/*
获取一个给定数据源的
BindingManagerBase */
bmb = this.BindingContext[dataTable];
/*
获取给定数据源和数据成员的
BindingManagerBase */
bmb = this.BindingContext[dataSet, "Numbers"];
第一种形式通常用来获取一个像
ADO.NET
的
DataTable
这样的列表的相关的
BindingManagerBase
。第二种形式用来获取含有子列表的父级数据源(比如含有子
DataTable
的
DataSet
)的
BindingManagerBase
。
BindingContext
最令人不解的方面之一是使用不同的形式来指定同一个数据源,将导致两个不同的
BindingManagerBase
实例。迄今为止,这是控件绑定到相同的数据源但却没有同步的最常见原因(控件通过
BindingContext
来获取一个
BindingManagerBase
)
。
例子:控件同步
/*
创建含有一个
DataTable
的
DataSet */
DataSet
dataSet = new DataSet();
DataTable
dataTable = dataSet.Tables.Add("Numbers");
dataTable.Columns.Add("ID", typeof(int));
dataTable.Columns.Add("Name", typeof(string));
dataTable.Rows.Add(0, "Zero");
dataTable.Rows.Add(1, "One");
/*******************************************************************
*
绑定第一个
DataGridView
和
TextBox
到
dataSet
中的
”Numbers”
表
*
这个
DataGridView
将使用
BindingContext
得到一个数据源的
CurrencyManager
* DataGridView1
将使用以下形式的
BindingContext
*
* bmb = BindingContext[dataSet, “Numbers”];
*
* textBox1
的文本绑定也将得到一个
BindingManagerBase,
并且使用如下的
BindingContext
形式
*
* bmb = BindingContext[dataSet, “Number”];
*
*
因此
dataGridView1
和
textBox1
将共享同一个
BindingManagerBase (CurrencyManager)
*
*******************************************************************/
this
.dataGridView1.DataSource = dataSet;
this
.dataGridView1.DataMember = "Numbers";
this
.textBox1.DataBindings.Add("Text", dataSet, "Numbers.Name", true);
/*******************************************************************
*
变量
“dataTable”
也指向这个
“Numbers”
表
.
虽然
*
上面的
DataGridView
和
TextBox
利用
“DataSource”
和
“DataMember”
形式
*
绑定到表
,
现在也可以绑定到相同的表(和数据),通过直接绑定到
“dataTable”
,正如下面所示
*
当这么做的时候
, DataGridView2
将使用以下形式的
BindingContext
*
* bmb = BindingContext[dataTable];
*
* textBox12
的文本绑定也将使用如下的
BindingContext
形式
*
* bmb = BindingContext[dataTable];
*
*
因此
dataGridView2
和
textBox2
将共享相同的
BindingManagerBase (CurrencyManager)
*
然而他们将不会和
dataGridView1
、
textBox1
共享相同的
CurrencyManager,
因为他们使用不同的形式
*
来获取他们的绑定
.
*******************************************************************/
this
.dataGridView2.DataSource = dataTable;
this
.textBox2.DataBindings.Add("Text", dataTable, "Name", true);
控制绑定操作
Windows
窗体简单绑定类型(
System.Windows.Forms.Binding
)允许开发者控制在用户界面元素内容发生改变时怎样和何时更新绑定的数据源属性,以及在数据源属性发生变化时怎样和何时更新用户界面元素。例如,如果一个开发者把
Customer
实例的
Name
属性绑定到一个
TextBox
控件的
Text
属性上,开发者可以指定何时把用户界面元素发生的更改“传播”到数据源。当前支持的选项是:在
TextBox
的验证
(validation)
期间(当用户将焦点移出
TextBox
时——这是默认值);当文本值发生任何改变时;以及永不更新数据源。开发者也能够控制何时把数据源的更改更新到绑定到的用户界面元素。当前支持的选项的是:当数据源属性改变时(默认值)
和
永不更新。注意,开发者可以组合使用“永不更新”和调用绑定
API
(
ReadValue/WriteValue
)来提供他们自己的显示的数据源与用户界面元素间的数据同步规则。
例子:显示的绑定
/*******************************************************************
* 设置 (使用 Visual Studio 窗体设计器):
*
* 添加3个TextBoxe到窗体Form (textBox1, textBox2 and textBox2)
* 添加1个 ErrorProvider 到窗体Form (errorProvider1)
* 添加一个按钮Button到窗体Form (button1)
* 添加下面的代码到Form.Load 事件处理方法
******************************************************************/
/*******************************************************************
* 数据源设置:
*
* 创建一个叫做 Numbers的表并添加3列
* ID: int
* Name: string
* Cost: Decimal
******************************************************************/
DataTable
_dt;
_dt =
new
DataTable
(
"Numbers"
);
_dt.Columns.Add(
"ID"
,
typeof
(
int
));
_dt.Columns.Add(
"Name"
,
typeof
(
string
));
_dt.Columns.Add(
"Cost"
,
typeof
(
decimal
));
_dt.Rows.Add(0,
"Zero"
, 10.0M);
_dt.Rows.Add(1,
"One"
, 11.1M);
_dt.Rows.Add(2,
"Two"
, 12.2M);
/*******************************************************************
* 设置 ErrorProvider:
*
* 把它绑定到TextBox使用的同一个数据源.
******************************************************************/
this
.errorProvider1.DataSource = _dt;
/*******************************************************************
* 绑定 TextBox.Text (string) 到Numbers.ID (integer)
*
* 设置DataSourceUpdateMode 为 OnPropertyChanged. 这将
* 导致一旦TextBox.Text发生改变,就会立即更新数据源属性
*
* 注意, 在这种模式下ErrorProvider将立刻显示一个错误,而不是在用户把焦点
* 移出TextBox之后才只显示一个错误
******************************************************************/
this
.textBox1.DataBindings.Add(
"Text"
, _dt,
"ID"
,
true
,
DataSourceUpdateMode
.OnPropertyChanged);
/*******************************************************************
* 绑定 TextBox.Text 到 Form.Size (在这个场景下, 窗体Form 是数据源)
* 不要让控件的改变更新到数据源(窗体Form)
******************************************************************/
Binding
sizeBinding =
new
Binding
(
"Text"
,
this
,
"Size"
,
true
,
DataSourceUpdateMode
.Never);
this
.textBox2.DataBindings.Add(sizeBinding);
/*******************************************************************
* 设置Button.Click 显示的更新数据源(Form.Size).
*
* 使用匿名代理让代码更简洁
******************************************************************/
this
.button1.Click +=
delegate
(
object
button,
EventArgs
args)
{
sizeBinding.WriteValue();