【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性

Download Source - 80KB


原文作者:Qwertie, Canada

博文主页:点击查看

原文地址:点击查看

免责说明:本文由CodeProject博文翻译而来,个人学习,仅供参考,欢迎指正,如有侵权,烦请告知删除。

翻译原因:在WPF/Silverlight数据绑定流行的时代,很多开发者并没有深入研究WinForm所提供的数据绑定机制,以至于很多人在编写应用时,仍在后台代码中操纵数据集合,并不断重新加载到数据控件上。如果您和我一样,还在这么做,不妨读一下这篇文章,改变一下编码方式。


 

详解Data Binding

通过几个简单示例深入了解WinForm数据绑定特性

简介:


       关于WinForm数据绑定(Data Binding)的文章非常少。它究竟是如何工作的?您可以用它来做些什么?诚然,知道如何使用数据绑定的人不少,但真正了解它的运行机制的人可能寥寥无几。就笔者而言,仅是掌握如何使用它就耗时颇多,故深入研究以揭开其中奥秘。

     “数据绑定”通常可理解为“控件(Controls)与数据表、行之间的自动同步”。在.NET框架(及.NET Compact Framework,即.NET 2.0精简版)中,您依旧可以这么定义,但它的真实概念已经扩展到了众多场景中,以至于您几乎可以将任何对象绑定到任何控件的任何属性上。

     System.Windows.Forms.BindingSource是.NET 2.0框架中的一个新的程序集。微软希望开发者使用BindingSource替代其他旧的类型,诸如:CurrencyManagerBindingContext类,因此,本文仅帮助您深入了解BindingSource类并掌握其应用。

     数据绑定可以使用反射机制,所以其应用范围不会局限于ADO.NET的DataSet中的数据表或行,几乎所有拥有属性(Property,译者注:本文中均译为“属性”)的类型都可以用于数据绑定。例如,通过数据绑定可以有效地实现一个可选对话框,而选项信息都存储在一个普通的.NET对象中。本文不是一篇关于ADO.NET、DataSetDataGridView的教程文章,如有此方面需求,可以参考“相关文章”一节。

备注:在本文中,将假设您是一位已熟练掌握C#(包括ADO.NET),但对数据绑定知之甚少的读者,以展开对数据绑定的讨论。

免责说明:本文中没有大篇幅的描述.NET精简框架对数据绑定的支持,所以不能保证这里所提到的所有内容都适用于.NET精简框架。

 

目录:


  • 数据绑定API
  • Airplane和Passenger示例类
  • 窗体设计中的绑定方式
  • 后台代码手工绑定方式
  • 数据绑定是如何工作的
  • 数据绑定能做些什么? 
    • 不使用BindingSource进行绑定
    • 分级式(Hierarchical)数据绑定
    • 使用DataSet的分级式绑定
      • 没有当前元素(Current Item)的数据绑定
    • 数据过滤
      • SubString过滤器
  • 数据绑定干不了什么?
  • 笔者尚不明确的一些问题
  • 推荐阅读

 

数据绑定API 


