Effective C#之Item 38:Utilize and Support Data Binding

  rel="File-List" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"> rel="themeData" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"> rel="colorSchemeMapping" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml">

Item 38: Utilize and Support Data Binding

支持并使用数据绑定

Experienced Windows programmers are familiar with writing the code to place data values in controls and to store values from controls:

有经验的Windows程序员都很熟悉编写这样的代码:向控件赋值,存储由控件得到的值:

  1. public Form1 : Form
  2. {
  3.   private MyType myDataValue;
  4.   private TextBox textBoxName;
  5.  
  6.   private void InitializeComponent( )
  7.   {
  8.     textBoxName.Text = myDataValue.Name;
  9.     this.textBoxName.Leave += new  System.EventHandler( this.OnLeave );
  10.   }
  11.  
  12.   private void OnLeave( object sender, System.EventArgs e )
  13.   {
  14.     myDataValue.Name = textBoxName.Text;
  15.   }
  16. }
  17.  

It's simple, repetitive code you know, the kind you hate to write because there must be a better way. There is. The .NET Framework supports data binding, which maps a property of an object to a property in the control:

你讨厌写这样简单却重复的代码,因为肯定有更好的方法。是的,确实有。.Net框架支持数据绑定,将一个对象的属性映射到控件的属性上:

textBoxName.DataBindings.Add ( "Text",myDataValue, "Name" );

The previous code binds the "Text" property of the textBoxName control to the "Name" property of the myDataValue object. Internally, two objects, the BindingManager and the CurrencyManager, implement the transfer of data between the control and the data source. You've probably seen this construct in many samples, particularly with DataSets and DataGrids. You've also done simple binding to text boxes. You've likely only scratched the surface of the capabilities you get from data binding. You can avoid writing repetitive code by utilizing data binding more effectively.

上面的代码将textBoxName控件的"Text"属性绑定到myDataValue对象的"Name"属性上。在内部,BindingManagerCurrencyManager这两个对象,在控件和数据源之间实现了传递。你可能在很多例子里面见过这种结构,尤其是DataSetsDataGrids。你也对text box进行过简单的绑定。你很可能仅仅抓住了数据绑定能力的皮毛。利用更高效的数据绑定,你可以避免编写重复的代码。

A full treatment of data binding would span at least one book, if not two. Both Windows applications and web applications support data binding. Rather than write a complete treatise of data binding, I want to make sure you remember the key advantages of it. First, using data binding is much simpler than writing your own code. Second, you should use it for more than text items-----other display properties can be bound as well. Third, on Windows forms, data binding handles synchronizing multiple controls that examine related data sources.

对数据绑定要进行完整介绍的话,即使没有2本书,至少也得要1本。Windows应用程序和web应用程序都支持数据绑定。我不想写完整的数据绑定的论述,只想让你记住它的关键优势。首先,使用数据绑定比编写自己的代码要简单。其次,不仅应该在文本上使用----在其他可以显示的属性上也很好。第三,在Windows 窗体上,数据绑定可以同步处理多个控件对相关数据源的检查。

For example, suppose you get a requirement to display the text in red whenever the data shows an invalid value. You could write the following snippet:

例如,假如你被要求:在数据无效时,以红色文本显示。你可能会编写如下的代码段:

  1. if ( src.TextIsInvalid )
  2. {  textBox1.ForeColor = Color.Red;}
  3.  else
  4. {  textBox1.ForeColor = Color.Black; }


That's well and good, but you need to call that snippet of code whenever the text in your source changes. That could be when the user edits the text or when the underlying data source changes. There are a lot of events to handle and many places that you might miss. Instead, use data binding. Add a property in your src object to return the proper foreground color.

这相当不错,但是不管何时,当你的源中的文字改变的时候,都需调用该代码段。可能是:用户编辑了文字;底层数据源发生了改变。这就有很多事件要进行处理了,你可能会漏掉很多地方。取而代之,使用数据绑定。在你的源对象里面添加一个属性来返回适当的前台颜色。

Other logic will set the value of that variable to the proper color based on the state of the text message:

