ToB企服应用市场:ToB评测及商务社交产业平台

标题: Winform控件绑定数据 [打印本页]

作者: 石小疯    时间: 2022-11-23 00:00
标题: Winform控件绑定数据
目录

简介

在C#中提起控件绑定数据,大部分人首先想到的是WPF,其实Winform也支持控件和数据的绑定。
Winform中的数据绑定按控件类型可以分为以下几种:
绑定基类

绑定数据类必须实现INotifyPropertyChanged接口,否则数据类属性的变更无法实时刷新到界面,但可以从界面刷新到类。
为了方便,我们设计一个绑定基类:
  1. /// <summary>
  2. /// 数据绑定基类
  3. /// </summary>
  4. public abstract class BindableBase : INotifyPropertyChanged
  5. {
  6.     public event PropertyChangedEventHandler PropertyChanged;
  7.     protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
  8.     {
  9.         if (!EqualityComparer<T>.Default.Equals(field, newValue))
  10.         {
  11.             field = newValue;
  12.             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  13.             return true;
  14.         }
  15.         return false;
  16.     }
  17. }
复制代码
需要绑定的数据类继承绑定基类即可:
  1. /// <summary>
  2. /// 数据类
  3. /// </summary>
  4. public class Data : BindableBase
  5. {
  6.     private int id = 0;
  7.     private string name = string.Empty;
  8.     public int ID      { get => id;   set => SetProperty(ref id, value); }
  9.     public string Name { get => name; set => SetProperty(ref name, value); }
  10. }
复制代码
功能扩展

主要为绑定基类扩展了以下两个功能:
这两个功能不属于绑定基类的必要功能,但可以为绑定提供方便,所以单独放在扩展方法类里面。
代码如下:
  1. /// <summary>
  2. /// 数据绑定基类的扩展方法
  3. /// </summary>
  4. public static class BindableBaseExtension
  5. {
  6.    
  7.     /// <summary>
  8.     /// 获取属性的描述,返回元组格式为 Item1:描述信息 Item2:属性名称
  9.     /// </summary>
  10.     /// <param name="type"></param>
  11.     /// <returns></returns>
  12.     public static Tuple<string, string>[] GetDescription(this BindableBase bindData)
  13.     {
  14.         var proAry = bindData.GetType().GetProperties();
  15.         var desAry = new Tuple<string, string>[proAry.Length];
  16.         string desStr;
  17.         for (int i = 0; i < proAry.Length; i++)
  18.         {
  19.             var attrs = (DescriptionAttribute[])proAry[i].GetCustomAttributes(typeof(DescriptionAttribute), false);
  20.             desStr = proAry[i].Name;
  21.             foreach (DescriptionAttribute attr in attrs)
  22.             {
  23.                 desStr = attr.Description;
  24.             }
  25.             desAry[i] = Tuple.Create(desStr, proAry[i].Name);
  26.         }
  27.         return desAry;
  28.     }
  29.     /// <summary>
  30.     /// 加载同类型指定对象的属性值,如果当前属性值或目标属性值为null则不执行赋值操作
  31.     /// </summary>
  32.     /// <param name="data"></param>
  33.     public static void Load(this BindableBase source, BindableBase dest)
  34.     {
  35.         if (source == null || dest == null)
  36.         {
  37.             //不执行操作
  38.             return;
  39.         }
  40.         Type type = source.GetType();
  41.         if (type != dest.GetType())
  42.         {
  43.             throw new ArgumentNullException("参数类型不一致");
  44.         }
  45.         var proAry = type.GetProperties();
  46.         for (int i = 0; i < proAry.Length; i++)
  47.         {
  48.             var proType = proAry[i].PropertyType;
  49.            
  50.             if (proType.IsSubclassOf(typeof(BindableBase)))
  51.             {
  52.                 //检测到内部嵌套的绑定基类,建议不处理直接跳过,这种情况应该单独处理内嵌对象的数据加载
  53.                 //var childData = (BindableBase)(proAry[i].GetValue(source));
  54.                 //childData.Load((BindableBase)(proAry[i].GetValue(dest)));
  55.             }
  56.             else
  57.             {
  58.                 proAry[i].SetValue(source, proAry[i].GetValue(dest));
  59.             }
  60.         }
  61.     }
  62. }
