Effective C# 原則38:使用和支援資料繫結(譯)

來源:互聯網
上載者:User

Effective C# 原則38:使用和支援資料繫結
Item 38: Utilize and Support Data Binding

有經驗的Windows程式員一定對寫代碼從一個控制項上取值,以及把值儲存到控制項上很熟悉:

public Form1 : Form
{
  private MyType myDataValue;
  private TextBox textBoxName;

  private void InitializeComponent( )
  {
    textBoxName.Text = myDataValue.Name;
    this.textBoxName.Leave += new
      System.EventHandler( this.OnLeave );
  }

  private void OnLeave( object sender, System.EventArgs e )
  {
    myDataValue.Name = textBoxName.Text;
  }
}

這太簡單了,正如你知道的,重複代碼。之所以不喜歡這樣重複代碼,就是因為應該有更好的方法。是的,.Net架構支援資料繫結,它可以把一個對象的屬性對應到控制項的屬性上:

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

上面的代碼就把textBoxName控制項的“Text”屬性上綁定了MyDataValue對象的"Name"屬性。在內部有兩個對象,綁定管理(BindingManager)和流通管理(CurrencyManager), 實現了在控制項與資料來源之間的傳輸實現。你很可能已經見過為種結構的例子,特別是在DataSet和DataGrid之間的。你也很可能已經做過資料繫結的例子。你很可能只在表面上簡單的使用過從資料繫結上得到的功能。你可以通過高效的資料繫結避免寫重複的代碼。

關於資料繫結的完整處理方案可能至少要花上一本書來說明,要不就是兩本。Windows應用程式和Web應用程式同時都支援資料繫結。比寫一個完整的資料繫結論述要強的是,我確實想讓你記住資料繫結的核心好處。首先,使用資料繫結比你自己寫代碼要簡單得多。其次,你應該在對文字元素通過屬性來顯示時,儘可能的使用它,它可以很好的綁定。第三,在Windows表單中,可以同步的對綁定在多控制項上的資料,進行相關資料來源的檢測。

例如,假設只要在資料不合法時,要求將文字顯示為紅色,你可能會寫這樣的代碼:

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

這很好,但只要在文字源發生改變時,你要隨時調用這段代碼。這可能是在使用者編輯了文字,或者是在底層的資料來源發生改變時。這裡有太多的事件要處理了,而且很多地方你可能會錯過。但,使用資料繫結時,在src對象上添加一個屬性,返回恰當的前景顏色就行了。

另一個邏輯可能是要根據文字訊息的狀態,來設定值可變化為恰當顏色的值:

private Color _clr = Color.Black;
public Color ForegroundColor
{
  get
  {
    return _clr;
  }
}

private string _txtToDisplay;
public string Text
{
  get
  {
    return _txtToDisplay;
  }
  set
  {
    _txtToDisplay = value;
    UpdateDisplayColor( IsTextValid( ) );
  }
}

private void UpdateDisplayColor( bool bValid )
{
  _clr = ( bValid ) ? Color.Black : Color.Red;
}

簡單的添加綁定到文字框裡就行了:

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

當資料繫結配置好以後,textBox1會根據內部來源物件的值,用正確的顏色來繪製文本。這樣,你就已經大大減少了從來源資料到控制項的資料來回傳輸。不再須要對不同地方顯示不同顏色來處理很多事件了。你的資料來源對象保持對屬性的正確顯示進行跟蹤,而表單控制項對資料繫結進行控制。

通過這個例子,我示範了Windows表單的資料繫結,同樣的在web應用程式中也是一樣的原則:你可以很好的綁定資料來源的屬性到web控制項的屬性上:

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

這就是說,當你建立一個應用程式在UI上顯示的類型時,你應該添加一些必須的屬性來建立和更新你的UI,以便使用者在必要時使用。

當你的對象不支援你要的屬性時怎麼辦呢?那就把它封裝成你想要的。看這樣的資料結構:

public struct FinancialResults
{
  public decimal Revenue
  {
    get { return _revenue; }
  }

  public int NumberOfSales
  {
    get { return _numSales; }
  }

  public decimal Costs
  {
    get { return _cost;}
  }

