c# 模仿 vue 实现 winform 的数据模型双向绑定 --升级版(动态代理版)

早先写了这样一篇文章 :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

猜你喜欢

转载自blog.csdn.net/mengtoumingren/article/details/82192928