复制代码
简单控件绑定

简单属性绑定是指某对象属性值和某控件属性值之间的简单绑定,需要了解以下内容:
使用方法如下:
  1. Data data = new Data() { ID=1,Name="test"};
  2. //常规绑定方法        
  3. textBox1.DataBindings.Add("Text", data, "ID");
  4. //使用这种方式避免硬编码
  5. textBox2.DataBindings.Add("Text", data, nameof(data.Name));
复制代码
注:这种绑定会自动处理字符串到数据的类型转换,转换失败会自动恢复原值。
列表控件绑定

列表控件绑定主要用于 ListBoxComboBox 控件,它们都属于 ListControl 类的派生类。ListControl 类ListBox 类和 ComboBox 类提供一个共同的成员实现方法。
注:CheckedListBox 类派生于 ListBox 类,不再单独说明。
使用列表控件绑定前,需要了解以下内容:
注:最终的选中值只能通过ListControl.SelectedValue 属性获取,目前还没找到可以绑定到数据的方法。
绑定BindingList集合

BindingList是一个可用来创建双向数据绑定机制的泛型集合,使用方法如下:
  1. BindingList<Data> list = new BindingList<Data>();
  2. list.Add(new Data() { ID = 1, Name = "name1" });
  3. list.Add(new Data() { ID = 2, Name = "name2" });
  4. comboBox1.DataSource = list;
  5. comboBox1.ValueMember = "ID";
  6. comboBox1.DisplayMember = "Name";
复制代码
注:如果使用List泛型集合则不支持双向绑定。同理,如果Data没有继承绑定基类,则属性值的变更也不会实时更新到界面。
绑定DataTable表格

DataTable支持双向绑定,使用方法如下:
  1. DataTable dt = new DataTable();
  2. DataColumn[] dcAry = new DataColumn[]
  3. {
  4.     new DataColumn("ID"),
  5.     new DataColumn("Name")
  6. };
  7. dt.Columns.AddRange(dcAry);
  8. dt.Rows.Add(1, "name1Dt");
  9. dt.Rows.Add(2, "name2Dt");
  10. comboBox1.DataSource = dt;
  11. comboBox1.ValueMember = "ID";
  12. comboBox1.DisplayMember = "Name";
复制代码
绑定BindingSource源

BindingSource 类封装窗体的数据源,旨在简化将控件绑定到基础数据源的过程,详细内容可查看 BindingSource 组件概述
有时候数据类型可能没有实现INotifyPropertyChanged接口,并且这个数据类型我们还修改不了,这种情况就只能使用BindingSource来将控件绑定到数据了。
假设Data类没有继承BindableBase,绑定方法如下:
  1. List<Data> list = new List<Data>();            
  2. list.Add(new Data() { ID = 1, Name = "name1" });
  3. list.Add(new Data() { ID = 2, Name = "name2" });
  4. BindingSource bs = new BindingSource();
  5. bs.DataSource = list;
  6. comboBox1.DataSource = bs;
  7. comboBox1.ValueMember = "ID";
  8. comboBox1.DisplayMember = "Name";
复制代码
关键是下面的步骤,改变集合内容时手动触发变更:
  1. //单项数据变更
  2. list[0].Name = "test";
  3. bs.ResetItem(0);
  4. //添加数据项
  5. list.Add(new Data() { ID = 3, Name = "name3" });
  6. bs.ResetBindings(false);
  7. //在BindingSource上添加或使用BindingList列表,则可以不用手动触发变更通知
  8. bs.Add(new Data() { ID = 4, Name = "name4" });
复制代码
表格控件绑定

绑定DataTable

