当前位置: 移动技术网 > IT编程>开发语言>.net > 【我们一起写框架】MVVM的WPF框架之序篇(一)

【我们一起写框架】MVVM的WPF框架之序篇(一)

2018年09月03日  | 移动技术网IT编程  | 我要评论

000190852202,tronatic motion,慈铭体检北京

前言

我想,有一部分程序员应该是在二三线城市的,虽然不知道占比,但想来应该不在少数。

我是这部分人群中的一份子。

我们这群人,面对的客户,大多是国内中小企业,或者政府的小部门。这类客户的特点是,资金有限,人力有限。

什么意思呢?就是你如果敢给他安一台linux服务器,客户的信息员和测试员会把你堵在墙角问候你全家安好,他们window都用不明白呢,你给安linux,要疯啊。

所以,core对我们而言,没有意义,因为大家都是windows。

关于业务

在二三线城市的我们,立身之本不是写算法,也不是各种高级的、新出的技术,而是,写业务模块。

不要小看写业务模块,在二三线城市,一个不会写业务模块的程序员,即便知识面再广,也是个烂程序员。为什么?因为他不能干活呀。

其实把业务模块写好,并不是件容易的事。因为它涉及到对业务的理解,对社会的认知。

以我多年的经验,能写好业务模块的优秀开发人员,通常都需要三四年经验。普通一点,大约就需要五到十年。当然还有十年以上经验,还很没掌握写业务的。

这里面有个特例,那就是硕士和博士。因为他们的年龄较大,阅历较多,所以,通常两年就能把业务写的很好。此外就没有特例了,什么一年经验就能架构,刚毕业就是高级程序员的,那都是培训机构骗毕业生的。

但是,不得不说,高学历真的管用,硕士博士的成材率真的很高。大多数都能成为及格的程序员。

关于框架

回到写框架这件事。在我看来,写框架这件事是个程序员都能干。但写的好坏就另说了,所以写框架这件事还是与经验挂钩的。

在我的认知中,技术视野相对更高,技术范围更广的人写的框架会更好。所以,我认为,[实战]架构师和高级程序员,在本质上没有区别,都是程序员。

只是架构师技术更会好一点,并且接受过项目的洗礼。然而,一个项目只能洗礼一个人,所以能不能成为架构师,就不能只看技术了,要看老板给谁机会了。说白了,就是老板肯不肯花钱赌你能成事。

所以,当技术相差无几,沟通能力,文档能力,甚至生活状态,家境,毅力都是领导考察的依据。因此,机会不是留给有准备的人,而是留给各方面都更出色的人。

当然,如果老板认可你,一年经验做架构师也不是没可能。但在资金有限,人员有限的二三线城市,能遇到这样脑残的领导或老板的概率不高。

虽然架构师不是人人都能做,但框架是可以先学会编写的,毕竟这是个基础。有了基础,就算不能年轻有为,但起码有个机会。

也许,人家28岁拿到的机会,你在40岁也可以拿到,不是吗。有机会总比没有强,不是吗。

框架的前期准备

关于框架编写,我不想在github上放一个源码,然后再写一篇介绍文档。我觉得,这种方式是高手之间的交流。

很多新手,会被这种海量的代码压垮,因为他们还不习惯阅读框架,会出现开始时事倍功半,到最后郁闷放弃的情况。

所以,我们一起从头开始,一起开始mvvm的wpf框架之旅吧。

框架的前期准备

框架是要一步一步编写的,首先,我们先定义框架包含的基本元素。基本元素如下:

wpfui:就是wpf的xaml页面。

viewmodel:每个wpf页面有唯一的viewmodel,用来处理页面业务逻辑。

utility:存放一些常规处理类。

dto:存放数据传输用的实体类。

proxy:获取数据用的代理类。

先定义这五个元素,如果后期需要,我们再进行补充。定义了元素后,我们创建对应的应用程序集。项目结构如下:

做好了项目结构后,我们让viewmodel引用dto,proxy,utility三个程序集,然后在让kibaframework引用viewmodel,这样就实现了上图的结构逻辑。

然后,我们再让viewmodel引用presentationcore,presentationframework,system.windows,windowsbase,systm.xaml这个五个dll,它们是wpf的核心类库,为了后期反射前台控件用。

我怎么知道要引用这五个类库的?

这是经验,仅仅是经验,没有其他。

