Winform数据绑定原理(一)——使用BIndingSource

一、控件的赋值

我们的目标是实现这样的效果:Label 控件会随着 TextBox 控件内容的修改而更新显示

image

1.传统方式实现

// 传统事件驱动代码
private void textBox1_TextChanged(object sender, EventArgs e)
{
    person.Name = textBox1.Text;
    label1.Text = $"Hello, {person.Name}";
}

在传统的WinForms开发中,开发者需要手动同步界面控件与后台数据源的状态,这里用户在TextBox中输入内容时,需要编写TextChanged事件处理代码,将新值手动更新到数据对象;反之,若数据对象的属性被代码修改,也需要手动更新对应的控件。这种方式存在以下问题:

  • 代码冗余:每个控件都需要编写事件监听和数据同步逻辑,当界面变得复杂时,事件处理代码会大量增加,UI 与业务逻辑耦合严重
  • 维护困难:当界面或数据模型变动时,需修改多处代码。
  • 易出错:手动同步可能遗漏某些场景

2.绑定模式

public Form1()
{
    InitializeComponent();
    var person = new Person();  // 创建 ViewModel 实例
    
    // 绑定 TextBox.Text 到 ViewModel.Name(双向绑定)
    textBox1.DataBindings.Add(
        "Text",                     // 控件的属性
        person,                   // 数据源(ViewModel)
        "Name",                     // 数据源的属性
        false,                      // 是否启用格式化
        DataSourceUpdateMode.OnPropertyChanged // 更新模式
    );
    
    // 绑定 Label.Text 到 ViewModel.Title(单向绑定)
    label1.DataBindings.Add(
        "Text", 
        person, 
        "Title", 
        false, 
        DataSourceUpdateMode.Never  // 数据源更新时不回写
    );
}

public class Person : INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title)));
        }
    }

    public string Title => $"Hello {Name}";
    public event PropertyChangedEventHandler PropertyChanged;
}

数据绑定的核心思想是通过数据绑定将视图与逻辑解耦,实现双向自动同步:

  • 数据源变更时,自动更新界面
  • 界面操作(如用户输入)自动更新数据源。

3.绑定说明

我们来看这行代码:textBox1.DataBindings.Add("Text", person, "Name", false, DataSourceUpdateMode.OnPropertyChanged); 前三个参数表示将 textBox1 控件的 Text 属性绑定到 personVM 对象的 Name 属性。这样,当两边对象的属性发生更改时,都会通知另一方。当我们通过控件(UI 界面)修改 textBox1 的值时,就会触发 person.Name 属性的 set 方法,而 set 方法中又调用了 TitlePropertyChanged 事件。由于 label1 绑定了 Title,所以 Label 也会随之更新。

代码解释:Control属性DataBindings是 从Control父类继承的属性 是一个 ControlBindingsCollection,其中包含该控件的 Binding 对象。Add方法内部则是new了一个Binding

二、BindingSource 数据源

1.简单使用

在上述例子中,保持其他代码不变,将 Form 构造函数中的代码修改如下:

InitializeComponent();
var person = new PersonViewModel();
BindingSource source = new BindingSource();
source.DataSource = person;
textBox1.DataBindings.Add("Text", source, "Name", false, DataSourceUpdateMode.OnPropertyChanged);
label1.DataBindings.Add("Text", source, "Title", false, DataSourceUpdateMode.Never);
person.Name = "test";

再操作UI界面,会得到相同的效果。那么,BindingSource作用是什么呢? 为什么要用它呢?

2.复杂例子

我们先来看一个稍微复杂的例子

image

在这个例子中,我们可能会觉得不知道如何使用Binding了,因为这里不再是一个属性对应一个值,而是一个集合。不信的话,你可以尝试使用以下代码:dataGridView1.DataBindings.Add("DataSource", listPerson, "Name", false, DataSourceUpdateMode.OnPropertyChanged) 你会发现没有任何响应,甚至会报错。

下面是正确的简单实现:

 List<Person> listPerson;
 public BindingSourceComp01()
 {
     InitializeComponent();
     listPerson = new List<Person>()
     {
         new Person() { ID=1,Name="关羽"},
         new Person() { ID=2,Name="张飞"},
         new Person() { ID=3,Name="马超"}
     };
     dataGridView1.DataSource = listPerson;
 }

 private void button1_Click(object sender, EventArgs e)
 {
     var name = textBox1.Text;
     listPerson.Add(new Person() { ID = listPerson.Count + 1, Name = name });
     // 没有绑定,需要手动刷新
     dataGridView1.DataSource = null;
     dataGridView1.DataSource = listPerson;
 }

这时候,BindingSource 就可以发挥作用了,只需要维护创建的数据源对象即可,代码会变得简洁:

 BindingSource bindingSource;
 public BindingSourceComp01()
 {
     InitializeComponent();
     List<Person> listPerson = new List<Person>()
     {
         new Person() { ID=1,Name="关羽"},
         new Person() { ID=2,Name="张飞"},
         new Person() { ID=3,Name="马超"}
     };
        // 将数据集合设置到 BindingSource
        bindingSource = new BindingSource() { DataSource = listPerson };
        // 将 BindingSource 作为 DataGridView 的数据源
        dataGridView1.DataSource = bindingSource;
 }

 private void button1_Click(object sender, EventArgs e)
 {
     var name = textBox1.Text;
     // 注意这里要用BindingSource 不能再用普通集合操作了
     bindingSource.Add(new Person()
     {
         ID = bindingSource.Count + 1, // 这里id不是很严谨,因为可能有删除操作,所以这里只是简单的加1
         Name = name
     });
 }

3.解释 BindingSource

BindingSource组件在Windows Forms中主要用于简化数据绑定并增强控件与数据源之间的交互,其核心功能及特点如下:

  1. 核心作用
    • 提供间接绑定层 ,将控件与数据源解耦。通过将控件绑定到BindingSource,再由BindingSource连接实际数据源(如数据库、对象集合等),实现数据变更的自动同步
  2. 同步与导航
    • 多个控件绑定同一BindingSource时,自动同步当前项及数据变更,避免手动更新
    • 内置CurrencyManager管理数据导航(如记录切换),简化分页或数据遍历操作

三、综合例子

带一个新增和删除的效果(暂时不考虑额外窗体编辑)

image

代码如下

private void InitDataGridView()
{
	bindingSource = new BindingSource() { DataSource = Student.GetList() };
	dgvStudent.DataSource = bindingSource;
}

private void btnAdd_Click(object sender, EventArgs e)
{
	bindingSource.Add(new Student
	{
		Id = 10,
		Name = "New Student",
		Age = 20,
		Classes = 4,
		Address = "New Address"
	});
}

private void btnDel_Click(object sender, EventArgs e)
{
	// 通过bindingSource.Current获取当前选中的对象
	if (bindingSource.Current is Student item)
	{
		var result = MessageBox.Show("是否删除?", "删除", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
		if (result.Equals(DialogResult.OK))
			bindingSource.Remove(item);
	}
}
posted @ 2020-01-12 23:05  flyreaver  阅读(281)  评论(0)    收藏  举报