当前位置:   article > 正文

c# 模仿 vue 实现 winform 的数据模型双向绑定_c# 像vue绑定

c# 像vue绑定

前前前段时间面试遭拒,当时面试关问自己的一些东西确实不懂,其中就包括vue(其实有看过相关文章和文档,秉着 如果只是用轮子的话,需要时间和文档就够了,事实上只是使用的话,按规范来就行了)。

但是自己怎么能轻易停留在用呢,于是在花了点时间,直接搜索vue绑定原理,详细看了两篇文章 

http://m.jb51.net/article/107927.htm

http://www.cnblogs.com/likeFlyingFish/p/6744212.html 

其实就是观察者的设计模式,关键在于事件的监听。而 js 有个 defineProperty 修改属相的get set 方法,使其在 set 数据的时候判断是否有变化后进行通知,也就是执行一边对该属性订阅的方法。
再将我们平时对dom操作的方法进行封装注册到某个属性的订阅列表,即可实现数据视图绑定。其实不管怎么做,最终也还是要操作dom的,只是人家封装好了的而已。

而后面突然有个想法,我能不能模仿 vue 做个winform 的双向绑定呢?
尽管窗口开发 双向绑定已经有成熟的 wpf 了, 而 winform 也有 DataBindings 绑定数据(这个在后面自己的测试中发现没能在变化中通知,应该还要进一步的封装),但是就想把 vue的方法用c#实现一下。

按照 js 那一套,要有个 Observe 来重写对象属性的 get;set;,还要有个 Dep 来保存订阅者,还要有个 Watcher 来监听属性。

但是 c# 没法代码批量改 getter setter 这两个方法(试过反射不出这两个方法),那只需要实现 Dep 和 Watcher 。

Watcher

对象的属性通过Watcher 来监听,不同控件绑定同一个 属性的话,就是不同的Watcher 。为统一通知(观察者调用),我们声明一个接口:

  1. namespace Mvvm
  2. {
  3. /// <summary>
  4. /// 统一监听接口
  5. /// </summary>
  6. public interface IWatcher
  7. {
  8. void Update();
  9. }
  10. }

然后实现这个接口(详细看注释)

  1. namespace Mvvm
  2. {
  3. /// <summary>
  4. /// 监听者
  5. /// </summary>
  6. public class Watcher : IWatcher
  7. {
  8. /// <summary>
  9. /// 实体类型
  10. /// </summary>
  11. private Type type = null;
  12. /// <summary>
  13. /// 属性变化触发的委托
  14. /// </summary>
  15. private Action<object> Action = null;
  16. /// <summary>
  17. /// 属性名称
  18. /// </summary>
  19. private string propertyName = null;
  20. /// <summary>
  21. /// 父控件
  22. /// </summary>
  23. private Control ParentControl = null;
  24. /// <summary>
  25. /// 实体
  26. /// </summary>
  27. private object model = null;
  28. /// <summary>
  29. /// 初始化监听者
  30. /// </summary>
  31. /// <param name="ParentControl">父控件</param>
  32. /// <param name="type">实体类型</param>
  33. /// <param name="model">实体</param>
  34. /// <param name="propertyName">要监听的属性名称</param>
  35. /// <param name="action">属性变化触发的委托</param>
  36. public Watcher(Control ParentControl, Type type, object model, string propertyName, Action<object> action)
  37. {
  38. this.type = type;
  39. this.Action = action;
  40. this.propertyName = propertyName;
  41. this.ParentControl = ParentControl;
  42. this.model = model;
  43. this.AddToDep();
  44. }
  45. /// <summary>
  46. /// 添加监听者到属性的订阅者列表(Dep)
  47. /// </summary>
  48. private void AddToDep()
  49. {
  50. PropertyInfo property = this.type.GetProperty(this.propertyName);
  51. if (property == null) return;
  52. Dep.Target = this;
  53. object value = property.GetValue(this.model, null);
  54. Dep.Target = null;
  55. }
  56. /// <summary>
  57. /// 更新数据(监听触发的方法)
  58. /// </summary>
  59. public void Update()
  60. {
  61. this.ParentControl.Invoke(new Action(delegate
  62. {
  63. this.Action(this.model);
  64. }));
  65. }
  66. }
  67. }