基于text信息的状态,其它逻辑将该变量的值设置为合适的颜色。

  1. private  Color _clr = Color.Black;
  2. public  Color  ForegroundColor
  3. {
  4.   get
  5.   {
  6.     return _clr;
  7.   }
  8. }
  9.  
  10. private  string _txtToDisplay;
  11. public  string  Text
  12. {
  13.   get
  14.   {
  15.     return _txtToDisplay;
  16.   }
  17.   set
  18.   {
  19.     _txtToDisplay = value;
  20.     UpdateDisplayColor( IsTextValid( ) );
  21.   }
  22. }
  23.  
  24. private void UpdateDisplayColor( bool bValid )
  25. {
  26.   _clr = ( bValid ) ? Color.Black : Color.Red;
  27. }
  28.  

Then simply add the binding to the text box:

然后就可以对text box添加简单的绑定了:

textBox1.DataBindings.Add ("ForeColor" ,src , "ForegroundColor");

When the data binding is configured, textBox1 will draw its text in the correct color, based on the internal value of the source object. You've done more to decouple the control from the data source. Instead of having multiple event handlers and multiple locations where the display color changes, you have two. Your data source object keeps track of the properties that affect the proper display. Your form controls the data binding.

当数据绑定可配置时,基于源对象的内部值,textBox1将以正确的颜色绘制文本。你已经做了很多对控件和数据源进行解耦。不再需要多个事件句柄,也不再有多个显示颜色可以改变的地方,你现在只有2个地方了。数据源对象保持对影响显示的属性的跟踪,窗体控制着数据绑定。

Although the samples I've shown are Windows forms, the same principle works for web applications: You can bind properties of data sources to a property in the web control as well:

虽然我刚才展示的例子是Windows窗体,但是该原则对web应用程序同样适用:你也可以将数据源的属性绑定到web控件的属性上。

  1. <asp:TextBox id=TextBox1 runat="server"  Text="<%# src.Text %>"
  2.   ForeColor="<%# src.ForegroundColor %>">
  3.  

This means that when you create the types that your application displays in its UI, you should add the necessary properties to create and update your UI in response to user needs.

这意味着,当创建你的应用程序要在它的UI上进行显示的类型时,应该添加必要的属性来对用户的需要进行响应,即创建或者更新UI

What do you do if the objects you have don't support the properties you need? You wrap what you have and add what you need. Consider this data structure:

如果你拥有的对象不支持需要的属性时,该怎么办呢?那就对拥有的东西进行包装,添加你所需要的。考虑这个数据结构:

  1. public struct FinancialResults
  2. {
  3.   public decimal Revenue
  4.   {
  5.     get { return _revenue; }
  6.   }
  7.  
  8.   public int NumberOfSales
  9.   {
  10.     get { return _numSales; }
  11.   }
  12.  
  13.   public decimal Costs
  14.   {
  15.     get { return _cost;}
  16.   }
  17.  
  18.   public decimal Profit
  19.   {
  20.     get { return _revenue - _cost; }
  21.   }
  22. }

 

You have requirements to display these in a form with some special formatting notes. If the profit is negative, you must display the profit in red. If the number of sales drops below 100, it should be bold. If the cost is above 10,000, it should be bold. The developer who created the FinancialResults structure did not add UI capabilities into the structure. That was most likely the right choice. FinancialResults should limit its capabilities to storing the actual values. You can create a new type to include the UI formatting properties with the original store properties in the FinancialResults structure:

你被要求:在窗体里面,用特定的格式来显示这些数据。如果profit(利润)是负的,你必须以红色进行显示。如果销售额低于100,应该加粗显示。如果花费高于了10000,也应该加粗。创建FinancialResults结构的开发者没有在结构里面加入UI能力。这最像是正确的选择。FinancialResults应该限制自己的存储实际值的能力。你可以创建一个新的包含UI格式属性的类型,同时在FinancialResults结构里面保持原来的存储属性。

  1. public struct FinancialDisplayResults
  2. {
  3.   private FinancialResults _results;
  4.   public FinancialResults Results
  5.   {
  6.     get { return _results; }
  7.   }
  8.  
  9.   public Color ProfitForegroundColor
  10.   {
  11.     get
  12.     {
  13.       return ( _results.Profit >= 0 ) ? Color.Black : Color.Red;
  14.     }
  15.   }
  16.   // other formatting options elided
  17. }

 