项目约定

创建完基础结构后,我们要做的是项目约定。(任何框架都有约定,而且约定要高于配置,这是约定优先原则。)

我们建立约定如下:

wpf项目窗体以window作为前缀名创建,如windowmain,windowlogin。

wpf项目页面以page作为前缀名创建,如pagemain,pagexxx。

wpf项目控件(usercontrol)以uc作为前缀名创建,如uctable,ucxxx。

wpf的窗体、页面、控件有且只有一个viewmodel。

viewmodel以vm_作为前缀名+对应的窗体名创建,如vm_windowmain,vm_pagemain。

框架的实现

做完准备工作后,我们开始编写框架,先从系统的核心viewmodel开始,第一步,建立wpf页面与view的关系。

首先我们创建vm的基类baseviewmodel——之后再建立的vm都要引用这个基类。

在vm基类里,我们通过反射实现创建xaml页面,并实现该页面的相关事件。代码如下:

namespace viewmodel
{
    public class baseviewmodel : inotifypropertychanged
    {
        public event propertychangedeventhandler propertychanged;
        public const string uinamesapce = "kibaframework";
        public string uielementname = "";
        public frameworkelement uielement { get; set; }
        public window windowmain { get; set; } //主窗体  
       
        public eventhandler closecallback = null; //窗体/页面/控件 关闭委托 
        public baseviewmodel()
        {
            windowmain = application.current.mainwindow; 
            setuielement();
        }
      
        
        #region 通过反射创建对应的ui元素
        public void setuielement()
        {
            type childtype = this.gettype();//获取子类的类型   
            string name = this.gettype().name;
            uielementname = name.replace("vm_", "");
            uielementname = uielementname.replace("`1", "");//应对泛型实体

            if (name.contains("window"))
            {
                uielement = getelement<window>();
                (uielement as window).closing += (s, e) =>
                {
                    if (closecallback != null)
                    {
                        closecallback(s, e);
                    }
                };
            }
            else if (name.contains("page"))
            {
                uielement = getelement<page>();
                (uielement as page).unloaded += (s, e) =>
                {
                    if (closecallback != null)
                    {
                        closecallback(s, e);
                    }
                };
            }
            else if (name.contains("uc"))
            {
                uielement = getelement<usercontrol>();
                (uielement as usercontrol).unloaded += (s, e) =>
                {
                    if (closecallback != null)
                    {
                        closecallback(s, e);
                    }
                };
            }
            else
            {
                throw new exception("元素名不规范");
            }
        }

        public e getelement<e>()
        {
            type type = getformtype(uinamesapce + "." + uielementname);
            e element = (e)activator.createinstance(type);
            return element;
        }

        public static type getformtype(string fullname)
        {
            assembly assembly = assembly.load(uinamesapce);
            type type = assembly.gettype(fullname, true, false);
            return type;
        }
        #endregion

        #region 窗体操作
        public void show()
        {
            if (uielement is window)
            {
                (uielement as window).show();
            }
            else
            {
                throw new exception("元素类型不正确");
            }
        }

        public void showdialog()
        {
            if (uielement is window)
            {
                (uielement as window).showdialog();
            }
            else
            {
                throw new exception("元素类型不正确");
            }
        }

        public void close()
        {
            if (uielement is window)
            {
                (uielement as window).close();
            }
            else
            {
                throw new exception("元素类型不正确");
            }
        }

        public void hide()
        {
            if (uielement is window)
            {
                (uielement as window).hide();
            }
            else
            {
                throw new exception("元素类型不正确");
            }
        }
        #endregion

        #region message
        public void messagebox(window owner, string msg)
        {
            dispatcherhelper.getuidispatcher().invoke(new action(() =>
            {
                if (owner != null)
                {
                    system.windows.messagebox.show(owner, msg, "提示信息");
                }
                else
                {
                    system.windows.messagebox.show(windowmain, msg, "提示信息");
                }
            }));
        }

        public void messagebox(string msg)
        {
            dispatcherhelper.getuidispatcher().invoke(new action(() =>
            {
                system.windows.messagebox.show(windowmain, msg, "提示信息");
            }));
        }

        public void messagebox(string msg, string strtitle)
        {
            dispatcherhelper.getuidispatcher().invoke(new action(() =>
            {
                system.windows.messagebox.show(windowmain, msg, "提示信息");
            }));
        }