这里的 ParentControl 指的是 From 或者是 包含有子空间的其它容器,绑定的话只会绑定其下的子控件,同时,异步更新UI时也是由该控件的委托来完成.

Dep

Dep 主要用来保存某个属性的订阅者,以便属性值变更时,通知其订阅者。因为 c#没法像js 一样可以用代码批量重写get set,所以为了简化属性get set 的写法,将属相的值也存放到 Dep 里(每个属性对应单独的 Dep),通过Dep 中的Get Set 方法给属性操作。

  1. namespace Mvvm
  2. {
  3. public class Dep
  4. {
  5. /// <summary>
  6. /// 全局变量,用户将指定属性的订阅者放入通知列表
  7. /// </summary>
  8. public static IWatcher Target = null;
  9. /// <summary>
  10. /// 保存属性的值,属性的get set将是对该值的操作
  11. /// </summary>
  12. private object oValue;
  13. /// <summary>
  14. /// 订阅者列表
  15. /// </summary>
  16. private List<IWatcher> lsWatcher = null;
  17. public Dep()
  18. {
  19. this.lsWatcher = new List<IWatcher>();
  20. }
  21. /// <summary>
  22. /// 添加订阅者
  23. /// </summary>
  24. /// <param name="watcher"></param>
  25. private void PushWatcher(IWatcher watcher)
  26. {
  27. this.lsWatcher.Add(watcher);
  28. }
  29. /// <summary>
  30. /// 通知
  31. /// </summary>
  32. public void Notify()
  33. {
  34. List<IWatcher> watchers = this.lsWatcher;
  35. watchers.ForEach(watcher =>
  36. {
  37. watcher.Update();
  38. });
  39. }
  40. /// <summary>
  41. /// 返回属性的值
  42. /// </summary>
  43. /// <typeparam name="T"></typeparam>
  44. /// <returns></returns>
  45. public T Get<T>()
  46. {
  47. // Dep.Target 不为空时,标识指向这个属性的一个订阅者,需要将它加入到订阅列表
  48. bool flag = Dep.Target != null;
  49. if (flag)
  50. {
  51. this.PushWatcher(Dep.Target);
  52. }
  53. return this.oValue==null?default(T): (T)this.oValue;
  54. }
  55. /// <summary>
  56. /// 设置属性值
  57. /// </summary>
  58. /// <param name="oValue"></param>
  59. public void Set(object oValue)
  60. {
  61. bool flag = this.oValue == null || !this.oValue.Equals(oValue);
  62. if (flag)
  63. {
  64. this.oValue = oValue;
  65. this.Notify();
  66. }
  67. }
  68. /// <summary>
  69. /// 初始化队列,分配给每个属性
  70. /// </summary>
  71. /// <param name="count"></param>
  72. /// <returns></returns>
  73. public static List<Dep> InitDeps(int count)
  74. {
  75. if (count < 1) throw new Exception("wrong number! count must biger than 0");
  76. var lsDep = new List<Dep>();
  77. for(var i=0;i<count;i++)
  78. {
  79. lsDep.Add(new Dep());
  80. }
  81. return lsDep;
  82. }
  83. }
  84. }

到目前为止,已经能手动新建 Watcher来实现 属相变化 并触发事件,但是,还要手动写触发事件,这怎么能忍。于是进行了一层封装,让绑定自动化,默认对几个变化事件做了绑定。