注意以下内容:

  • 控件的DataBindings集合属性保存被绑定的数据对象,DataBindings集合的每个元素都包含一个object类型的名为DataSource的属性。(译者注:DataBindingsControlBindingsCollection类型,每个元素为Binding类型,DataSource属性包含于Binding类型中)。
  • 常用的ListBoxDataGridView等控件的DataSource属性均为object类型。
  • BindingSource类也有一个object类型的名为DataSource的属性。

      因此,这些对象都代表什么意义?笔者在查阅相关文章的过程中发现对其(译者注:DataSource属性的异同)描述甚为混乱,因此撰写本文以澄清视听。在现实生活中,您很可能会将一个BindingSource类对象赋值给列表控件或Binding类对象的DataSource属性。如果直接使用来源于数据库的数据信息,那么BindingSource类对象的DataSource属性通常是一个DataSet对象;否则,它将是您当前应用程序中一个自定义类型的实例对象。

      开发者似乎可以用多种方式进行数据绑定,但笔者却没有发觉以上不同类型的DataSource的属性在拼写上有任何区别。所以笔者编写了一系列实验程序去深入了解这些异同。

      让我们先从经典示例开始:将一个BindingSource类对象赋值给某控件的DataSource属性。可以将该BindingSource类对象想象为“二合一”的数据源,它包含以下两部分:

  1. 一个名为Current的属性(包含一个单一数据对象)。可以将Current绑定在一个控件的某个属性上(译者注:比如将Current属性所含的对象绑定在TextBoxText属性上)。
  2. 一个实现IList接口的名为List的属性。该列表所包含的众多对象都应与Current对象具有相同类型。List是一个只读属性,它将返回一个BindingSource类对象的“内部列表(MSDN:一个空ArrayList)”(如果DataMember属性没有被赋值),或返回一个“外部列表”(如果DataMember属性已被赋值)。Current属性通常是List的一员(或为null)。当你将DataSource设置为一个单对象(非列表对象)时,List仅包含这一个元素。

 数据绑定的工作方式随控件的不同而相异:

  • ComboBoxListBox通过它们的DataSourceDisplayMember属性将数据绑定在List上。通常将一个BindingSource类对象赋值给它们的DataSource属性,并将DisplayMember属性设置为想要显示的字段名称(该字段即为Current属性所含的对象的某个字段)。
  • DataGridDataGridView通过它们的DataSource属性将数据绑定在List上。它们没有DisplayMember属性,因为他们可以同时显示多列值。DataGridView有一个附加的DataMember属性,该属性与BindingSource类的DataMember属性类似。不是所有类型的数据都可以设置为DataGridViewDataMember属性的值,除非其DataSource属性不是BindingSource类型的值(如果使用的是BindingSource类对象,则需要设置BindingSource类对象的DataMember属性,以起到设置DataGridViewDataMember属性的值的作用)。
  • 诸如TextBoxButtonCheckBox这样的“简单”控件,只能将DataBindings数据集合的Current对象绑定在控件上。事实上,列表控件也拥有DataBindings数据集合,但同样也不被使用。DataBindings数据集合可以在窗体设计界面中进行设置。

 备注1:在本文中,会经常将数据值绑在TextBoxText属性上。其他的可绑属性如下:

  • CheckBoxRadioButtonChecked属性。
  • ComboBoxListBoxListViewSelectedIndex属性。
  • ComboBoxListBoxSelectedValue属性。
  • 任何控件的EnableVisible属性。
  • 一些控件的Text属性。

备注2:在桌面应用程序中,微软鼓励开发者使用DataGrid的升级版本DataGridView

备注3:ListViewTreeView的内容无法进行数据绑定(仅限于SelectedIndexEnable这样的属性可以进行绑定)。但CodeProject上的一些文章已给出了解除该限制的方法。

 

 Airplane与Passenger示例类


 本文中众多代码都是用了基于对象的数据源,定义AirplanePassenger类如下:

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第1张图片

示例程序中将使用一个DataGridView显示一组Airplane,一个TextBox显示Model属性,并支持对它的修改。

 

窗体设计中的绑定方式


       在Visual Studio设计界面中建立数据绑定,首先新建一个Windows Forms工程,并创建上节中提及的AirplanePassenger类。随后,在Form1中拖放一个名为“grid”的DataGridView和一个名为“txtModel”的TextBox。选择DataGridView,点击其右上角的小箭头弹出配置窗口,点击“Choose Data Source”下拉菜单,选择“Add Project Data Source”,弹出“Data Source Configuration Wizard”向导窗口。

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第2张图片

备注:只有在AirplanePassenger类被创建,并编译工程后,向导中才能看到它们。

      选择“Object”,点击“Next”,在树状菜单中选择Airplane。配置向导将在组件托盘中创建一个BindingSource类对象,并将其DataSource属性设置为Airplane类型。

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第3张图片

      配置向导同样会在DataGridView中创建3列,分别对应于Airplane的三个属性。但其中并没有一个列对应于Passengers列表,因为一个单一Cell中无法显示复杂的列表对象。(当然,可以在Cell中添加一个DropDownList来显示列表数据,本文中不再赘述。)

      打开TextBox的属性页,选择“Data Bindings(数据绑定)”节点,点击“Advanced(高级)”的“”按钮,弹出“Formatting and Advanced Binding(格式化与高级绑定)”窗口。在左侧“Property(属性)”属性菜单中,选择Text属性,之后点击中间的“Binding”下拉菜单,选择airplaneBindingSource的Model属性。

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第4张图片

      最后点击“确定”按钮。现在仅需要在airplaneBindingSource中添加数据即可。在Form1的后置代码中,创建Form1_Load()方法,并输入如下代码:

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第5张图片

