天天看點

C# BindingSource

1.引言

BindingSource元件是資料源和控件間的一座橋,同時提供了大量的API和Event供我們使用。使用這些API我們可以将Code與各種具體類型資料源進行解耦;使用這些Event我們可以洞察資料的變化。 2.簡單綁定

    DataTable myTable = myTableAdapter.GetData();//建立Table

    BindingSource myBindingSource= new BindingSource();//建立BindingSource

    DataGridView myGrid = new DataGridView();//建立GridView

    myGrid.DataSource = myBindingSource;//将BindingSource綁定到GridView

    myTable;//綁定資料到BindingSource

    注:

    1)綁定到DataTable,其實是綁定到DataTable提供的DataView上。每個DataTable都有一個預設的DataView

    2)DataView是綁定的實質,正如其名,它是DataTable的資料的展現。是以可以對同一個DataTable

    ,建構多個DataView,進而可以對這同樣的資料實施不同的過濾、排序等方法,從不同側面展示DataTable。這也展現了一定的MVC思想。

    3)BindingSouce也可作為資料(其實是資料引用)的容器在不同窗體間傳遞,進而實作在彈出窗體中對資料的編輯 

3.主細表

    image

    以上圖所示資料為例:

    1)DataSet:myDataSet

    2)DataTable:ParentTable、ChildTable、GrandChildTable

    3)Relation:FK_Parent_Child、FK_Child_GrandChild

    //綁定父資料

    parentBindingSource.DataSource = myDataSet;

    parentBindingSource.DataMember = "ParentTable";

    m_GrandParentGrid.DataSource = m_GrandParentBindingSource;

    //綁定子資料。     childBindingSource.DataSource = parentBindingSource;//綁定到“父BindingSource”,而不是父Table

    childBindingSource.DataMember = "FK_Child_GrandChild";//綁定到“父-子Relation”

    //綁定孫子資料。     grandChildBindingSource.DataSource = childBindingSource;//綁定到“子BindingSource”

    grandChildBindingSource.DataMember = "FK_Child_GrandChild";//綁定到“子-孫Relation”

這樣你就可以在Form上擺上3個DataView,分布綁定到這3個BindingSouce,很容易就實作了主細表關聯展現。 4.資料操縱 要操縱資料,首先需要擷取目前資料項。BindingSource的Current屬性傳回DataRowView類型的對象(就像DataView是對 DataTable的封裝一樣,DataRowView是對DataRow的封裝),它是對目前資料項的封裝,可以通過類型轉換變成你想要的對象。

    DataRowView currentRowView = myBindingSource.Current;//擷取目前RowView

    CustomersRow custRow = currentRowView.Row as CustomersRow;//類型轉換為目前資料項

    string company = custRow.CompanyName;//使用目前資料項

    string phoneNo = custRow.Phone;

5.用BindingSource做資料容器

BindingSource還可以用作資料容器,即便它沒有綁定到資料源上,它内部有一個可以容納資料的list。 5.1Add方法

調用Add方法會在BindingSource的list中插入資料項。如果這時第一次插入資料,并且沒有綁定資料,那麼插入資料的類型就決定了今後此list中資料的類型。

    注:

    1)此時再插入其它類型對象會抛出InvalidOperationException異常

    2)設定DataSource屬性時會重新整理list,造成Add方法添加到list中的資料丢失

5.2AddNew方法

AddNew方法傳回BindingSourc所容納資料類型的對象;如果之前沒有容納資料,則會傳回Object對象。

AddNew方法會調用EndEdit方法,并将送出對目前資料的操縱;然後新資料項就成為目前項。

