Windows Forms 的數據綁定架構在 .NET Framework 2.0 中迎來重大升級,BindingSource、BindingNavigator 與 DataGridView 等控件讓開發者能以極少的代碼快速構建功能完整的數據驅動介面。本文將透過一個完整的 Northwind 訂單管理範例,示範如何放棄 DataSet,改用自定義實體類別搭配泛型集合,並結合 ADO.NET 直接操作資料庫,打造出既具備豐富 UI 交互又保持架構清晰的企業級應用程序。
範例應用程序提供直觀的訂單瀏覽、新增、刪除、修改、查詢與導航功能。左側 DataGridView 載入所有訂單列表,右側則以 TextBox、ComboBox 等控件顯示當前選中訂單的詳細信息。所有控件均綁定至同一 BindingSource,確保數據同步無縫銜接。

圖 1:DataGridView 中瀏覽 Northwind 訂單
頂部的 BindingNavigator 工具欄集成了標準導航按鈕(首筆、上一筆、下一筆、末筆)、新增/刪除/保存按鈕,以及一個搜索框(望遠鏡圖標),支援按訂單編號模糊查詢。右側詳細區域針對外鍵欄位(如 EmployeeID、CustomerID)採用 ComboBox 呈現,顯示員工姓名與客戶公司名稱而非無意義的 ID 值,大幅提升使用者體驗。
雖然 DataSet 功能強大,但自定義實體類別能提供更強的類型安全、更清晰的領域模型,並便於單元測試。本範例定義了 Order、Customer、Employee 三大實體,均實作 INotifyPropertyChanged 介面,確保屬性變更能自動通知 UI 更新。
以 Customer 類為例:
public class Customer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _customerID;
public string CustomerID
{
get { return _customerID; }
set { _customerID = value; }
}
private string _companyName;
public string CompanyName
{
get { return _companyName; }
set
{
if (!value.Equals(_companyName))
{
_companyName = value;
OnPropertyChanged("CompanyName");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
關鍵在於 Set 存取器中先比較新舊值,僅在真正變更時觸發事件,避免不必要的 UI 重繪。BindingSource 內建監聽此事件,實現雙向綁定的自動刷新。
資料存取層採用靜態方法封裝 CRUD 操作,回傳 List<T> 泛型集合。相較於 ArrayList,泛型集合提供編譯期類型檢查,省去強制轉換,程式碼更簡潔安全:
// 取得所有訂單
public static List<Order> GetEntityList()
{
var list = new List<Order>();
using (var cn = new SqlConnection(Common.ConnectionString))
using (var cmd = new SqlCommand("pr_Order_GetList", cn) { CommandType = CommandType.StoredProcedure })
{
cn.Open();
using (var rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
while (rdr.Read())
list.Add(FillOrder(rdr));
}
}
return list;
}
// 由 DataReader 填充實體
private static Order FillOrder(SqlDataReader rdr)
{
return new Order
{
OrderID = Convert.ToInt32(rdr["OrderID"]),
CustomerID = rdr["CustomerID"].ToString(),
EmployeeID = Convert.ToInt32(rdr["EmployeeID"]),
OrderDate = Convert.ToDateTime(rdr["OrderDate"]),
ShipVia = rdr["ShipVia"].ToString(),
ShipName = rdr["ShipName"].ToString(),
ShipAddress = rdr["ShipAddress"].ToString(),
ShipCity = rdr["ShipCity"].ToString(),
ShipCountry = rdr["ShipCountry"].ToString()
};
}
使用 SqlDataReader 而非 DataTable 可減少記憶體開銷,配合 CommandBehavior.CloseConnection 確保連線隨 Reader 關閉而自動釋放。寫入操作同樣封裝於靜態方法,透過輸出參數取得新增記錄的自動編號:
private static Order AddEntity(Order order)
{
using (var cn = new SqlConnection(Common.ConnectionString))
using (var cmd = new SqlCommand("pr_Order_Add", cn) { CommandType = CommandType.StoredProcedure })
{
cmd.Parameters.AddWithValue("@customerID", order.CustomerID);
cmd.Parameters.AddWithValue("@employeeID", order.EmployeeID);
cmd.Parameters.AddWithValue("@orderDate", order.OrderDate);
cmd.Parameters.AddWithValue("@shipVia", order.ShipVia);
cmd.Parameters.AddWithValue("@shipName", GetValue(order.ShipName));
cmd.Parameters.AddWithValue("@shipAddress", GetValue(order.ShipAddress));
cmd.Parameters.AddWithValue("@shipCity", GetValue(order.ShipCity));
cmd.Parameters.AddWithValue("@shipCountry", GetValue(order.ShipCountry));
var pOrderID = new SqlParameter("@orderID", SqlDbType.Int) { Direction = ParameterDirection.Output };
cmd.Parameters.Add(pOrderID);
cn.Open();
cmd.ExecuteNonQuery();
int newID = Convert.ToInt32(pOrderID.Value);
return GetEntity(newID); // 回傳完整實體(含資料庫端計算欄位)
}
}

圖 2:資料來源視窗中的自定義實體
在「資料來源」視窗中加入專案參考的實體類別後,即可為每個屬性指定生成的控件類型(如 OrderDate 對應 DateTimePicker,OrderID 設為 Label 唯讀)。針對外鍵屬性(CustomerID、EmployeeID),將控件類型改為 ComboBox,稍後再分別綁定客戶與員工清單。
將 Order 實體以「詳細資料」模式拖曳至表單,VS 自動生成:
再將 Order 實體以 DataGridView 模式拖曳至左側區域,VS 偵測到現有 BindingSource 而不再重複建立,確保左右兩側控件共享同一資料游標。
CustomerID ComboBox 需同時滿足兩個條件:
解決方案:
DataSource = customerBindingSourceDisplayMember = "CompanyName"ValueMember = "CustomerID"DataBindings.Add("SelectedValue", orderBindingSource, "CustomerID")
EmployeeID ComboBox 同理處理。此技巧讓「顯示清單」與「寫回值」分離綁定,互不干擾。
表單建構子中呼叫 SetupBindings(),將各實體的 List<T> 包裝為 BindingList<T> 並指派給對應 BindingSource:
private void SetupBindings()
{
orderBindingSource.DataSource = new BindingList<Order>(Order.GetEntityList());
customerBindingSource.DataSource = new BindingList<Customer>(Customer.GetEntityList());
employeeBindingSource.DataSource = new BindingList<Employee>(Employee.GetEntityList());
}
BindingList 實作 IBindingList,支援排序、搜尋與變更通知,是 Windows Forms 綁定的最佳介面卡。
BindingNavigator 的「刪除」按鈕預設行為直接移除 BindingSource 項目,但實際專案通常需確認對話框、呼叫資料存取層、錯誤處理。因此將 DeleteItem 設為「無」,自行處理 Click 事件:
private void Delete()
{
var order = orderBindingSource.Current as Order;
if (order == null) return;
if (MessageBox.Show($"確定刪除訂單 {order.OrderID}?", "確認", MessageBoxButtons.OKCancel) != DialogResult.OK)
return;
Order.DeleteEntity(order); // 資料庫端刪除
orderBindingSource.RemoveCurrent(); // UI 端移除
MessageBox.Show($"訂單 {order.OrderID} 已刪除。");
}
「新增」按鈕會在 BindingSource 末尾插入新項目並清空詳細區域;「保存」按鈕則統一處理新增與更新:
private void Save()
{
var order = orderBindingSource.Current as Order;
if (order == null) return;
// 驗證邏輯...
Order.SaveEntity(order); // 內部依 OrderID 判斷 Insert/Update
// 重新載入列表以同步資料庫端變更(如自動編號、觸發器計算欄位)
orderBindingSource.DataSource = new BindingList<Order>(Order.GetEntityList());
}
工具欄搜尋框允許使用者輸入訂單編號前碼(如 "102" 查詢 10248、10249...)。利用 List<T>.Find(Predicate<T>) 配合輔助類別實現:
private class OrderFilter
{
private readonly int _prefix;
public OrderFilter(int prefix) { _prefix = prefix; }
public bool BeginsWithOrderID(Order o)
=> o.OrderID.ToString().StartsWith(_prefix.ToString());
}
private void toolBtnFindOrderNumber_Click(object sender, EventArgs e)
{
var list = new List<Order>((BindingList<Order>)orderBindingSource.DataSource);
var filter = new OrderFilter(Convert.ToInt32(toolTxtFindOrderNumber.Text));
var found = list.Find(filter.BeginsWithOrderID);
if (found == null)
MessageBox.Show("找不到符合的訂單");
else
orderBindingSource.Position = orderBindingSource.IndexOf(found);
}
Predicate<T> 是泛型委託,List.Find 會依序傳入元素直到委託回傳 true,效能優於手寫迴圈且程式碼更具宣告式風格。
本文完整展示了如何在 Windows Forms 中:
INotifyPropertyChanged 實現雙向綁定List<T> 與 BindingList<T> 獲得類型安全與綁定支援SqlDataReader 輕量級讀取,搭配存儲過程封裝資料存取邏輯Predicate<T> 實現高效的客戶端搜尋這套架構既保持了關注點分離,又充分發揮 .NET Framework 2.0 綁定基礎設施的威力,是中型企業應用程序的優秀範本。