编译运行程序,可以得到如下结果。点击不同行,TextBox所显示的值也将随之改变。

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第6张图片

备注:由于ID等属性是只读的,DataGridView自动禁止修改其对应值。然而其他一些控件并没有这么只能。例如,Model属性只读,但在txtModel依然可以对其进行修改,所以需要手动将txtModel的ReadOnly属性置为true

      这种绑定方式未尝不可,但笔者更倾向于在后置代码中进行数据绑定。重新开始,在设计窗口中删除airplaneBindingSource,使相关控件失去绑定。

 

后台代码手工绑定方式


       修改后置代码如下,//***显示了与上节中不同的部分。我们需要自行创建BindingSource类。运行结果与上节一致。

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第7张图片

      DataGridView依然没有创建一列以显示AirplanePassengers属性。添加了这些代码,便可不再依赖于设计窗体进行数据绑定。

      笔者发现一件有趣的事,在Form1_Load()的代码中,数据绑定不会介意你编写代码的顺序并可以良好的运行。另一件有趣的事是,我们无需告知BindingSource需要保存哪种类型的数据对象,即可以删除对DataSource的赋值语句,但在第一次将数据添加进BindingSource是,在其内部,会获悉数据的确切类型(如Airplane类),如果继续添加非此类型(如非Airplane类)时,它将抛出InvalidOperationException异常。

      此外,DataSource是一个不可思议的属性。如果直接将一个Airplane类对象赋给DataSource,以替代typeof(Airplane)的赋值语句。你能想象下面的Add()语句将发生什么吗?

      运行程序会发现在列表中仅有AirbusCessna两条信息。这样写法的效果同下:

      如果在代码中修改txtModel.Text的值,当前AirplaneModel属性并不会被更新,至少不会被立即更新(一些事件将会以某种方式触发更新行为)。一种更新方案是更新Model属性,再调用bs.ResetCurrentItem()方法以更新UI显示。

 

数据绑定是如何工作的?


       当然,txtModel并不是必须的,因为可以在DataGridViewModel列中直接修改。但是,此例旨在演示两个控件之间的动态同步:

  • 如果改变当前选择行,txtModel会自动显示当前行的Model值。
  • 如果修改txtModel值并按[Tab]键,其他控件中的Model信息将被刷新。

      着实神奇!两个控件之间是如何通信的?其背后究竟发生了什么?事实上,BindingSource是原因所在。阅读MSDN文献可以了解到,BindingSource“通过在Windows窗体控件与数据源之间提供流通管理(Currency Management)、更改通知(Change Notification)和其他服务简化了窗体上的控件与数据的绑定。”

      流通管理?当笔者看到该概念时,想“是不是NumberFormatInfo起控制作用?”但事实证明“流通管理”与钱币流通无关,而是微软对“Currentness”(译者注:暂译为“当前”)一词的叫法。换言之,BindingSource不断追踪List中那个元素为当前指定元素。在其内部,BindingSource使用一个CurrencyMananger类对象,该对象保存了一个指向List的引用,并不断追踪当前元素。

      在本例中,当用户编辑Model内容时,控件(txtModel)以某种方式修改BindingSource.Current对象,同时,BindingSource类对象触发CurrentItemChanged事件。事实上,单独的修改行为会触发多个事件,如果想知道它触发了哪些事件,可以将以下代码添加在Form1_Load()方法中。

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第8张图片

      但是,控件是如何将其Text属性变更一事告知BindingSource类对象的呢?要知道,从控件的角度来看,DataSource仅仅是一个对象。再考虑一下这些问题,控件是否使用一些特殊手段来支持BindingSource,或是它们实现了某种接口以使他们可以接受其他类型?BindingSource时候使用一些特殊手段来支持DataSet,或是它是否仅关注某些方法或属性?换言之,数据绑定是否基于“Duck Typing”(即:鸭子类型,动态类型的一种风格,在该风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法或属性的集合决定。参见维基百科),或它是针对某些类型、接口的特例?一般期望从数据源得到什么?

      使用VS2008并遵循以下提示内容(MSDN文献),您可以跟踪调试.NET框架的源代码。这里还介绍一个特殊工具(CodeProject文献),该工具可以帮在其他版本的VS中实现调试.NET框架源码的功能,它会下载.NET框架的所有源码。此外,也可以使用Reflector和FileDisassembler插件查看代码,但是这个方法不允许开发者跟踪调试源代码。

     不幸的是,涉及数据绑定的源码量极大且晦涩难懂,经过数小时的研究,笔者找出了一个控件通知BindingSource类对象的途径,即txtModel的Text属性被修改时,是如何通知DataGridView的?下面将对其过程进行描述,但其内容之复杂可能是您从未听过的。