AddNew方法會引發AddingNew事件,可以在此事件中為資料項指派,或者建立新資料項

    private void OnAddingNew(object sender, AddingNewEventArgs e)     {           e.NewObject = new MyCustomObject();//     }

6.用BindingSource對資料排序、過濾、搜尋 6.1 Sort

為Sort屬性賦上Sort表達式,可以對資料進行排序

    myBindingSource.Sort = "ContactName ASC";//對ContanctName列按ASC進行排序

    myBindingSource.Sort = "Region ASC, CompanyName DESC"//先按Region、再按CompanyName排序

6.2 Find

    Find方法根據指定屬性和關鍵字進行查找,并傳回第一個比對對象的Index     int index = m_CustomersBindingSource.Find("CompanyName",IBM);//按CompanyName查找IBM     if (index != -1)     {         myBindingSource.Position = index;//定位BindingSource     }

6.3 Filter

為Filter屬性賦上表達式,可以對資料進行過濾

    m_CustomersBindingSource.Filter = "Country = 'Germany'";//過濾出Country屬性為Germany的資料

7.用Event監控資料 7.1 Event

    1)AddingNew

    調用AddNew()方法時觸發。

    2)BindingComplete

    當控件完成資料綁定時觸發,說明控件已經從資料源中讀取目前資料項的值。當BindingSource重新綁定或目前資料項改變時,會觸發此事件

    注:

        * 當有多個控件綁定到同一資料源時,這個事件會觸發多次

    3)CurrrentChanged

    目前資料項改變時觸發此事件。觸發此事件的情況如下

        * Position屬性改變時         * 添加、删除資料時         * DataSource或DataMember屬性改變時

    4)CurrentItemChanged

    目前資料項的值改變時觸發

    5)DataError

    通常輸入無效資料時,由CurrencyManage抛出異常,進而觸發此事件。

    6)PositionChanged

    Position屬性改變時觸發此事件。

    7)ListChanged

    資料集合改變時觸發。觸發此事件的情況如下

        * adding, editing, deleting, 或 moving 資料項時

    改變那些會影響List行為特征的屬性時,如AllowEdit屬性

        * 替換List時(綁到新資料源)

8.限制資料修改

BindingSource不僅是資料源與控件間的“橋梁”,同時也是資料源的“看門人”。通過BindingSource,我們可以控制對資料的修改。

BinidingSource的AllowEdit, AllowNew和AllowRemove屬性可以控制用戶端代碼和控件對資料的修改 9.複雜資料類型的Binding

對于String類型的資料,直接Binding到Text控件即可,對于複雜類型有下面幾種情況

    * 對于DateTime、Image等類型的資料,它們存儲的格式與顯示要求并不一緻。     * 有時,你并不想顯示客戶ID,而是希望顯示客戶名稱     * 資料庫中的Null值

9.1 Binding類

解決以上問題的關鍵是要了解Binding類,了解它是如何控制資料Binding的過程。

    DataTable table = customersDataSet.Customers;

    //将TextBox的Text屬性Binding到table的CustomerID列     customerIDTextBox.DataBindings.Add("Text", table,"CustomerID", true);

    //上面一行代碼等同下面兩行代碼

    Binding customerIDBinding = new Binding("Text", table,"CustomerID", true);     customerIDTextBox.DataBindings.Add(customerIDBinding);

從代碼可以看出,Binding是資料源(table)和控件(customerIDTextBox)間的中介人,它有以下功能

    * 從資料源取資料,并按照控件要求的資料類型對此資料進行格式化(Formatting),然後傳給控件     * 從控件取資料,并按照資料源的資料類型要求對此資料進行解析(Parsing),然後傳回給資料源     * 自動對資料進行格式轉換

9.2Binding類構造函數和屬性

Binding構造函數有多個重載版本,下面介紹其重要的參數,這些參數同時存在于Binding對象的屬性中。下面介紹中,參數名和屬性名都列出來

    1)formattingEnabled(屬性FormattingEnabled)

          o true,Binding對象自動在資料源類型和控件要求的類型間進行轉換           o false,反之

    2)dataSourceUpdateMode

    決定控件上數值的改變在何時送出回資料源

    3)nullValue

    DBNull、 null和Nullab<T>對應的值。

    4)formatString

    格式轉換

    5)formatInfo

    一個實作IFormatProvider接口的對象引用,用來自定義格式轉換

要了解類型如何轉換的,請學習Type Conversions and Format Providers相關内容。關于上面屬性的應用,請看下面介紹 9.3基于Binding類的内置機制(屬性、參數)進行類型轉換

通過Binding類構造時的參數,或屬性設定,可以控制它進行類型轉換的機制。

1)DateTime

下面先介紹一個DateTime類型的例子,使用DateTimePicker控件

    //建立Binding,設定formattingEnabled為true

    birthDateTimePicker.DataBindings.Add("Value",m_EmployeesBindingSource, "BirthDate", true);

    //設定為使用自定義格式     birthDateTimePicker.Format = DateTimePickerFormat.Custom;

    //設定格式     birthDateTimePicker.CustomFormat = "MM/dd/yyyy";