ViewBind

  1. namespace Mvvm
  2. {
  3. public class ViewBind
  4. {
  5. /// <summary>
  6. /// 默认绑定事件
  7. /// </summary>
  8. private string Events = "CollectionChange|SelectedValueChanged|ValueChanged|TextChanged";
  9. //private string Perpertis = "DataSource|Value|Text";
  10. /// <summary>
  11. /// 绑定视图
  12. /// </summary>
  13. /// <param name="ParentControl">父控件</param>
  14. /// <param name="model">模型(对象)</param>
  15. public ViewBind(Control ParentControl, object model)
  16. {
  17. this.BindingParentControl(ParentControl, model);
  18. }
  19. /// <summary>
  20. /// 绑定控件
  21. /// </summary>
  22. /// <param name="ParentControl">父控件</param>
  23. /// <param name="model">实体</param>
  24. private void BindingParentControl(Control ParentControl, object model)
  25. {
  26. this.BindControl(ParentControl, model, ParentControl.Controls);
  27. }
  28. /// <summary>
  29. /// 绑定控件
  30. /// </summary>
  31. /// <param name="ParentControl">父控件</param>
  32. /// <param name="model">实体</param>
  33. /// <param name="Controls">子控件列表</param>
  34. private void BindControl(Control ParentControl, object model, Control.ControlCollection Controls)
  35. {
  36. foreach (Control control in Controls)
  37. {
  38. var tag = control.Tag;
  39. if (tag == null) continue;
  40. foreach (var tagInfo in tag.ToString().Split('|'))
  41. {
  42. var tagInfoArr = tagInfo.Split('-');
  43. if (tagInfoArr[0].Equals("data") && tagInfoArr.Length == 3)
  44. {
  45. //数目绑定
  46. string propertyName = tagInfoArr[tagInfoArr.Length - 1];
  47. this.BindingProperty(ParentControl, control, model, propertyName, tagInfoArr[1]);
  48. this.BindListener(control, model, propertyName, tagInfoArr[1]);
  49. }
  50. else if (tagInfoArr[0].Equals("ev") && tagInfoArr.Length == 3)
  51. {
  52. //事件绑定
  53. BindEvent(ParentControl, control, model, tagInfoArr[1], tagInfoArr[2]);
  54. }
  55. else
  56. {
  57. if (control.Controls.Count > 0)
  58. {
  59. this.BindControl(ParentControl, model, control.Controls);
  60. }
  61. }
  62. }
  63. }
  64. }
  65. /// <summary>
  66. /// 绑定事件
  67. /// </summary>
  68. /// <param name="ParentControl">父控件</param>
  69. /// <param name="control">要绑定事件的控件</param>
  70. /// <param name="model">实体</param>
  71. /// <param name="eventName">事件名称</param>
  72. /// <param name="methodName">绑定到的方法</param>
  73. private void BindEvent(Control ParentControl, Control control, object model, string eventName, string methodName)
  74. {
  75. var Event = control.GetType().GetEvent(eventName);
  76. if (Event != null)
  77. {
  78. var methodInfo = model.GetType().GetMethod(methodName);
  79. if (methodInfo != null)
  80. {
  81. Event.AddEventHandler(control, new EventHandler((s, e) =>
  82. {
  83. ParentControl.Invoke(new Action(() =>
  84. {
  85. switch (methodInfo.GetParameters().Count())
  86. {
  87. case 0: methodInfo.Invoke(model, null); break;
  88. case 1: methodInfo.Invoke(model, new object[] { new { s = s, e = e } }); break;
  89. case 2: methodInfo.Invoke(model, new object[] { s, e }); break;
  90. default: break;
  91. }
  92. }));
  93. }));
  94. }
  95. }
  96. }
  97. /// <summary>
  98. /// 添加监听
  99. /// </summary>
  100. /// <param name="control">要监听的控件</param>
  101. /// <param name="model">实体</param>
  102. /// <param name="mPropertyName">变化的实体属性</param>
  103. /// <param name="controlPropertyName">对应变化的控件属性</param>
  104. private void BindListener(Control control, object model, string mPropertyName, string controlPropertyName)
  105. {
  106. var property = model.GetType().GetProperty(mPropertyName);
  107. var events = this.Events.Split('|');
  108. foreach (var ev in events)
  109. {
  110. var Event = control.GetType().GetEvent(ev);
  111. if (Event != null)
  112. {
  113. Event.AddEventHandler(control, new EventHandler((s, e) =>
  114. {
  115. try
  116. {
  117. var controlProperty = control.GetType().GetProperty(controlPropertyName);
  118. if (controlProperty != null)
  119. {
  120. property.SetValue(model, Convert.ChangeType(controlProperty.GetValue(control, null), property.PropertyType), null);
  121. }
  122. }
  123. catch (Exception ex)
  124. {
  125. //TPDO
  126. }
  127. }));
  128. }
  129. }
  130. }
  131. /// <summary>
  132. /// 绑定属性
  133. /// </summary>
  134. /// <param name="ParentControl">父控件</param>
  135. /// <param name="control">绑定属性的控件</param>
  136. /// <param name="model">实体</param>
  137. /// <param name="mPropertyName">绑定的实体属性名称</param>
  138. /// <param name="controlPropertyName">绑定到的控件的属性名称</param>
  139. private void BindingProperty(Control ParentControl, Control control, object model, string mPropertyName, string controlPropertyName)
  140. {
  141. Action<object> action = info =>
  142. {
  143. try
  144. {
  145. var controlType = control.GetType();
  146. var mType = info.GetType().GetProperty(mPropertyName);
  147. var controlProperty = controlType.GetProperty(controlPropertyName);
  148. if (controlProperty != null)
  149. {
  150. controlProperty.SetValue(control, Convert.ChangeType(mType.GetValue(info, null), controlProperty.PropertyType), null);
  151. }
  152. }
  153. catch (Exception ex)
  154. {
  155. //TODO
  156. }
  157. };
  158. //添加到监听
  159. new Watcher(ParentControl, model.GetType(), model, mPropertyName, action);
  160. //初始化数据(将实体数据赋给控件属性)
  161. action(model);
  162. }
  163. }
  164. }