长话短说,其步骤如下:

  • 通过txtModel.DataBindings.Add()方法添加的Binding类对象拥有一些指向TextBox的TextChanged和Validating事件的句柄。
  • 句柄Binding.Target_Validate通过BindToObjectReflectPropertyDescriptor等内部类传递新值。ReflectPropertyDescriptor类对象通过反射修改保存在Airplane中的Model的值,并调用它的基类PropertyDescriptor的OnValueChanged方法。
  • PropertyDescriptor的OnValueChanged调用一个与当前Airplane相关联的指向BindingSource.ListItem_PropertyChanged的委托。
  • 该句柄触发它的ListChanged事件(事件参数ListChangedEventArgs包含了被修改元素的index值)。
  • CurrencyManager.List_ListChanged句柄与ListChanged事件相关联。该句柄触发自己的ItemChanged事件,BindingSource.CurrencyManager_CurrentItemChanged句柄与之相关联。
  • 该事件句柄(BindingSource.CurrencyManager_CurrentItemChanged)触发BindingSource.CurrentItemChanged事件,此事件将触发的Debug.WriteLine(“CurrentItemChanged”)方法。
  • 随后,CurrencyManager.List_ListChanged触发自己的List_Changed事件。
  • DataGridView的内部类型拥有一个DataGridViewDataConnection.currencyManager_ListChanged句柄,负责刷新被修改的行数据。
  • 最后,currencyManager_ListChanged句柄触发DataBindingComplete事件,但没有任何句柄处理该事件(用户可以注册该句柄的话)。

      这个过程结束了。那么,PropertyDescriptor中的Airplane与txtModel的DataBindings集合中的Bindings相关联的情况下,BindingSource是如何将一个事件句柄与PropertyDescriptor中的Airplane相关联的?

  • 当为DataBindings集合添加新的Binding时,Binding.SetBindableComponent()将其引用赋给要绑定的控件,该控件拥有一个BindingContext类对象,它管理了一个Bindings集合,该对象与DataBindings类似,但又有所区别。(具体区别可以参见MSDN)。
  • Binding类对象(以下称之为"b")将它自己传递给BindingContext.UpdateBinding()方法。MSDN中讲到,该方法“将Binding类对象与一个新的BindingContext相关联。”
  • UpdateBinding()方法调用BindingContext.EnsureListManager(),此方法会通知Binding类对象的DataSource(注意:这是一个BindingSource类对象)实现ICurrencyManagerProvider接口,所以Binding类对象会调用ICurrencyManagerProvider.GetRelatedCurrencyManager(dataMember)方法,参数dataMember与当前Binding类对象相关联,此时,dataMember是一个空字符串。这是笔者发现的一个神奇之处,DataSource并不仅仅是被当作一个Object对象对待。可以看到,.NET框架使用了特殊的接口而非鸭子类型(基于反射)的方式来处理DataSource类对象。反射仅在获得Airplane属性时使用。
  • 此时,BindingSource有机会返回它的内部类对象CurrencyManager(以下称之为”c”)。
  • 但是,此时UpdateBinding()方法并没有将b添加到BindingContext中,也没有将CurrencyManager赋给b。相反,c通过调用c.Bindings.Add(b)将b添加给了CurrencyManager类对象。
  • c.BindingsLsitManagerBindingsCollection类型的(这是一个内部类)。c.Bindings.Add(b)会调用方法b.SetListManager(c)。由此,BindingSourceCurrencyManager最终与Binding b及b的BindToObject对象相关联。所以,当用户更改txtModel的Text属性并按[Tab]键时,b的BindToObject通过存储在b.BindToObject.fieldInfo中的一个ReflectPropertyDescriptor类对象以访问CurrencyManager。在Form1_Load()方法中第一次调用bs.Add()方法时, ListBindingHelper.GetListItemProperties()方法代表BindingSource创建了ReflectPropertyDescriptor类对象。该对象包含一个从AirplaneBindingSource.ListItem_PropertyChanged的关系映射图,因此,BindingSource可以被告知某Airplane类对象的信息已被修改。

      这个框架结构很复杂。上述过程很难被探索清楚,因为BCL(基础类库)是被JIT优化的,这意味着一些函数并不会出现在堆栈试图(Debug时,按Ctrl+D, C组合键)里,也无法在调试时看到一些变量。此外,智能感知功能(Intellisense)在该过程中无效,众多内部代码没有注释信息。但是笔者至少可以肯定BindingContext(记住这是各控件的BindingContext)类对象拥有针对数据源的特殊代码,这些代码实现了ICurrencyManagerProviderIListIListSource接口,因此,可以数据源必须实现这些接口中之一,以列表形式呈现。

      对于BindingSource,它创建了一个BindingList<T>类型的List的属性,T是用户期望使用的一种类型,即本例中的Airplane类。BindingSource看起来并没有对DataSet有特殊支持,尽管其中有一些针对某些接口的特殊代码。

  • 如果T实现了INotifyPropertyChanged接口,BindingList会订阅该接口的PropertyChanged事件。
  • BindingSourceCurrencyManager联系紧密,它拥有IList并维护列表中的当前元素位置。CurrencyManager包含一些特殊代码以支持列表数据,这些列表都实现了IBindingListITypedListICancelAddNew接口,而列表中的元素对象都实现了IEditableObject接口。

      可惜整个绑定框架内部联系过于紧密(如类之间拥有众多引用和关系),笔者推荐使用实例程序和文字来描述这些绑定框架,尽管这些关系可能用UML表示更加一目了然。

 

