早先写了这样一篇文章 :https://blog.csdn.net/mengtoumingren/article/details/78680208 ,讲了自己模仿vue 实现winform上实现数据与ui双向绑定功能,但是当时一来做完没有再花心思,二来当时能力与知识层面还不足,直到前段时间才知道 动态代理类库 Castle ClassProxy 模式(其它动态代理类库则不得而知了),对于标识为 virtual 的属性均能进行拦截,犹如当头一棒,立即想到这里的mvvm如何进行改进了。
在原来的实现方式中,有一阶段的用法非常麻烦,估计很多人也因此望而却步。
当时为了实现数据变化的监听,对 viewmodel 的get set 进行了重写:
public class TestModel
{
List<Dep> dep = Dep.InitDeps(3);
public int Value { get=>dep[0].Get<int>(); set=>dep[0].Set(value); }
public string Text { get=>dep[1].Get<string>(); set=>dep[1].Set(value); }
public string BtnName { get=>dep[2].Get<string>(); set=>dep[2].Set(value); }
public void BtnClick(object o)
{
this.BtnName = string.Format("绑定事件{0}", DateTime.Now.Millisecond);
}
}
但是这样虽然是实现了,但还是很麻烦!!!!!
而使用了动态代理的方式后,只需要在属性前面多加一个关键字 virtual,一切都交给动态代理实现即可。不卖关子了,直接上代码。
改进后,对代码重新做了重组,分离了核心部分以及核心扩展部分,核心部分则为实现模型某个属性数据变化发出通知,不依赖ui,而扩展部分则是实现winform 数据和ui数据的绑定。
TViewCore
1.Dep
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
namespace TViewCore
{
public class Dep
{
/// <summary>
/// 全局变量,用户将指定属性的订阅者放入通知列表
/// </summary>
public static IWatcher Target = null;
public static Dictionary<object,Dictionary<string, List<IWatcher>>> dicWatcher = null;
static Dep()
{
dicWatcher = new Dictionary<object, Dictionary<string, List<IWatcher>>>();
}
/// <summary>
/// 添加订阅者
/// </summary>
/// <param name="watcher"></param>
public static void PushWatcher(object obj,string propertyName,IWatcher watcher)
{
Dictionary<string, List<IWatcher>> dicProWatchers = null;
if (dicWatcher.ContainsKey(obj))
{
dicProWatchers = dicWatcher[obj];
}
List<IWatcher> watchers = null;
if (dicProWatchers == null)
{
dicProWatchers = new Dictionary<string, List<IWatcher>>();
dicWatcher.Add(obj, dicProWatchers);
}
if (dicProWatchers.ContainsKey(propertyName))
{
watchers = dicProWatchers[propertyName];
}
if (watchers == null)
{
watchers = new List<IWatcher>();
dicProWatchers.Add(propertyName, watchers);
}
watchers.Add(watcher);
}
/// <summary>
/// 通知
/// </summary>
public static void Notify(object obj,string propertyName)
{
if(dicWatcher.ContainsKey(obj))
{
var dicProWatchers = dicWatcher[obj];
if(dicProWatchers!=null)
{
if (dicProWatchers.ContainsKey(propertyName))
{
var watchers = dicProWatchers[propertyName];
if (watchers != null)
{
watchers.ForEach(watcher =>
{
try
{
watcher.Update();
}
catch (Exception)
{
//
}
});
}
}
}
}
}
}
}
2.IWatcher
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TViewCore
{
/// <summary>
/// 统一监听接口
/// </summary>
public interface IWatcher
{
void Update();
}
}
3.Watcher
using Castle.DynamicProxy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace TViewCore
{
/// <summary>
/// 监听者
/// </summary>
public class Watcher : IWatcher
{
/// <summary>
/// 实体类型
/// </summary>
private Type type = null;
/// <summary>
/// 属性变化触发的委托
/// </summary>
private Action<object> Action = null;
/// <summary>
/// 属性名称
/// </summary>
private string propertyName = null;
/// <summary>
/// 实体
/// </summary>
private object model = null;
/// <summary>
/// 初始化监听者
/// </summary>
/// <param name="type">实体类型</param>
/// <param name="model">实体</param>
/// <param name="propertyName">要监听的属性名称</param>
/// <param name="action">属性变化触发的委托</param>
public Watcher(object model, string propertyName, Action<object> action)
{
this.type = model.GetType();
this.Action = action;
this.propertyName = propertyName;
this.model = model;
this.AddToDep();
}
/// <summary>
/// 添加监听者到属性的订阅者列表(Dep)
/// </summary>
private void AddToDep()
{
PropertyInfo property = this.type.GetProperty(this.propertyName);
if (property == null) return;
Dep.Target = this;
object value = property.GetValue(this.model, null);
Dep.Target = null;
}
/// <summary>
/// 更新数据(监听触发的方法)
/// </summary>
public void Update()
{
this.Action.Invoke(this.model);
}
}
}
4.PropertyInterceptor (这是升级的核心,需引用 Castle,而前面的 dep,watcher 也随着这个更新做了点小调整)
using Castle.DynamicProxy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace TViewCore
{
public class PropertyInterceptor : IInterceptor
{
private object obj;
public PropertyInterceptor(object obj)
{
this.obj = obj;
}
public void Intercept(IInvocation invocation)
{
var methodName = invocation.Method.Name;
//判断是否是属性的 get,set 方法
if (methodName.StartsWith("get_") || methodName.StartsWith("set_"))
{
PropertyInfo property = obj.GetType().GetProperty(methodName.Substring(4, methodName.Length - 4));
//get
if (methodName.StartsWith("get_"))
{
invocation.ReturnValue = property.GetValue(obj, null);
//初始化 watcher
if(Dep.Target!=null)
{
Dep.PushWatcher(obj, property.Name,Dep.Target);
}
}
//set
else if (methodName.StartsWith("set_"))
{
//判断新值跟旧值是否一致,不一致则更新并通知更新
var newValue = invocation.Arguments[0];
var oldValue = property.GetValue(obj, null);
if (!newValue.Equals(oldValue))
{
property.SetValue(obj, newValue, null);
invocation.ReturnValue = newValue;
//Notify
Dep.Notify(obj, property.Name);
}
}
}
else invocation.Proceed();
}
}
}
至此,核心部分已经完成,该部分不会依赖 winform或者其他 ui 类库,仅仅是实现对 model中属性的监听。
利用核心做的一个简单的测试:
TViewCore测试:
using Castle.DynamicProxy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TViewCore;
namespace TViewCoreTest
{
public class Program
{
public class Model
{
public virtual int Value { get; set; }
}
static void Main(string[] args)
{
var model = new Model { Value = 30 };
var pg = new ProxyGenerator();
var pi = new PropertyInterceptor(model);
//通过动态代理得到新的 model
model = (Model)pg.CreateClassProxy(model.GetType(), new IInterceptor[] { pi });
var action = new Action<object>(m =>
{
Console.WriteLine("Value值发生变化:"+((Model)m).Value);
});
//添加到监听
new Watcher(model, "Value", action);
//初始化数据(将实体数据赋给控件属性)
action(model);
model.Value = 133;
model.Value = 3445;
model.Value = 56756;
//这个值与前一个相等,则不刷新
model.Value = 56756;
model.Value = 5656;
Console.ReadLine();
}
}
}
输出结果:
TView (引入 TViewCore 结合 winform实现的)
1.ViewBind (有了 TViewCore 扩展实现winform的ui绑定也比较简单了)
using Castle.DynamicProxy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using TViewCore;
namespace TView
{
public class ViewBind
{
/// <summary>
/// 默认绑定事件
/// </summary>
private string DefaultEvents = "CollectionChange|SelectedValueChanged|ValueChanged|TextChanged";
/// <summary>
/// 默认绑定的属性,从左往右,能找到则赋值
/// </summary>
private string DefaultProperty = "DataSource|Value|Text";
/// <summary>
/// 绑定视图
/// </summary>
/// <param name="ParentControl">父控件</param>
/// <param name="model">模型(对象)</param>
public ViewBind(Control ParentControl, object model)
{
var pg = new ProxyGenerator();
var pi = new PropertyInterceptor(model);
model = pg.CreateClassProxy(model.GetType(), new IInterceptor[] { pi });
this.BindingParentControl(ParentControl, model);
}
/// <summary>
/// 绑定控件
/// </summary>
/// <param name="ParentControl">父控件</param>
/// <param name="model">实体</param>
private void BindingParentControl(Control ParentControl, object model)
{
this.BindControl(ParentControl, model, ParentControl.Controls);
}
/// <summary>
/// 绑定控件
/// </summary>
/// <param name="ParentControl">父控件</param>
/// <param name="model">实体</param>
/// <param name="Controls">子控件列表</param>
private void BindControl(Control ParentControl, object model, Control.ControlCollection Controls)
{
foreach (Control control in Controls)
{
var tag = control.Tag;
if (tag == null) continue;
foreach (var tagInfo in tag.ToString().Split('|'))
{
var tagInfoArr = tagInfo.Split('-');
//属性绑定
if (tagInfoArr[0].Equals("dt")|| tagInfoArr[0].Equals("data"))
{
var bindProperty = string.Empty;
if (tagInfoArr.Length == 2)
{
foreach (var property in DefaultProperty.Split('|'))
{
if(control.GetType().GetProperty(property)!=null)
{
bindProperty = property;
break;
}
}
}
else if (tagInfoArr.Length == 3)
{
bindProperty = tagInfoArr[1];
}
else continue;
string propertyName = tagInfoArr[tagInfoArr.Length - 1];
this.BindingProperty(ParentControl, control, model, propertyName, bindProperty);
this.BindListener(control, model, propertyName, bindProperty);
}
else if (tagInfoArr[0].Equals("ev") && tagInfoArr.Length == 3)
{
//事件绑定
BindEvent(ParentControl, control, model, tagInfoArr[1], tagInfoArr[2]);
}
else
{
if (control.Controls.Count > 0)
{
this.BindControl(ParentControl, model, control.Controls);
}
}
}
}
}
/// <summary>
/// 绑定事件
/// </summary>
/// <param name="ParentControl">父控件</param>
/// <param name="control">要绑定事件的控件</param>
/// <param name="model">实体</param>
/// <param name="eventName">事件名称</param>
/// <param name="methodName">绑定到的方法</param>
private void BindEvent(Control ParentControl, Control control, object model, string eventName, string methodName)
{
var Event = control.GetType().GetEvent(eventName);
if (Event != null)
{
var methodInfo = model.GetType().GetMethod(methodName);
if (methodInfo != null)
{
Event.AddEventHandler(control, new EventHandler((s, e) =>
{
ParentControl.Invoke(new Action(() =>
{
switch (methodInfo.GetParameters().Count())
{
case 0: methodInfo.Invoke(model, null); break;
case 1: methodInfo.Invoke(model, new object[] { new { s = s, e = e } }); break;
case 2: methodInfo.Invoke(model, new object[] { s, e }); break;
default: break;
}
}));
}));
}
}
}
/// <summary>
/// 添加监听事件
/// </summary>
/// <param name="control">要监听的控件</param>
/// <param name="model">实体</param>
/// <param name="mPropertyName">变化的实体属性</param>
/// <param name="controlPropertyName">对应变化的控件属性</param>
private void BindListener(Control control, object model, string mPropertyName, string controlPropertyName)
{
var property = model.GetType().GetProperty(mPropertyName);
var events = this.DefaultEvents.Split('|');
foreach (var ev in events)
{
var Event = control.GetType().GetEvent(ev);
if (Event != null)
{
Event.AddEventHandler(control, new EventHandler((s, e) =>
{
try
{
var controlProperty = control.GetType().GetProperty(controlPropertyName);
if (controlProperty != null)
{
property.SetValue(model, Convert.ChangeType(controlProperty.GetValue(control, null), property.PropertyType), null);
}
}
catch (Exception ex)
{
//TPDO
}
}));
}
}
}
/// <summary>
/// 绑定属性
/// </summary>
/// <param name="ParentControl">父控件</param>
/// <param name="control">绑定属性的控件</param>
/// <param name="model">实体</param>
/// <param name="mPropertyName">绑定的实体属性名称</param>
/// <param name="controlPropertyName">绑定到的控件的属性名称</param>
private void BindingProperty(Control ParentControl, Control control, object model, string mPropertyName, string controlPropertyName)
{
Action<object> action = m =>
{
ParentControl.Invoke(new Action(delegate
{
try
{
var controlType = control.GetType();
var mType = m.GetType().GetProperty(mPropertyName);
var controlProperty = controlType.GetProperty(controlPropertyName);
if (controlProperty != null)
{
switch (controlPropertyName)
{
case "DataSource":
controlProperty.SetValue(control, mType.GetValue(m, null), null);
break;
default:
controlProperty.SetValue(control, Convert.ChangeType(mType.GetValue(m, null), controlProperty.PropertyType), null);
break;
}
}
}
catch (Exception ex)
{
//TODO
}
}));
};
//添加到监听
new Watcher( model, mPropertyName, action);
//初始化数据(将实体数据赋给控件属性)
action(model);
}
}
}
TView 的测试效果:(具体使用方式可参考 https://blog.csdn.net/mengtoumingren/article/details/78756677)