  public decimal Profit
  {
    get { return _revenue - _cost; }
  }
}

要求你在一個表單上以特殊的格式資訊來顯示這些,如果收益為負,你必須以紅色來顯示收益。如果薪水小於100,你應該用粗體顯示。如果開銷在10千(1萬)以上,你也應該用粗體顯示。建立FinancialResults結構的開發人員沒有添加UI功能到這個結構上。這很可能是正確的選擇,FinancialResults應該限制它的功能,只用於儲存實際的值。你可以建立一個新類型,包含UI格式化屬性,以及在FinancialResults結構中的原始的儲存屬性:

public struct FinancialDisplayResults
{
  private FinancialResults _results;
  public FinancialResults Results
  {
    get { return _results; }
  }

  public Color ProfitForegroundColor
  {
    get
    {
      return ( _results.Profit >= 0 ) ?
        Color.Black : Color.Red;
    }
  }

  // other formatting options elided
}

這樣,你就建立了一個簡單的資料結構來協助你所包含的資料結構來進行資料繫結:

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

我已經建立了一個唯讀屬性,用於訪問核心的財政資料結構。這種構造在你試圖支援對資料的讀寫操作時不能工作,FinancialResults結構是實值型別,這就是說擷取訪問器不提供對儲存空間的訪問,它只是返回一個拷貝。這樣的方式很樂意返回一個拷貝,而這樣的拷貝並不能在資料繫結中進行修改。然而,如果你試圖對資料進行編輯時,FinancialResults類應該是一個類,而不是一個結構(參見原則6)。做為一個參考型別,你的擷取訪問器返回一個內部儲存的引用,而且可以被使用者編輯。內部的結構應該須要對儲存的資料發生改變時做出響應。FinancialResults應該觸發事件來告訴其它代碼這一狀態的改變。

有一個很重要的事情要記住:把資料來源用在同一表單中的所有相關控制項上。使用DataMember屬性來區別每個控制項顯示的屬性。你可以像這樣寫綁定過程:

// Bad practice: creates two binding managers
textBox1.DataBindings.Add ("Text",src.Results, "Profit");
textBox1.DataBindings.Add ("ForeColor",src,“rofitForegroundColor");

這會建立兩個綁定管理者,一個為src對象,另一個為src.Results對象。每個資料來源由不同的綁定管理者控制,如果你想讓綁定管理者在資料來源發生改變時,更新所有的屬性,你須要確保資料來源是一致的。

你幾乎可以在所有的Windows控制項和web控制項上使用資料繫結。在控制項裡顯示的值,字型,唯讀狀態,甚至是控制項控制項的位置,都可以成為綁定操作的對象。我的建議是建立類或者結構,包含一些使用者要求的,以某種樣式顯示的資料。這些資料就是用於更新控制項。

另外,在簡單控制項中,資料繫結經常出現在DataSet和DataGrids中。這非常有用,你把DataGrid綁定到DataSet上,然後DataSet中所有的值就顯示了。如果你的DataSet有多個表,你甚至還可以在多個表中間進行導航。這不是很好嗎?

好了,下面的問題就是如果你的資料集不包含你想顯示的欄位時該怎麼辦。這時,你必須添加一個列到DataSet中,這一列計算一些UI中必須的值。如果值可以用SQL運算式計算,那麼DataSet可以為你完成。下面的代碼就添加了一個列到Employees 資料表中,用於顯示格式化了名字:

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

通過添加列到DataSet中,你可以添加這些列到DataGrid上。你所建立的對象層,是在資料存放區對象的最項層上,用於建立資料表現層給你的使用者。
到目前為止,這一原則裡所使用的都是string類型,.net架構可以處理字元到數位轉化:它試圖轉化使用者的輸入到恰當的類型。如果失敗,原始的值會恢複。這是可以工作的,但使用者完全沒的反饋資訊,他們的輸出被安靜的忽略了。你可以通過處理綁定過程中的轉化事件來添加反饋資訊。這一事件在綁定管理者從控制項上更新值到資料來源時發生。ParseEventArgs包含了使用者輸入的文字 ,以及它所期望被轉化的類型。你可以捕獲這一事件,其後完成你自己的通知,也可以修改資料並且用你自己的值來更新資料:

private void Form1_Parse( object sender, ConvertEventArgs e )
{
  try {
    Convert.ToInt32 ( e.Value );
  } catch
  {
    MessageBox.Show (
     string.Format( "{0} is not an integer",
       e.Value.ToString( ) ) );
    e.Value = 0;
  }
}

你可能還要處理Format事件,這一個HOOK,可以在資料從資料來源到控制項時格式化資料。你可以修改ConvertEventArgs的Value欄位來格式化必須顯示的字串。

.Net提供了通用的架構,可以讓你支援資料繫結。你的工作就是為你的應用程式和資料提供一些特殊的事件控制代碼。Windows表單和Web表單以及子系統都包含了豐富的資料繫結功能。架構庫已經包含了所有你須要的工具,因此,你的UI代碼應該真實的描述資料來源和要顯示的屬性,以及在把這些元素儲存到資料來源時須要遵守的規則。你應該集中精力建立資料類型,用於描述顯示的參數,然後Winform以及Webform的資料繫結完成其它的。不應該在把資料從使用者控制項到資料來源之間進行傳輸時寫相關的代碼(譯註:指用資料繫結,而不用其它的方法)。不管怎樣,資料必須從你的業務對象關聯到UI控制項上與使用者進行互動。通過建立類型層以及使用資料繫結的概念,你就可以少寫很多代碼。.Net架構已經 同時在Windows和Web應用程式中為你處理了傳輸的工作。

========================================

    

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:

public Form1 : Form
{
  private MyType myDataValue;
  private TextBox textBoxName;

  private void InitializeComponent( )
  {
    textBoxName.Text = myDataValue.Name;
    this.textBoxName.Leave += new
      System.EventHandler( this.OnLeave );
  }

  private void OnLeave( object sender, System.EventArgs e )
  {
    myDataValue.Name = textBoxName.Text;
  }
}

 

It's simple, repetitive codeyou 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:

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.

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 itemsother display properties can be bound as well. Third, on Windows forms, data binding handles synchronizing multiple controls that examine related data sources.

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:

if ( src.TextIsInvalid )
{
  textBox1.ForeColor = Color.Red;
} else
{
  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:

private Color _clr = Color.Black;
public Color ForegroundColor
{
  get
  {
    return _clr;
  }
}

private string _txtToDisplay;
public string Text
{
  get
  {
    return _txtToDisplay;
  }
  set
  {
    _txtToDisplay = value;
    UpdateDisplayColor( IsTextValid( ) );
  }
}

private void UpdateDisplayColor( bool bValid )
{
  _clr = ( bValid ) ? Color.Black : Color.Red;
}

 

Then simply add the binding to the 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.

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:

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

 

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.

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:

public struct FinancialResults
{
  public decimal Revenue
  {
    get { return _revenue; }
  }

  public int NumberOfSales
  {
    get { return _numSales; }
  }

  public decimal Costs
  {
    get { return _cost;}
  }

  public decimal Profit
  {
    get { return _revenue - _cost; }
  }
}

 

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:

public struct FinancialDisplayResults
{
  private FinancialResults _results;
  public FinancialResults Results
  {
    get { return _results; }
  }

  public Color ProfitForegroundColor
  {
    get
    {
      return ( _results.Profit >= 0 ) ?
        Color.Black : Color.Red;
    }
  }

  // other formatting options elided
}

 

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

// Use the same datasource. That creates one Binding Manager
textBox1.DataBindings.Add ("Text",
  src, "Results.Profit");
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.

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:

// Bad practice: creates two binding managers
textBox1.DataBindings.Add ("Text",
src.Results, "Profit");
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.

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.

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?

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:

DataTable dt = data.Tables[ "Employees" ];
dt.Columns.Add( "EmployeeName",
  typeof( string ),
   "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.

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:

private void Form1_Parse( object sender, ConvertEventArgs e )
{
  try {
    Convert.ToInt32 ( e.Value );
  } catch
  {
    MessageBox.Show (
     string.Format( "{0} is not an integer",
       e.Value.ToString( ) ) );
    e.Value = 0;
  }
}

 

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.

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.

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.