数据绑定能做些什么?


       首先,可以将包含列表的数据对象绑定在DataSource上。试一下如下代码:

 【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第9张图片

     不同于以前,代码中制定了DataMember属性。grid列表中只显示了a中的Passengers列表,为没有显示Airplane列表信息。

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第10张图片

备注:如果要显示一个ADO.NET表,仅需用DataSet替换Airplane,用DataTable的名字替换这里的“Passengers”即可。

      也可以直接为BindingSource(bs)或a.Passengers添加元素。但直接修改a.Passengers有时并不奏效,比如在Form1_Load()方法的尾部添加如下代码:

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第11张图片

      运行结果只显示了共四行数据(实际上Passengers中有6条数据),插入值只显示了“Oops 1”,当点击gird的不同行时,“Oops 2”会出现在第二行。数据绑定在此时之所以没有奏效,是因为BindingSource没有得到任何变更通知。可以按下图使用grid.Refesh()方法:

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第12张图片

      运行结果显示了插入的两条数据,但并没有将全部6条数据显示出来。原因同上,可以再按下图使用bs.RestBindings(false)方法:

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第13张图片

      再次运行程序,Passengers的全部数据均显示在列表中。

译者注:参考MSDN文献BindingSource.ResetBindings()方法的介绍:

作用:使绑定到BindingSource的控件重新读取列表中的所有项,并刷新这些项的显示值。

参数:true,数据框架已更改;false,只有值发生了更改。

 

不使用BindingSource进行绑定


       也可以不使用BindingSource进行数据绑定。例如,在Form1上添加一个名为button1的Button,重写Form1_Load()方法并注册button1_Click事件:

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第14张图片