2)Numeric

    salaryTextBox.DataBindings.Add("Text", employeesBindingSource,"Salary", true,  DataSourceUpdateMode.OnValidation,"<not specified>", "#.00");

    以上代碼做了以下處理

        * 設定formattingEnabled為true:代表自動類型轉換         * 設定DataSourceUpdateMode為OnValidation:         * 設定nullValue為"<not specified>":這些DBNull就顯示為,"<not specified>", 同時使用者錄入,"<not specified>"時,資料值為DBNull         * 設定formatString為"#.00":數值保留2位小數

9.4. 事件

下面介紹Binding的主要事件,以及如何基于這些事件進行類型轉換的控制。

主要事件:

1)Format事件

發生在從資料源擷取資料後,控件顯示此資料之前。在這個事件裡将資料源的資料類型轉換為控件要求的資料類型。

2)Parse事件

與Event相反。它發生控件值改變後,資料更新回資料源之前。在這個事件裡将控件的資料類型轉換為資料源要求的資料類型。

這兩個事件為我們控制資料提供了機制,它們都聲明為ConvertEventHandler類型,

    void ConvertEventHandler(object sender, ConvertEventArgs e);

有兩個參數,第二個參數ConvertEventArgs e 提供了我們要formatting和parsing的資料。它有兩個屬性

    * e.DesiredType是數值要轉換的目标類型     * e.Value是要轉換的數值。我們可以替換此Value

9.5. 基于事件的類型轉換 9.5.1 處理Format Event

    void OnCountryFromFormat(object sender, ConvertEventArgs e)     {         if (e.Value == null || e.Value == DBNull.Value)         {              pictureBox.Image = null;              return;         }

        //綁定的是資料源的CountryID字段,是以e.Value傳回的ID号,通過此ID号取得對應資料行         CountriesRow countryRow =    GetCountryRow((int)e.Value);

         //将e.Value指派為CountryName,進而在控件中顯示名稱          e.Value = countryRow.CountryName;         // 資料轉換

        ImageConverter converter = new ImageConverter();         pictureBox.Image =    converter.ConvertFrom(countryRow.Flag) as Image;     }

9.5.2 處理Format Event

void OnCountryFromParse(object sender, ConvertEventArgs e) { // Need to look up the Country information for the country name ExchangeRatesDataSet.CountriesRow row = GetCountryRow(e.Value.ToString()); if (row == null) { string error = "Country not found"; m_ErrorProvider.SetError(m_CountryFromTextBox, error); m_CountryFromTextBox.Focus(); throw new ArgumentException(error); } e.Value = row.CountryID; } 10 完成資料編輯

經常會遇到這種情況,你在一個控件中錄入或選擇一些資料,隻有當年離開此控件時,關聯的資料才能同步更新。這個問題是由DataRow内部機制決定的。

DataRowView類實作IEditableObject接口,支援對象的事務性編輯(當你确認完成編輯前,可以復原資料)。我們通過BeginEdit()方法來開始資料編輯,通過EndEdit()方法送出編輯。

不要将DataRowView的EndEdit()與DataSet、DataTable、DataRow的AcceptChanges()方法混淆。 DataRow有original和current版本,同時IEditableObject的caching機制讓它有transient版本,在調用 EndEdit()方法前,資料修改是不會送出到資料源。這就是前面問題的内在原因。

如果希望編輯的資料立即送出,那調用 EndEdit()函數的最佳位置就是Validated事件。Validate事件在控件錄入的資料parsed,并且通過validate後觸發,在這個事件中觸發EndEdit()就會通知綁定到同一資料源的所有控件,進而實作資料同步更新。

    private void OnCountryTextValidated(object sender, EventArgs e)     {               exchangeRatesBindingSource.EndEdit();     }

當然,目前資料項改變時,也會觸發EndEdit()事件 11 使用AutoComplete

當你希望TexbBox或ComboBox中會自動提示功能,那你應該學習一下AutoComplete功能。下面以TextBox為例介紹相關步驟

1)設定TextBox的AutoCompleteSource屬性:FileSystem, HistoryList, RecentlyUsedList

