C# DataBindings数据绑定功能

有的项目界面多个地方使用到模型的同一个属性,不使用数据绑定功能时,每当添加或修改一些功能时,都要手动赋值更新界面,总是担心哪里漏掉没有更新。

使用DataBinding可以实现自动绑定,当模型数据改变时,界面上绑定了模型属性的控件将自动更新,不需要手动一一赋值。

代码如下(注意代码中用的是Form2不是Form1):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.CompilerServices;

namespace WindowsFormsApplication1
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}

Test test;
private void Form2_Load(object sender, EventArgs e)
{
test=new Test();
label1.DataBindings.Add("Text", test, "Str");
label2.DataBindings.Add("Text", test, "Str");
label3.DataBindings.Add("Text", test, "Str");

}

private void textBox1_TextChanged(object sender, EventArgs e)
{
test.Str = textBox1.Text;
}

}

//要使用绑定数据功能,需要模型支持INotifyPropertyChanged接口
public class Test : INotifyPropertyChanged
{
string _str;
public string Str
{
get
{
return _str;
}
set
{
_str = value;
FireStrChanged();
}
}

//必须实现INotifyPropertyChanged接口的此事件
public event PropertyChangedEventHandler PropertyChanged;

//要在.net4.0使用CallerMemberName特性,需要加上后面一段代码
public void FireStrChanged([CallerMemberName] string propertyName="")
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

}
}

namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
public class CallerMemberNameAttribute : Attribute
{

}

[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
public class CallerFilePathAttribute : Attribute
{

}

[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
public class CallerLineNumberAttribute : Attribute
{

}
}

________________________

2023.1.23更新,更简洁的写法,实现效果一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.CompilerServices;

namespace WindowsFormsApplication1
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}

Test test;
private void Form2_Load(object sender, EventArgs e)
{
test=new Test();
label1.DataBindings.Add("Text", test, "Str");
label2.DataBindings.Add("Text", test, "Str");
label3.DataBindings.Add("Text", test, "Str");

}

private void textBox1_TextChanged(object sender, EventArgs e)
{
test.Str = textBox1.Text;
}

}

//要使用绑定数据功能,需要模型支持INotifyPropertyChanged接口
public class Test : INotifyPropertyChanged
{
string _str;
public string Str
{
get
{
return _str;
}
set
{
_str = value;
//FireStrChanged();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Str)));
}
}

//必须实现INotifyPropertyChanged接口的此事件
public event PropertyChangedEventHandler PropertyChanged;

//要在.net4.0使用CallerMemberName特性,需要加上后面一段代码
//public void FireStrChanged([CallerMemberName] string propertyName="")
//{
// PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
//}

}
}

________________________

实现效果:

简介

在C#中提起控件绑定数据,大部分人首先想到的是WPF,其实Winform也支持控件和数据的绑定。

Winform中的数据绑定按控件类型可以分为以下几种:

  • 简单控件绑定
  • 列表控件绑定
  • 表格控件绑定

简单控件绑定

简单属性绑定是指某对象属性值和某控件属性值之间的简单绑定,需要了解以下内容:

  • Control.DataBindings 属性:代表控件的数据绑定的集合。
  • Binding 类:代表某对象属性值和某控件属性值之间的简单绑定。

使用方法如下:

1
2
3
4
5
 Data data = new Data() { ID=1,Name="test"};
//常规绑定方法
textBox1.DataBindings.Add("Text", data, "ID");
//使用这种方式避免硬编码
textBox2.DataBindings.Add("Text", data, nameof(data.Name));

注:这种绑定会自动处理字符串到数据的类型转换,转换失败会自动恢复原值。

列表控件绑定
列表控件绑定主要用于 ListBox 与 ComboBox 控件,它们都属于 ListControl 类的派生类。ListControl 类为 ListBox 类和 ComboBox 类提供一个共同的成员实现方法。 注:CheckedListBox 类派生于 ListBox 类,不再单独说明。 使用列表控件绑定前,需要了解以下内容:

ListControl.DataSource 属性:获取或设置此 ListControl 的数据源,值为实现 IList 或 IListSource 接口的对象,如 DataSet 或 Array。

ListControl.DisplayMember 属性:获取或设置要为此 ListControl 显示的属性,指定 DataSource 属性指定的集合中包含的对象属性的名称,默认值为空字符串(“”)。

ListControl.ValueMember 属性:获取或设置属性的路径,它将用作 ListControl 中的项的实际值,表示 DataSource 属性值的单个属性名称,或解析为最终数据绑定对象的属性名、单个属性名或句点分隔的属性名层次结构, 默认值为空字符串(“”)。

注:最终的选中值只能通过ListControl.SelectedValue 属性获取,目前还没找到可以绑定到数据的方法。

绑定BindingList集合

BindingList是一个可用来创建双向数据绑定机制的泛型集合,使用方法如下:

1
2
3
4
5
6
7
BindingList<Data> list = new BindingList<Data>();
list.Add(new Data() { ID = 1, Name = "name1" });
list.Add(new Data() { ID = 2, Name = "name2" });

comboBox1.DataSource = list;
comboBox1.ValueMember = "ID";
comboBox1.DisplayMember = "Name";

注:如果使用List泛型集合则不支持双向绑定。同理,如果Data没有继承绑定基类,则属性值的变更也不会实时更新到界面。

绑定DataTable表格

DataTable支持双向绑定,使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
DataTable dt = new DataTable();
DataColumn[] dcAry = new DataColumn[]
{
new DataColumn("ID"),
new DataColumn("Name")
};
dt.Columns.AddRange(dcAry);
dt.Rows.Add(1, "name1Dt");
dt.Rows.Add(2, "name2Dt");

comboBox1.DataSource = dt;
comboBox1.ValueMember = "ID";
comboBox1.DisplayMember = "Name";

UI线程全局类

上面所有绑定的数据源都不支持非UI线程的写入,会引起不可预知的问题,运气好的话也不会报异常出来。

为了方便多线程情况下更新数据源,设计一个UIThread类封装UI线程SynchronizationContext的Post、Send的操作,用来处理所有的UI更新操作,关于SynchronizationContext可以参考SynchronizationContext 综述。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/// <summary>
/// UI线程全局类
/// </summary>
public static class UIThread
{
private static SynchronizationContext context;


/// <summary>
/// 同步更新UI控件的属性及绑定数据源
/// </summary>
/// <param name="act"></param>
/// <param name="state"></param>
public static void Send(Action<object> act, object state)
{
context.Send(obj=> { act(obj); }, state);
}

/// <summary>
/// 同步更新UI控件的属性及绑定数据源
/// </summary>
/// <param name="act"></param>
public static void Send(Action act)
{
context.Send(obj => { act(); }, null);
}

/// <summary>
/// 异步更新UI控件的属性及绑定数据源
/// </summary>
/// <param name="act"></param>
/// <param name="state"></param>
public static void Post(Action<object> act, object state)
{
context.Post(obj => { act(obj); }, state);
}

/// <summary>
/// 异步更新UI控件的属性及绑定数据源
/// </summary>
/// <param name="act"></param>
public static void Post(Action act)
{
context.Post(obj => { act(); }, null);
}


/// <summary>
/// 在UI线程中初始化,只取第一次初始化时的同步上下文
/// </summary>
public static void Init()
{
if (context == null)
{
context = SynchronizationContext.Current;
}
}
}

直接在主界面的构造函数里面初始化即可:

1
UIThread.Init();

使用方法如下:

1
2
3
4
5
Task.Run(() => 
{
//同步更新UI
UIThread.Send(() => { dataList.RemoveAt(0); });
});