button1负责两件事:

  • 当修改grid或txtModel时,底层数据源也同样被修改;
  • 如果在底层数据源中添加一行数据,必须调用grid.ResetBindings()方法(但是用BindingSource进行绑定时,增加一行则不需要调用该方法)。

此例会让人产生疑问,何必还要再用BindingSource

  • BindingSource可以在多个控件之间自动同步并显示源数据。该例之所以工作良好是因为两个控件的数据是相互独立的。(译者注:不太理解“相互独立”)
  • 当增加或移除BindingSouce.List中的元素时,BindingSource自动刷新绑定的控件。
  • 多个BindingSource可以形成绑定链(将在下节介绍)。
  • 当修改Model时,奇怪的事情发生了,grid的CurrentRow.Index变为了0。笔者不知道为什么,但怀疑是在使用BindingSource时出发了这种改变。

译者注:运行此例时,出现了如下错误,尚不清楚原因。欢迎反馈。

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第15张图片

 

分级式(Hierarchical)数据绑定


       现在在Form1中添加一个ListBox以显示各Airplane对应的Passengers数据。UI和后台代码如下所示:

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第16张图片

      通过ListBox.DisplayMember属性指明显示Passengers.Name属性。但笔者不确定是否可以在其他场景下也使用类似的点号分隔表示法。

      如果想修改PassengersName属性,可以增加一个TextBox,并在后置代码添加如下内容:

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第17张图片

      这类数据绑定方式被称之为“明细绑定(Master-Details)”。为实现此绑定模式,需要两个BindingSource类对象,因为一个BindingSource类对象只有一个CurrencyManager,所以只能跟踪一个“当前记录”,txtModel绑定当前Airplane,txtName绑定当前所选的Passenger

      在使用BindingSource时,尽管将DataGridViewAllowUserToAddRows属性设为true,它可能也不能新增一行(笔者对此很迷惑。译者注:将其值为出果然无法在UI上新增行)。这是因为BindingSourceList拥有一个名为AllowNew的属性,必须也将它置为true才能实现增加行的操作。相同的,需要删除时要相应的将其AllowRemove置为true。只有选中整行,再按[Del]键才能将其删除。

 