You have created a single data structure to facilitate data binding of your contained structure:

你已经创建了一个单独的数据结构,这样便于对你的内嵌的结构进行数据绑定:

  1. // Use the same datasource. That creates one Binding Manager
  2. textBox1.DataBindings.Add ("Text",  src, "Results.Profit");
  3. textBox1.DataBindings.Add ("ForeColor", src, "ProfitForegroundColor");

I've created one read-only property that allows access to the core financial structure. That construct doesn't work if you intend to support read/write access to the data. The FinancialResults struct is a value type, which means that the get accessor does not provide access to the existing storage; it returns a copy. This idiom has happily returned a copy that cannot be modified using data binding. However, if you intended editing, the FinancialResults type would be a class, not a struct (see Item 6). As a reference type, your get accessor returns a reference to the internal storage and would support edits by the user. The internal structure would need to respond to changes made to the internal storage. The FinancialResults would raise events to notify other code of changes in state.

我已经创建了一个只读的属性,允许对核心的金融结构进行访问。如果你想支持对数据的读/写访问,那个结构就不适用了。FinancialResults结构是值类型,那意味着get访问符不提供对现存数据的访问,而是返回一个副本。然而,如果你想进行编辑,FinancialResults就得是一个类,而不是结构体(Item 6)。作为引用类型,get访问符返回一个对内部存储的引用,将支持用户的编辑。内部结构体需要对针对内部存储的变化做出响应。FinancialResults会产生事件来通知其它代码发生了状态的改变。

It's important to remember to use the data source for all related controls in the same form. Use the DataMember property to differentiate the property displayed in each control. You could have written the binding construct this way:

在同一个窗体里,记得为所有相关的控件使用数据源,是很重要的。使用DataMember属性来区分在每个控件里面显示的属性。你可能已经用这种方式编写了绑定结构:

  1. // Bad practice: creates two binding managers
  2. textBox1.DataBindings.Add ("Text",src.Results, "Profit");
  3. textBox1.DataBindings.Add ("ForeColor",src, "ProfitForegroundColor");

That would create two binding managers, one for the src object and one for the src.Results object. Each data source is managed by a different binding manager. If you want the binding manager to update all property changes when the data source changes, you need to make sure that the data sources match.

这将创建2个绑定管理对象,1个为src对象1个为rc.Results对象。每个数据源都由不同的绑定管理者进行管理。如果你希望当数据源变化时,绑定管理者更新所有的属性变化,你需要确认匹配了正确的数据源。

You can use data binding for almost any property of a Windows or web control. The values displayed in the control, the font, the read-only state, and even the location of the control can be the target of a binding operation. My advice is to create the class or struct that contains the values you need to display your data in the manner requested by your users. Then use data binding to update the controls.

对于Windows或者web控件的任意属性,你几乎都可以使用数据绑定。控件里面显示的值,字体,只读状态,甚至控件的位置,都可以是绑定操作的目标。我的建议是,创建类或者结构体,包含用户要求的需要进行显示的值,然后使用数据绑定来更新控件。

In addition to simple controls, data binding often involves DataSets and DataGrids. It's very powerful: You bind the DataGrid to the DataSet, and all the values in the DataSet are displayed. If your DataSet has multiple tables, you can even navigate between tables. What's not to love?

对于简单的控件,数据绑定经常涉及到DataSetsDataGrids。这是相当强有力的:将DataGrid绑定到 DataSet, DataSet中的所有值都会被显示。如果DataSet有多个表,你甚至可以在各个表之间进行导航。这不是很好吗?

Well, the problem arises if your data set does not contain the fields you want to display. In those cases, you must add a column to the DataSet that computes the value needed for the user interface. If the value can be computed using a SQL expression, the DataSet can compute the value for you. The following code adds a column n to the Employees data table that displays a formatted version of the name:

好的,如果你的数据集合不包含你想要显示的字段,就会有问题了。在那些情况下,你必须向DataSet添加一个列,来计算用户接口需要的数值。如果该值可以通过SQL表达式来计算的话,DataSet就可以为你计算该值。下面的代码向Employees数据表添加了一个列n,显示名字的一个格式化过的版本:

  1. DataTable dt = data.Tables[ "Employees" ];
  2. dt.Columns.Add( "EmployeeName",  typeofstring ),   "lastname + ', ' + firstname");

 

By adding columns to the DataSet, you can add columns to the DataGrid. You build layers of objects on top of the stored data objects to create the data presentation you want to give the user.

通过向DataSet添加列,就可以向DataGrid添加列。在存储的数据对象上部构建对象层,给你的用户创建数据表述层。

All the items I showed you so far are string types. The framework does handle converting strings to numeric values: It tries to convert the user's input to the proper type. If that fails, the original value is restored. It works, but the user gets absolutely no feedback, their input is silently ignored. You add that feedback by processing the Parse event from the binding context. That event occurs when the binding manager updates the value in the data source from the value in the control. ParseEventArgs gives you the text typed by the user and the desired type to convert the text. You can trap this event and perform your own notification, even going so far as to modify the value and update the text with your own value:

迄今为止我向你展示的所有代码都是string类型。框架对将string转化成数值型:它尝试将用户的输入转换成合适的类型。如果失败,就重新存储原来的值。这是可以的,但是用户却得不到反馈,它们的输入被无声的丢弃了。通过处理来自绑定上下文的解析事件,你可以添加反馈。当绑定管理者根据控件上的值对数据源的值进行更新时,该事件就发生。ParseEventArgs给你提供用户输入的文字,以及将文字需要转换成的类型。你可以捕捉该事件,并且执行自己的通知,甚至可以进一步修改这值,用你自己的值来更新文字:

  1. private void Form1_Parse( object sender, ConvertEventArgs e )
  2. {
  3.   try {
  4.  Convert.ToInt32 ( e.Value ); }
  5. catch
  6.   {
  7.     MessageBox.Show ( string.Format( "{0} is not an integer", e.Value.ToString( ) ) );
  8.     e.Value = 0;
  9.   }
  10. }

 

You might also want to handle the Format event. This is the hook that lets you format the data that comes from your data source and goes into the control. You can modify the Value field of ConvertEventArgs to format the string that should be displayed.

你可能也希望去处理Format事件。这是一个钩子,让你可以格式化来自数据源发往控件的数据。你可以修改ConvertEventArgsValue字段,来格式化需要显式的字符串。

The .NET Framework provides the generic framework for you to support data binding. Your job is to provide the specific event handlers for your application and your data. Both the Windows Forms and Web forms subsystems contain rich data-binding capabilities. The library already contains all the tools you need, so your UI code should really be describing the data sources and properties to be displayed and what rules should be followed when you store those elements back in the data source. You concentrate on building the data types that describe the display parameters, and the Winforms and Webforms data binding does the rest. There is no way around writing the code that transfers values between the user controls and the data source objects. Somehow, data must get from your business objects to the controls that your users interact with. But by building layers of types and leveraging data-binding concepts, you write a lot less of it. The framework handles the transfers for you, in both Windows and web applications.

.Net框架提供了通用的框架来支持数据绑定。你的工作就是为应用程序提供特定的事件句柄和数据。Windows窗体和Web窗体子系统都包含富数据绑定能力。框架库已经包含了你需要的所有工具,因此,你的UI代码应该描述数据源和需要被显式的属性,以及当你将那些元素存储回数据源时,应该遵循什么原则。你应该关注构建描述显示参数这样的数据类型,WinformsWebforms数据绑定会做剩下的工作。没办法编写这样的代码:在用户控件和数据源对象之间传递数值。不管怎样,数据必须从你的业务对象传递到用户进行交互的控件上。但是通过构建类型层面,并利用数据绑定概念,你可以少写很多代码。在Windowsweb应用程序中,框架都为你处理了传输工作。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值