1.引言 BindingSource組件是數(shù)據(jù)源和控件間的一座橋,同時提供了大量的API和Event供我們使用。使用這些API我們可以將Code與各種具體類型數(shù)據(jù)源進(jìn)行解耦,;使用這些Event我們可以洞察數(shù)據(jù)的變化,。
2.簡單綁定
DataTable myTable = myTableAdapter.GetData();//創(chuàng)建Table
BindingSource myBindingSource= new BindingSource();//創(chuàng)建BindingSource
DataGridView myGrid = new DataGridView();//創(chuàng)建GridView
myGrid.DataSource = myBindingSource,;//將BindingSource綁定到GridView
myTable;//綁定數(shù)據(jù)到BindingSource
注:
1)綁定到DataTable,其實(shí)是綁定到DataTable提供的DataView上,。每個DataTable都有一個缺省的DataView
2)DataView是綁定的實(shí)質(zhì),,正如其名,它是DataTable的數(shù)據(jù)的展現(xiàn),。因此可以對同一個DataTable
,,構(gòu)建多個DataView,進(jìn)而可以對這同樣的數(shù)據(jù)實(shí)施不同的過濾、排序等方法,,從不同側(cè)面展示DataTable,。這也體現(xiàn)了一定的MVC思想。
3)BindingSouce也可作為數(shù)據(jù)(其實(shí)是數(shù)據(jù)引用)的容器在不同窗體間傳遞,,從而實(shí)現(xiàn)在彈出窗體中對數(shù)據(jù)的編輯
3.主細(xì)表
以上圖所示數(shù)據(jù)為例:
1)DataSet:myDataSet
2)DataTable:ParentTable,、ChildTable、GrandChildTable
3)Relation:FK_Parent_Child,、FK_Child_GrandChild
//綁定父數(shù)據(jù)
parentBindingSource.DataSource = myDataSet;
parentBindingSource.DataMember = "ParentTable";
m_GrandParentGrid.DataSource = m_GrandParentBindingSource;
//綁定子數(shù)據(jù)。 childBindingSource.DataSource = parentBindingSource;//綁定到“父BindingSource”,,而不是父Table
childBindingSource.DataMember = "FK_Child_GrandChild";//綁定到“父-子Relation”
//綁定孫子數(shù)據(jù),。 grandChildBindingSource.DataSource = childBindingSource;//綁定到“子BindingSource”
grandChildBindingSource.DataMember = "FK_Child_GrandChild";//綁定到“子-孫Relation”
這樣你就可以在Form上擺上3個DataView,分布綁定到這3個BindingSouce,,很容易就實(shí)現(xiàn)了主細(xì)表關(guān)聯(lián)展現(xiàn),。
4.數(shù)據(jù)操縱
要操縱數(shù)據(jù),首先需要獲取當(dāng)前數(shù)據(jù)項(xiàng),。BindingSource的Current屬性返回DataRowView類型的對象(就像DataView是對DataTable的封裝一樣,,DataRowView是對DataRow的封裝),它是對當(dāng)前數(shù)據(jù)項(xiàng)的封裝,,可以通過類型轉(zhuǎn)換變成你想要的對象,。
DataRowView currentRowView = myBindingSource.Current;//獲取當(dāng)前RowView
CustomersRow custRow = currentRowView.Row as CustomersRow;//類型轉(zhuǎn)換為當(dāng)前數(shù)據(jù)項(xiàng)
string company = custRow.CompanyName;//使用當(dāng)前數(shù)據(jù)項(xiàng)
string phoneNo = custRow.Phone;
5.用BindingSource做數(shù)據(jù)容器
BindingSource還可以用作數(shù)據(jù)容器,,即便它沒有綁定到數(shù)據(jù)源上,,它內(nèi)部有一個可以容納數(shù)據(jù)的list。
5.1Add方法
調(diào)用Add方法會在BindingSource的list中插入數(shù)據(jù)項(xiàng),。如果這時第一次插入數(shù)據(jù),,并且沒有綁定數(shù)據(jù),那么插入數(shù)據(jù)的類型就決定了今后此list中數(shù)據(jù)的類型,。
注:
1)此時再插入其它類型對象會拋出InvalidOperationException異常
2)設(shè)置DataSource屬性時會刷新list,,造成Add方法添加到list中的數(shù)據(jù)丟失
5.2AddNew方法
AddNew方法返回BindingSourc所容納數(shù)據(jù)類型的對象;如果之前沒有容納數(shù)據(jù),,則會返回Object對象,。
AddNew方法會調(diào)用EndEdit方法,,并將提交對當(dāng)前數(shù)據(jù)的操縱;然后新數(shù)據(jù)項(xiàng)就成為當(dāng)前項(xiàng),。
AddNew方法會引發(fā)AddingNew事件,,可以在此事件中為數(shù)據(jù)項(xiàng)賦值,或者創(chuàng)建新數(shù)據(jù)項(xiàng)
private void OnAddingNew(object sender, AddingNewEventArgs e) { e.NewObject = new MyCustomObject();// }
6.用BindingSource對數(shù)據(jù)排序,、過濾,、搜索
6.1 Sort
為Sort屬性賦上Sort表達(dá)式,可以對數(shù)據(jù)進(jìn)行排序
myBindingSource.Sort = "ContactName ASC";//對ContanctName列按ASC進(jìn)行排序
myBindingSource.Sort = "Region ASC, CompanyName DESC"http://先按Region,、再按CompanyName排序
6.2 Find
Find方法根據(jù)指定屬性和關(guān)鍵字進(jìn)行查找,,并返回第一個匹配對象的Index int index = m_CustomersBindingSource.Find("CompanyName",IBM);//按CompanyName查找IBM if (index != -1) { myBindingSource.Position = index;//定位BindingSource }
6.3 Filter
為Filter屬性賦上表達(dá)式,可以對數(shù)據(jù)進(jìn)行過濾
m_CustomersBindingSource.Filter = "Country = 'Germany'";//過濾出Country屬性為Germany的數(shù)據(jù)
7.用Event監(jiān)控?cái)?shù)據(jù)
7.1 Event
1)AddingNew
調(diào)用AddNew()方法時觸發(fā),。
2)BindingComplete
當(dāng)控件完成數(shù)據(jù)綁定時觸發(fā),,說明控件已經(jīng)從數(shù)據(jù)源中讀取當(dāng)前數(shù)據(jù)項(xiàng)的值。當(dāng)BindingSource重新綁定或當(dāng)前數(shù)據(jù)項(xiàng)改變時,,會觸發(fā)此事件
注:
- 當(dāng)有多個控件綁定到同一數(shù)據(jù)源時,,這個事件會觸發(fā)多次
3)CurrrentChanged
當(dāng)前數(shù)據(jù)項(xiàng)改變時觸發(fā)此事件。觸發(fā)此事件的情況如下
- Position屬性改變時
- 添加,、刪除數(shù)據(jù)時
- DataSource或DataMember屬性改變時
4)CurrentItemChanged
當(dāng)前數(shù)據(jù)項(xiàng)的值改變時觸發(fā)
5)DataError
通常輸入無效數(shù)據(jù)時,,由CurrencyManage拋出異常,從而觸發(fā)此事件,。
6)PositionChanged
Position屬性改變時觸發(fā)此事件,。
7)ListChanged
數(shù)據(jù)集合改變時觸發(fā)。觸發(fā)此事件的情況如下
- adding, editing, deleting, 或 moving 數(shù)據(jù)項(xiàng)時
改變那些會影響List行為特征的屬性時,,如AllowEdit屬性
8.限制數(shù)據(jù)修改
BindingSource不僅是數(shù)據(jù)源與控件間的“橋梁”,,同時也是數(shù)據(jù)源的“看門人”。通過BindingSource,,我們可以控制對數(shù)據(jù)的修改,。
BinidingSource的AllowEdit, AllowNew和AllowRemove屬性可以控制客戶端代碼和控件對數(shù)據(jù)的修改
9.復(fù)雜數(shù)據(jù)類型的Binding
對于String類型的數(shù)據(jù),直接Binding到Text控件即可,,對于復(fù)雜類型有下面幾種情況
- 對于DateTime,、Image等類型的數(shù)據(jù),它們存儲的格式與顯示要求并不一致,。
- 有時,,你并不想顯示客戶ID,而是希望顯示客戶名稱
- 數(shù)據(jù)庫中的Null值
9.1 Binding類
解決以上問題的關(guān)鍵是要理解Binding類,,了解它是如何控制數(shù)據(jù)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是數(shù)據(jù)源(table)和控件(customerIDTextBox)間的中介人,,它有以下功能
- 從數(shù)據(jù)源取數(shù)據(jù),,并按照控件要求的數(shù)據(jù)類型對此數(shù)據(jù)進(jìn)行格式化(Formatting),,然后傳給控件
- 從控件取數(shù)據(jù),并按照數(shù)據(jù)源的數(shù)據(jù)類型要求對此數(shù)據(jù)進(jìn)行解析(Parsing),,然后返回給數(shù)據(jù)源
- 自動對數(shù)據(jù)進(jìn)行格式轉(zhuǎn)換
9.2Binding類構(gòu)造函數(shù)和屬性
Binding構(gòu)造函數(shù)有多個重載版本,,下面介紹其重要的參數(shù),這些參數(shù)同時存在于Binding對象的屬性中,。下面介紹中,,參數(shù)名和屬性名都列出來
1)formattingEnabled(屬性FormattingEnabled)
- true,Binding對象自動在數(shù)據(jù)源類型和控件要求的類型間進(jìn)行轉(zhuǎn)換
- false,,反之
2)dataSourceUpdateMode
決定控件上數(shù)值的改變在何時提交回?cái)?shù)據(jù)源
3)nullValue
DBNull,、 null和Nullab<T>對應(yīng)的值。
4)formatString
格式轉(zhuǎn)換
5)formatInfo
一個實(shí)現(xiàn)IFormatProvider接口的對象引用,,用來自定義格式轉(zhuǎn)換
要了解類型如何轉(zhuǎn)換的,,請學(xué)習(xí)Type Conversions and Format Providers相關(guān)內(nèi)容。關(guān)于上面屬性的應(yīng)用,,請看下面介紹
9.3基于Binding類的內(nèi)置機(jī)制(屬性,、參數(shù))進(jìn)行類型轉(zhuǎn)換
通過Binding類構(gòu)造時的參數(shù),或?qū)傩栽O(shè)置,,可以控制它進(jìn)行類型轉(zhuǎn)換的機(jī)制,。
1)DateTime
下面先介紹一個DateTime類型的例子,使用DateTimePicker控件
//創(chuàng)建Binding,,設(shè)置formattingEnabled為true
birthDateTimePicker.DataBindings.Add("Value",m_EmployeesBindingSource, "BirthDate", true);
//設(shè)定為使用自定義格式 birthDateTimePicker.Format = DateTimePickerFormat.Custom;
//設(shè)定格式 birthDateTimePicker.CustomFormat = "MM/dd/yyyy";
2)Numeric
salaryTextBox.DataBindings.Add("Text", employeesBindingSource,"Salary", true, DataSourceUpdateMode.OnValidation,"<not specified>", "#.00");
以上代碼做了以下處理
- 設(shè)定formattingEnabled為true:代表自動類型轉(zhuǎn)換
- 設(shè)定DataSourceUpdateMode為OnValidation:
- 設(shè)定nullValue為"<not specified>":這些DBNull就顯示為,"<not specified>", 同時用戶錄入,"<not specified>"時,數(shù)據(jù)值為DBNull
- 設(shè)定formatString為"#.00":數(shù)值保留2位小數(shù)
9.4. 事件
下面介紹Binding的主要事件,,以及如何基于這些事件進(jìn)行類型轉(zhuǎn)換的控制,。
主要事件:
1)Format事件
發(fā)生在從數(shù)據(jù)源獲取數(shù)據(jù)后,控件顯示此數(shù)據(jù)之前,。在這個事件里將數(shù)據(jù)源的數(shù)據(jù)類型轉(zhuǎn)換為控件要求的數(shù)據(jù)類型,。
2)Parse事件
與Event相反。它發(fā)生控件值改變后,,數(shù)據(jù)更新回?cái)?shù)據(jù)源之前,。在這個事件里將控件的數(shù)據(jù)類型轉(zhuǎn)換為數(shù)據(jù)源要求的數(shù)據(jù)類型。
這兩個事件為我們控制數(shù)據(jù)提供了機(jī)制,,它們都聲明為ConvertEventHandler類型,,
void ConvertEventHandler(object sender, ConvertEventArgs e);
有兩個參數(shù),第二個參數(shù)ConvertEventArgs e 提供了我們要formatting和parsing的數(shù)據(jù),。它有兩個屬性
- e.DesiredType是數(shù)值要轉(zhuǎn)換的目標(biāo)類型
- e.Value是要轉(zhuǎn)換的數(shù)值,。我們可以替換此Value
9.5. 基于事件的類型轉(zhuǎn)換
9.5.1 處理Format Event
void OnCountryFromFormat(object sender, ConvertEventArgs e) { if (e.Value == null || e.Value == DBNull.Value) { pictureBox.Image = null; return; }
//綁定的是數(shù)據(jù)源的CountryID字段,因此e.Value返回的ID號,,通過此ID號取得對應(yīng)數(shù)據(jù)行 CountriesRow countryRow = GetCountryRow((int)e.Value);
//將e.Value賦值為CountryName,從而在控件中顯示名稱 e.Value = countryRow.CountryName; // 數(shù)據(jù)轉(zhuǎn)換
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 完成數(shù)據(jù)編輯
經(jīng)常會遇到這種情況,,你在一個控件中錄入或選擇一些數(shù)據(jù),,只有當(dāng)年離開此控件時,關(guān)聯(lián)的數(shù)據(jù)才能同步更新,。這個問題是由DataRow內(nèi)部機(jī)制決定的,。
DataRowView類實(shí)現(xiàn)IEditableObject接口,支持對象的事務(wù)性編輯(當(dāng)你確認(rèn)完成編輯前,,可以回滾數(shù)據(jù)),。我們通過BeginEdit()方法來開始數(shù)據(jù)編輯,通過EndEdit()方法提交編輯,。
不要將DataRowView的EndEdit()與DataSet,、DataTable、DataRow的AcceptChanges()方法混淆,。DataRow有original和current版本,,同時IEditableObject的caching機(jī)制讓它有transient版本,在調(diào)用EndEdit()方法前,,數(shù)據(jù)修改是不會提交到數(shù)據(jù)源,。這就是前面問題的內(nèi)在原因。
如果希望編輯的數(shù)據(jù)立即提交,,那調(diào)用EndEdit()函數(shù)的最佳位置就是Validated事件,。Validate事件在控件錄入的數(shù)據(jù)parsed,并且通過validate后觸發(fā),,在這個事件中觸發(fā)EndEdit()就會通知綁定到同一數(shù)據(jù)源的所有控件,,從而實(shí)現(xiàn)數(shù)據(jù)同步更新。
private void OnCountryTextValidated(object sender, EventArgs e) { exchangeRatesBindingSource.EndEdit(); }
當(dāng)然,,當(dāng)前數(shù)據(jù)項(xiàng)改變時,,也會觸發(fā)EndEdit()事件
11 使用AutoComplete
當(dāng)你希望TexbBox或ComboBox中會自動提示功能,那你應(yīng)該學(xué)習(xí)一下AutoComplete功能,。下面以TextBox為例介紹相關(guān)步驟
1)設(shè)定TextBox的AutoCompleteSource屬性:FileSystem, HistoryList, RecentlyUsedList
2)如果希望使用自定義的列表,,則設(shè)定AutoCompleteSource屬性為CustomSource
3)設(shè)定AutoCompleteMode為SuggestAppend。這意味著你輸入部分字符時,,控件在下拉列表中提示所有相近的數(shù)據(jù)
4)如果不想使用內(nèi)置的提示源,,你可以自己創(chuàng)建一個AutoCompleteStringCollection類的列表,
5)創(chuàng)建這個列表后,,將它賦給TextBox的AutoCompleteCustomSourc屬性
12 DataBinding的生命周期
BindingSource的DataSourceUpdateMode屬性是關(guān)鍵,,它有以下三種可能值,下面分布以TextBox控件為例介紹此屬性不同時DataBinding的生命周期
1)OnValidating(缺省值)
TextBox.Leave, TextBox.Validating, Binding.Parse, TextBox.Validated
- 此時若將控件的CausesValidation屬性設(shè)為false,,那么Validating事件就不會發(fā)生
2)OnPropertyChanged
此時,每次控件值發(fā)生改變時都會觸發(fā)Binding.Parse,。對TextBox控件來說,每次錄入字符都會觸發(fā)Binding.Parse。
3)Never
此時Parse事件不會觸發(fā),,也就是說控件將成為只讀的,。
13 子父綁定
前面介紹了主細(xì)綁定,它其實(shí)是一個父子綁定,。有時我們希望由子到父的關(guān)聯(lián)綁定,,下面我們就一起來實(shí)現(xiàn)這個機(jī)制。實(shí)現(xiàn)這個機(jī)制的關(guān)鍵還是Event,,這個Event就是BindingSource的CurrentChanged事件
private void OnCurrentChanged(object sender, EventArgs e) { // 獲取當(dāng)前的子DataRow ExchangeRatesDataSet.ExchangeRatesRow currentRow = (ExchangeRatesDataSet.ExchangeRatesRow) ((DataRowView)m_ExchangeRatesBindingSource.Current).Row;
// 獲取關(guān)聯(lián)的父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 綁定到數(shù)據(jù)的多個復(fù)本
有時,,我們希望以不同角度看到同一數(shù)據(jù),這時需要綁定到同一數(shù)據(jù)的多個復(fù)本,。這里的關(guān)鍵是CurrencyManager類,,每個BindingSource管理著一個CurrencyManager。如果多個控件綁定到同一個BindingSource,,那么只有一個CurrencyManager,,因此也就只有一個CurrentItem,這樣就造成這些綁定到同一BindingSource的控件同步刷新,。要解決這個問題,,我們需要多個CurrencyManager,也就是說我們可以創(chuàng)建多個BindingSource,,且綁定到同一個數(shù)據(jù)源,。
9.5 處理Null類型
這里有兩個概念要弄清楚,.Net內(nèi)置的Null類型與代表數(shù)據(jù)庫中的Null類型,,以及它們的區(qū)別,。
1).Net內(nèi)置的Null類型
- Nullable,引用類型
- Nuallable<T>,值類型
2).Net用來代表數(shù)據(jù)庫中的Null類型
- DBNull,,它有一個屬性Value,,可以用來判斷數(shù)據(jù)是否為DBNull
if (northwindDataSet.Employees[0].Country == DBNull.Value) { // Handle null case here }
對強(qiáng)類型數(shù)據(jù)集
if (northwindDataSet.Employees[0].IsCountryNull()) { // Handle null case here }
1)AddNew()函數(shù):用來添加一條數(shù)據(jù),返回類型由綁定的DataSource決定,。
1)綁定到DataSet/DataTable時,,返回DataRowView對象,。
注意:
a)返回的不是DataSet或DataTable或DataRow,。
b)如果希望獲取添加的數(shù)據(jù),需要進(jìn)行類型轉(zhuǎn)換
//bs為你創(chuàng)建的BindingSource
DataRow row=(DataRow)((DataRowView) bs.AddNew()).Row;
c)使用TypedDataSet時,,轉(zhuǎn)換方法與上面類似,,只是用TypedDataRow而已
//MyDataRow為你定義的TypedDataRow
MyDataRow row=(MyDataRow)((DataRowView) bs.AddNew()).Row;
|