方法如下:
  1. DataColumn c1 = new DataColumn("ID", typeof(string));
  2. DataColumn c2 = new DataColumn("名称", typeof(string));
  3. dt.Columns.Add(c1);
  4. dt.Columns.Add(c2);
  5. dt.Rows.Add(11, 22);
  6. //禁止添加行,防止显示空白行
  7. dataGridView1.AllowUserToAddRows = false;
  8. //选择是否自动创建列
  9. dataGridView1.AutoGenerateColumns = true;
  10. dataGridView1.DataSource = dt.DefaultView;
复制代码
绑定BindingList

方法如下:
  1. //填充数据
  2. BindingList<Data> dataList = new BindingList<Data>();
  3. for (int i = 0; i < 5; i++)
  4. {
  5.      dataList.Add(new Data() { ID = i, Name = "Name" + i.ToString() });
  6. }
  7. //禁止添加行,防止显示空白行
  8. dataGridView1.AllowUserToAddRows = false;
  9. //选择是否自动创建列
  10. dataGridView1.AutoGenerateColumns = false;
  11. //手动创建列
  12. var desAry = dataList[0].GetDescription();
  13. int idx = 0;
  14. foreach (var des in desAry)
  15. {
  16.      idx = dataGridView1.Columns.Add($"column{idx}", des.Item1);     // 手动添加某列
  17.      dataGridView1.Columns[idx].DataPropertyName = des.Item2;        // 设置为某列的字段
  18. }
  19. //绑定集合
  20. dataGridView1.DataSource = dataList;
  21. //集合变更事件
  22. dataList.ListChanged += DataList_ListChanged;
复制代码
注:上面的GetDescription()是绑定基类的扩展方法。
BindingList提供集合的变更通知,Data通过继承绑定基类提供属性值的变更通知。
UI线程全局类

上面所有绑定的数据源都不支持非UI线程的写入,会引起不可预知的问题,运气好的话也不会报异常出来。
为了方便多线程情况下更新数据源,设计一个UIThread类封装UI线程SynchronizationContextPostSend的操作,用来处理所有的UI更新操作,关于SynchronizationContext可以参考SynchronizationContext 综述
代码如下:
  1. /// <summary>
  2. /// UI线程全局类
  3. /// </summary>
  4. public static class UIThread
  5. {
  6.     private static SynchronizationContext context;
  7.     /// <summary>
  8.     /// 同步更新UI控件的属性及绑定数据源
  9.     /// </summary>
  10.     /// <param name="act"></param>
  11.     /// <param name="state"></param>
  12.     public static void Send(Action<object> act, object state)
  13.     {
  14.         context.Send(obj=> { act(obj); }, state);
  15.     }
  16.     /// <summary>
  17.     /// 同步更新UI控件的属性及绑定数据源
  18.     /// </summary>
  19.     /// <param name="act"></param>
  20.     public static void Send(Action act)
  21.     {
  22.         context.Send(obj => { act(); }, null);
  23.     }
  24.     /// <summary>
  25.     /// 异步更新UI控件的属性及绑定数据源
  26.     /// </summary>
  27.     /// <param name="act"></param>
  28.     /// <param name="state"></param>
  29.     public static void Post(Action<object> act, object state)
  30.     {
  31.         context.Post(obj => { act(obj); }, state);
  32.     }
  33.     /// <summary>
  34.     /// 异步更新UI控件的属性及绑定数据源
  35.     /// </summary>
  36.     /// <param name="act"></param>
  37.     public static void Post(Action act)
  38.     {
  39.         context.Post(obj => { act(); }, null);
  40.     }
  41.     /// <summary>
  42.     /// 在UI线程中初始化,只取第一次初始化时的同步上下文
  43.     /// </summary>
  44.     public static void Init()
  45.     {
  46.         if (context == null)
  47.         {
  48.             context = SynchronizationContext.Current;
  49.         }
  50.     }
  51. }
复制代码
直接在主界面的构造函数里面初始化即可:
  1. UIThread.Init();
复制代码
使用方法如下:
  1. Task.Run(() =>
  2. {
  3.     //同步更新UI
  4.     UIThread.Send(() => { dataList.RemoveAt(0); });
  5. });
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4