2)如果希望使用自定義的清單,則設定AutoCompleteSource屬性為CustomSource

3)設定AutoCompleteMode為SuggestAppend。這意味着你輸入部分字元時,控件在下拉清單中提示所有相近的資料

4)如果不想使用内置的提示源,你可以自己建立一個AutoCompleteStringCollection類的清單,

5)建立這個清單後,将它賦給TextBox的AutoCompleteCustomSourc屬性 12 DataBinding的生命周期

BindingSource的DataSourceUpdateMode屬性是關鍵,它有以下三種可能值,下面分布以TextBox控件為例介紹此屬性不同時DataBinding的生命周期

1)OnValidating(預設值)

    * DataBinding的生命周期:

    TextBox.Leave, TextBox.Validating, Binding.Parse, TextBox.Validated

    * 此時若将控件的CausesValidation屬性設為false,那麼Validating事件就不會發生

2)OnPropertyChanged

    * DataBinding的生命周期:

    此時,每次控件值發生改變時都會觸發Binding.Parse。對TextBox控件來說,每次錄入字元都會觸發Binding.Parse。

3)Never

    此時Parse事件不會觸發,也就是說控件将成為隻讀的。

13 子父綁定

前面介紹了主細綁定,它其實是一個父子綁定。有時我們希望由子到父的關聯綁定,下面我們就一起來實作這個機制。實作這個機制的關鍵還是Event,這個Event就是BindingSource的CurrentChanged事件

    private void OnCurrentChanged(object sender, EventArgs e)           {              // 擷取目前的子DataRow              ExchangeRatesDataSet.ExchangeRatesRow currentRow =                 (ExchangeRatesDataSet.ExchangeRatesRow)                 ((DataRowView)m_ExchangeRatesBindingSource.Current).Row;

             // 擷取關聯的父DataRow              ExchangeRatesDataSet.CountriesRow fromCountryRow =                 currentRow.CountriesRowByFK_ExchangeRates_CountriesFrom;              ExchangeRatesDataSet.CountriesRow toCountryRow =                 currentRow.CountriesRowByFK_ExchangeRates_CountriesTo;

             //顯示父DataRow的資訊

             if (fromCountryRow != null && toCountryRow != null)              {                 m_FromCountryCombo.SelectedValue = fromCountryRow.CountryID;                 m_ToCountryCombo.SelectedValue = toCountryRow.CountryID;              }

    }

14 綁定到資料的多個複本

有 時,我們希望以不同角度看到同一資料,這時需要綁定到同一資料的多個複本。這裡的關鍵是CurrencyManager類,每個 BindingSource管理着一個CurrencyManager。如果多個控件綁定到同一個BindingSource,那麼隻有一個 CurrencyManager,是以也就隻有一個CurrentItem,這樣就造成這些綁定到同一BindingSource的控件同步重新整理。要解決這個問題,我們需要多個CurrencyManager,也就是說我們可以建立多個BindingSource,且綁定到同一個資料源。

  9.5 處理Null類型

這裡有兩個概念要弄清楚,.Net内置的Null類型與代表資料庫中的Null類型,以及它們的差別。

    1).Net内置的Null類型

        * Nullable,引用類型         * Nuallable<T>,值類型

    2).Net用來代表資料庫中的Null類型

        * DBNull,它有一個屬性Value,可以用來判斷資料是否為DBNull

              if (northwindDataSet.Employees[0].Country == DBNull.Value)              {                      // Handle null case here               }

             對強類型資料集

    if (northwindDataSet.Employees[0].IsCountryNull())     {     // Handle null case here     }

  1)AddNew()函數:用來添加一條資料,傳回類型由綁定的DataSource決定。

    1)綁定到DataSet/DataTable時,傳回DataRowView對象。

    注意:

          a)傳回的不是DataSet或DataTable或DataRow。

          b)如果希望擷取添加的資料,需要進行類型轉換

               //bs為你建立的BindingSource

              DataRow row=(DataRow)((DataRowView) bs.AddNew()).Row;

           c)使用TypedDataSet時,轉換方法與上面類似,隻是用TypedDataRow而已

             //MyDataRow為你定義的TypedDataRow

              MyDataRow row=(MyDataRow)((DataRowView) bs.AddNew()).Row;