        public void messagebox(string msg, action<bool> callback)
        {
            messagebox("系统提示", msg, callback);
        }

        public void messagebox(string title, string msg, action<bool> callback)
        {
            dispatcherhelper.getuidispatcher().invoke(new action(() =>
            {
                if (system.windows.messagebox.show(windowmain, msg, title, messageboxbutton.yesno) == messageboxresult.yes)
                {
                    callback(true);
                }
                else
                {
                    callback(false);
                }
            }));
        }
        #endregion

        #region   异步线程
        public void asyncload(action action)
        {
            iasyncresult result = action.begininvoke((iar) =>
            {
            }, null);
        }

        public void asyncload(action action, action callback)
        {
            iasyncresult result = action.begininvoke((iar) =>
            {
                this.domenthodbydispatcher(callback);
            }, null);
        }

        public void asyncload<t>(action<t> action, t para, action callback)
        {
            iasyncresult result = action.begininvoke(para, (iar) =>
            {
                this.domenthodbydispatcher(callback);
            }, null);
        }

        public void asyncload<t, r>(func<t, r> action, t para, action<r> callback)
        {
            iasyncresult result = action.begininvoke(para, (iar) =>
            {
                var res = action.endinvoke(iar);
                this.domenthodbydispatcher<r>(callback, res);
            }, null);
        }

        public void asyncload<r>(func<r> action, action<r> callback)
        {
            iasyncresult result = action.begininvoke((iar) =>
            {
                var res = action.endinvoke(iar);
                this.domenthodbydispatcher<r>(callback, res);
            }, null);
        }

        public void domenthodbydispatcher<t>(action<t> action, t obj)
        {
            dispatcherhelper.getuidispatcher().begininvoke(new action(() =>
            {
                action(obj);
            }), dispatcherpriority.normal);
        }

        public void domenthodbydispatcher(action action)
        {
            dispatcherhelper.getuidispatcher().begininvoke(new action(() =>
            {
                action();
            }), dispatcherpriority.normal);
        }
        #endregion  

        protected void onpropertychanged([callermembername]string propertyname = "")
        {
            if (propertychanged != null)
            {
                propertychanged(this, new propertychangedeventargs(propertyname));
            }
        }
    }
}

baseviewmodel的代码如上所示,主要实现了以下功能:

1,ui元素window,page,usercontrol的创建;

2,基础窗体方法,比如show,close,message等等。

3,一系列线程切换的异步操作。

4,简洁化消息处理。(不理解的消息的可参看这篇文章c#语法——消息,mvvm的核心技术。

--------------------------------------------------------------------------------------------------------------------------------

这样,baseviewmodel就编写完成了,之后我们一起修改wpf项目,让窗体的启动的时候,使用viewmodel启动。

在wpf项目中创建windowmain窗体,并在vm中创建对应的viewmodel。

然后在app.xaml.cs文件中重写启动函数,代码如下:

protected override void onstartup(startupeventargs e)
{
    vm_windowmain vm = new vm_windowmain();
    application.current.mainwindow = vm.uielement as window;
    vm.show();
    base.onstartup(e);
} 

在删除app.xaml的startupuri属性。

这样运行wpf就会启动我们的windowmain窗体了。

viewmodel创建窗体

主窗体已经运行了,如果我们想运行其他窗体,该怎么做呢?

很简单,只要在主窗体的viewmodel中new那个想要运行的窗体的vm,然后show一下就可以了。代码如下:

vm_windowcreateuser vm = new vm_windowcreateuser();
vm.show();

到此,窗体相关的内容我们已经一起编写完成了。

接下来需要编写的是page和usercontrol的基础使用方式。

但page和usercontrol是被window使用的,不能直接呈现,所以,在使用page和usercontrol之前,我们需要编写mvvm框架中,用于在wpf页面和viewmodel传递信息的command(命令)。

本篇文章就先不介绍command了,敬请期待下一篇文章,让我们一起继续完善我们的框架。

框架代码已经传到github上了,并且会持续更新。

to be continued

github地址:https://github.com/kiba518/kibaframework

----------------------------------------------------------------------------------------------------

注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
若您觉得这篇文章还不错,请点击下右下角的推荐】,非常感谢!
如果您觉得这篇文章对您有所帮助,那就不妨支付宝小小打赏一下吧。 

 

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网