写在前面
承接篇4。本篇讲 WinForms 的数据绑定:BindingSource、BindingList、INotifyPropertyChanged、DataGridView。
WinForms 的数据绑定比 WPF 弱很多(没有 XAML 那套强大绑定系统),但做"表单 ↔ 对象"“列表 ↔ DataGridView"这种典型场景完全够用。理解它的模型,你能在 WinForms 里把数据驱动 UI 做得不错——只是不如 WPF 优雅。
两类绑定:
1
2
3
4
5
6
7
8
9
| 简单绑定(Simple Binding):
控件的某属性 ↔ 数据对象的某属性(一对一)
textBox1.DataBindings.Add("Text", obj, "Name"); // textBox.Text ↔ obj.Name
复杂绑定(Complex Binding):
控件 ↔ 数据集合(一对多)
dataGridView1.DataSource = list; // 整个表 ↔ 列表
comboBox1.DataSource = list;
comboBox1.DisplayMember = "Name";
|
数据流向(DataSourceUpdateMode):
1
2
3
| OnValidation 默认,控件失焦校验后回写源
OnPropertyChanged 源属性变就回写
Never 只单向(源→控件),不回写
|
二、INotifyPropertyChanged:让对象"可观察”
数据源要通知 UI"我变了",实现 INotifyPropertyChanged:
1
2
3
4
5
6
7
8
9
10
11
12
13
| public class User : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set { if (_name != value) { _name = value; OnPropertyChanged(nameof(Name)); } }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string n) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n));
}
|
1
2
3
4
| 没实现 INPC:
obj.Name 改了,绑定的 textBox 不刷新(UI 还是旧值)
实现了 INPC:
obj.Name 改 → 触发 PropertyChanged → 绑定系统听到 → 刷新 textBox
|
WPF 里 INPC 是主角;WinForms 里同样要 INPC 才能让"对象 → 控件"方向自动刷新。C# 现代写法用 CommunityToolkit.Mvvm 的 [ObservableProperty] 源生成器省样板(第 6 篇会提)。
三、BindingList:可观察集合
列表场景,改集合(增删元素)要通知 UI 刷新,用 BindingList<T>(WinForms 专用):
1
2
3
4
5
6
| var users = new BindingList<User> { new() { Name = "张三" }, new() { Name = "李四" } };
dataGridView1.DataSource = users;
// 增删自动反映到 DataGridView
users.Add(new User { Name = "王五" }); // 表格立即多一行
users.RemoveAt(0); // 表格立即少一行
|
1
2
3
4
5
6
| ObservableCollection<T>(WPF 用)vs BindingList<T>(WinForms 用):
ObservableCollection 集合变更通知(Add/Remove)→ UI 刷新
BindingList 集合变更通知 + 元素属性变更(INPC)→ UI 刷新 + 可编辑
WinForms 的 DataGridView 对 BindingList 支持最好(行编辑、排序)
两者都实现了 IList/IBindingList,互转不难
|
WinForms 优先用 BindingList<T>;要和 WPF 共享代码用 ObservableCollection<T> 再包装。
四、BindingSource:绑定中枢
BindingSource 是 WinForms 绑定的中间层——你把集合给它,控件绑它。它统一了列表/对象/DataTable 的绑定接口,并提供导航(Position/Current)、排序、过滤。
1
2
3
4
5
6
7
| var bs = new BindingSource { DataSource = users };
dataGridView1.DataSource = bs; // 控件绑 BindingSource(不是直接绑 list)
bs.PositionChanged += (s, e) => // 当前选中行变化
Text = $"选中: {((User)bs.Current).Name}";
bs.Add(new User { Name = "赵六" }); // 通过 bs 增删
|
1
2
3
4
5
| 为什么用 BindingSource:
✓ 统一接口(list/object/DataTable 都能 DataSource)
✓ 导航(Position/Current/MoveNext)
✓ 排序/过滤(Sort/Filter,绑 DataTable 时)
✓ 解耦:控件绑 bs,bs 绑数据;换数据源只动 bs
|
复杂表单/主从表(两个 DataGridView 联动)几乎必用 BindingSource。简单单控件绑定可以跳过它直接 DataBindings.Add。
五、DataGridView:数据绑定主力
DataGridView 是 WinForms 表格控件,绑集合/DataTable 后自动生成列,支持编辑、排序、选择:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // 简单绑定
dataGridView1.DataSource = users;
// 控制:自动生成列关掉,手定义
dataGridView1.AutoGenerateColumns = false;
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = "Name", // 绑定到 User.Name
HeaderText = "姓名",
Width = 150
});
dataGridView1.Columns.Add(new DataGridViewCheckBoxColumn
{
DataPropertyName = "IsActive",
HeaderText = "启用"
});
dataGridView1.DataSource = users;
|
常用能力:
| 能力 | 怎么用 |
|---|
| 编辑 | ReadOnly = false(默认可编辑,改完回写源) |
| 排序 | 点列头自动排序(绑 DataTable/BindingList 支持) |
| 选择 | SelectionMode(FullRowSelect 等) |
| 主从表 | 两个 DataGridView,主表的 BindingSource 作为从表 DataSource |
| 单元格样式 | RowsDefaultCellStyle / CellFormatting 事件 |
1
2
3
4
5
6
| // 按条件着色:Active=false 的行标红
private void grid_CellFormatting(object s, DataGridViewCellFormattingEventArgs e)
{
if (dataGridView1.Rows[e.RowIndex].DataBoundItem is User u && !u.IsActive)
e.CellStyle.BackColor = Color.MistyRose;
}
|
DataGridView 是 WinForms 表格场景的瑞士军刀,大数据量时考虑 VirtualMode(虚拟模式,只渲染可见行)。
六、主从表(主从绑定)
两个表联动:选主表一行,从表显示对应明细。BindingSource 让这很简单:
1
2
3
4
5
6
7
8
9
| // 主:订单列表
var orders = new BindingList<Order> { /* ... */ };
var bsOrders = new BindingSource { DataSource = orders };
// 从:通过 DataMember 指定"订单的明细集合属性"
var bsDetails = new BindingSource { DataSource = bsOrders, DataMember = "Details" };
gridOrders.DataSource = bsOrders; // 主表
gridDetails.DataSource = bsDetails; // 从表(随主表选中自动联动)
|
选 gridOrders 一行 → bsOrders.Current 变 → bsDetails 自动显示该订单的 Details。
| 维度 | WinForms | WPF |
|---|
| 绑定声明 | 代码 DataBindings.Add / 设计器 | XAML {Binding} 声明式 |
| 绑定目标 | 控件属性 ↔ 对象属性(有限) | 任意 DP ↔ 任意属性 |
| Converter | Format/Parse 事件(简陋) | IValueConverter(强大) |
| 集合控件 | DataGridView + BindingList | ItemsControl + ObservableCollection + DataTemplate |
| 验证 | Validating 事件 / ErrorProvider | ValidationRule / INotifyDataErrorInfo |
| 模板 | DataGridView 列样式(有限) | DataTemplate(任意外观) |
1
2
3
| 一句话:
WinForms 绑定 = 够用的表单/表格双向
WPF 绑定 = 任意 UI 任意数据的声明式绑定,配合模板无敌
|
八、小结
1
2
3
4
5
6
7
| WinForms 数据绑定核心:
✓ 简单绑定:DataBindings.Add("控件属性", 源, "源属性")
✓ 复杂绑定:DataSource = 集合(DataGridView/ComboBox)
✓ 数据源实现 INotifyPropertyChanged → 控件自动刷新
✓ 列表用 BindingList<T>(增删 + 元素属性变更都通知)
✓ BindingSource 是绑定中枢(导航/排序/过滤/主从)
✓ DataGridView 是表格主力(编辑/排序/主从/CellFormatting)
|
下一篇:线程与 async——UI 线程模型、Control.Invoke、async/await,以及 WinForms 经典的 .Result 死锁(呼应本站高并发篇)。