使用DataSet的分级式绑定


       对于更复杂的场景,开发者会要求使用数据库数据,或者DataSet进行数据绑定。下面的示例程序与上节中相似,但是使用DataSet来存储AirplanePassenger信息。笔者在代码中手工创建了DataSet架构,以避免读者连接数据库并获取数据的麻烦。构造DataSet示例数据的方法如下:

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第18张图片

      DataSetMode_Load()的内容如下(//***标注了与上节例子不同之处):

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第19张图片

     运行程序和上节示例相同,另外,默认可以排序、新增行、删除行。

 

没有当前元素(Current Item)的数据绑定


       通常,如果使用DataSet进行数据绑定,bsP.Current指向一个DataRowView对象,在上节中的示例中则指向一个Passenger。但是,当你创建一个新行时,它并不包含Passengers,bsP.Current则为null。笔者没有发现任何文章讲解绑定架构是如何支持当前元素为空的情况,但至少数据绑定框架知道将txtName的内容清空。但是,会发现仍然可以修改其内容。

      可以为bsP注册一个ListChanged事件以处理空Passengers的情况。

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第20张图片

 

数据过滤


 

      在真实的应用环境中,可能需要显示海量数据。如果需要根据用户提供的条件筛选列表,应当如何做呢?

      BindingSource提供了一个“Filter”属性接收一个布尔表达式,并以此作为筛选数据的依据。但BindingSource并不评估该表达式,而仅仅是将其传递给其List属性(必须实现IBindingListView接口)。在基于对象(即:Form1)的示例代码中,Airplane列表是一个BindingList<Airplane>列表,Passengers列表是一个List<Passenger>列表。这两类泛型集合都没有实现IBindingListView,因此该示例无法使用过滤(可以使用开源类库BindingListView为其添加过滤和排序功能)。

      但是,可以对DataTableDataView进行过滤。DataTable本身并没有实现IBindingListView接口,但它的DefaultView属性通过IListSource.GetList()方法返回的集合数据却实现了该接口。

      为演示基于DataSet数据绑定的的过滤功能,在上节DataSetMode窗体中添加2个TextBox,即txtAirplaneFilter和txtPassengerFilter,并为之添加TextChanged事件,如下图所示:

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第21张图片

        DataSet支持SQL风格的过滤表达式。本例中,对于txtAirplaneFilter可以使用Model like ‘*Bo*’FuelLeftKg < 1000等表达式;txtPassengerFilter同理。如果表达式语法不正确,TextBox的背景色将被置为粉色。将过滤表达式置为空字符串时会清楚过滤器,就如同调用BindingSource.RemoveFilter()方法一样的效果。DateSource.CaseSensitive属性控制是否对过滤字符串大小写敏感。

备注:如果有两个列表绑定在不同的BindingSource上,但每个BindingSource都附加在了相同的DataTable之上,则两个列表将共享过滤字符串。若要对不同列表使用不同过滤字符串,则需要分别对其创建DataView,再附加到DataTable之上(通过DataView.Table属性),并将不同BindingSourceDataSource设置为不同的DataView

 

SubString过滤器 


      通常,用户不希望输入一长串难以记忆的过滤字符串。因此,您可以只要求用户输入一些关键字以过滤数据。如何实现这一功能?使用一个委托来进行过滤不错,但DataView不支持这一做法。您必须在已经提供的过滤字符串语法的基础上,去尽量匹配用户输入的关键字。所以使用如下代码是明智的做法:

      可惜,这么做并无法完全支持用户的过滤需求。比如,用户在文本框中输入含有单引号等特殊符号的字符串。笔者提供了一个转码方法来处理这些特殊字符。

【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性_第22张图片

      之后就可以修改过滤器赋值语句为:

 

数据绑定干不了什么?


 

  • 您不能显示一个通过源数据计算得出的值。例如,上述示例程序中grid拥有一个FuelLeftKg列,如果通过FuelLeftKg属性计算出一个名为FuelLeftLbs的值以按“磅”显示,则无法在列表中显示该值,因为它并不存在与底层源数据中。ADO.NET的DataColumn通过“Expression”属性可以支持显示计算值,但笔者认为这些列是只读的。但是,可以通过后台代码使一个DataGridView的显示“未绑定”的数据列(参考MSDN论坛文献,第29项)。
  • 如同基于对象模式的数据绑定示例程序,当一组对象被封装为列表,BindingSouce.List是一个BindingList<T>集合,PropertyDescriptor并不支持对该集合进行排序、搜索操作。如果您想实现这些功能,需要创建一个继承于BindingList<T>类的新集合类,并重写名字以“Core”结尾的相关方法和属性(对于排序,请参见BindingList.ApplySortCore的相关文献,MSDN文献MSDN社区文献)。这样就可以把定制的列表赋给BindingSource.DataSource

 

笔者尚不明确的一些问题?


 

笔者仍有一些事情不太清楚:

  • 也许,有时需要一个“取消”按钮来终止一条数据的所有变更。或者直到点击“保存”按钮时才保存这些变更。这些需求如何实现?
  • 也许,用户需要利用DataGridViewListBox等控件的“可选多行”特性来同时修改多条数据的某列值(设为相同值)。这样的需求又该如何实现?

 

推荐阅读


  • MSDN论坛:WinForm数据控件及其绑定
  • 利用DataGridView展示数据(详细教程)
  • ADO.NET新手入门
  • 处理@@IDENITY危机(在ADO.NET和SQL Server中处理自增和标识列)
  • ADO.NET的面向对象编程
  • TreeView的数据绑定:点击查看和点击查看
  • 什么是BindingSource,为什么要使用它?
  • 使用DataGridView的101种方法(其实只有十几种高级应用方法,笔者夸张而已)
  • 实现IBindingList和ITypedList接口的集合的复杂数据绑定
  • .NET / C# WinForm中的数据绑定
  • ADO.NET中DataSets的应用
  • WinForm中DataGridView与DataGrid的异同

转载于:https://www.cnblogs.com/lichence/archive/2012/02/17/2356001.html

你可能感兴趣的:(【译文】详解Data Binding - 通过几个简单示例深入了解WinForm数据绑定特性)