这样就行了么?

当然不行,我们还要给对象的属性手动改get set 呢(wpf也是要手动给需要绑定通知变更的属性改get set),前面Dep我们集成了 Get  Set  ,那这列给模型属性改 get set 就简单多了。

  1. public class TestModel
  2. {
  3. List<Dep> dep = Dep.InitDeps(3);
  4. public int Value { get=>dep[0].Get<int>(); set=>dep[0].Set(value); }
  5. public string Text { get=>dep[1].Get<string>(); set=>dep[1].Set(value); }
  6. public string BtnName { get=>dep[2].Get<string>(); set=>dep[2].Set(value); }
  7. public void BtnClick(object o)
  8. {
  9. this.BtnName = string.Format("绑定事件{0}", DateTime.Now.Millisecond);
  10. }
  11. }

这就完了么? NO

就像 vue ,要正常解析,要按 它的规则去在代码中加标识 什么 v-if  v-model 等等,那我们这里也有个规则。说到这个规则,winform控件里有个 Tag 属性,是给程序员做拓展用的,一般很少用,而我们的 ViewBind解析代码里面是按照一定规则解析的。

我们的规则是这样的 

绑定属性: data-控件要绑定的属性名-模型对应的属性名  ,比如给 控件的 Text 属性绑定 模型对象 的 Name 属性 则 data-Text-Name.

绑定事件:ev-控件的事件名-模型中的方法名   ,如控件的 Click 事件 绑定模型中 Change 方法 则 ev-Click-Change

一个控件绑定多个属性或者事件用|隔开 ,如 data-Text-Name|ev-Click-Change


到这里准备工作就做完了。

What???准备工作那么多?

不不不,前面的 Dep ,Watcher 等只是封装好了的,我们要做的就是 

1.更改我们 模型属性的 get set 方法,看代码很清楚,很容易,只要创建多个 Dep,然后给每个 属性改一下。

2.按照规则给控件绑定数据或者事件。

3.最后,在窗口初始化时加上创建绑定对象。

  1. private void Form1_Load(object sender, EventArgs e)
  2. {
  3. var model = new TestModel { Value = 50, BtnName="绑定事件" };
  4. new ViewBind(this, model);
  5. }



设定好控件的 Tag,我们在窗体初始化的时候,是需要简单的一行代码就可以了。


到这里就实现了。看效果(这里 输入框 和 label 以及 滚动条 绑定的是 Value 属性,而 按钮绑定 Text 属性 和 Click 事件)





本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
  

闽ICP备14008679号