如何搭建新的WPF项目框架
下面就wpf项目框架搭建步骤一步一步的分享给大家。
在wpf项目开发中最常用的开发模式无疑是mvvm模式, mvvm模式开发的好处,在这里就不详细讨论, 还有 本文中所使用mvvmlight框架,为什么使用mvvm框架(1、框架较轻,2、学习成本低、3、适用大多数中小型项目,4、相对于微软的prism框架更容易上手)
下面开始 一步一步 搭建框架
第一步: 利用反射创建vm构造器
public class viewmodelfactory { private static dictionary<string, object> vmmap = new dictionary<string, object>();<br> public static t getviewmodel<t>() where t : viewmodelbase { type vmtype = typeof(t); if (vmmap.containskey(vmtype.fullname)) { return (t)vmmap[vmtype.fullname]; } else { object vm = activator.createinstance(vmtype); vmmap.add(vmtype.fullname, vm); return (t)vm; } } public static t getviewmodel<t>(object[] data,string id) where t : viewmodelbase { type vmtype = typeof(t); if (vmmap.containskey(id)) { return (t)vmmap[id]; } else { object vm = activator.createinstance(vmtype, data); vmmap.add(id, vm); return (t)vm; } } }
为什么用一个dictionary 将viewmodel 缓存起来,相信利用mvvm模式开发大多数的开发者碰到的问题无疑是各个vm之间的数据通信问题,利用dictionary缓存起来有两个好处:
1、可以解决vm之间相互通信的问题(当然你也可以用mvvmlight的 message机制来通信,ps:个人认为完全没必要用mvvmlight中的 messgae,如果我们框架搭的合理完全可以规避去用mvvmlight中 message,message比较难于管理,如果在我们的代码中出现大量的message无疑是一件痛苦的事情,所以笔者不推荐用mvvmlight中的message)
2、如果我们的应用程序要频繁的与服务器做交互,我们完全可以用缓存,以避免每次都去请求服务器(可以缓存一些在应用程序中一直使用的数据,规避二次请求)
public static t getviewmodel<t>() where t : viewmodelbase 这个函数(将我们的vm完全限定名作为key缓存)适用于单例模式的vm,
public static t getviewmodel<t>(object[] data,string id) where t : viewmodelbase 这个函数(主要构件带参数的vm构造函数,id是唯一id),为什么会用到它,举个例子
例如我们的qq聊天窗口,所有聊天窗口基本相同用到的vm类型也是相同,所以这时候就需要多个vm实例了,第一种方法就行不通了 所以会用到这种方法去构建vm,并将id作为key值缓存起来
第二步:构建我们的viewmodel 基类:
public delegate void closeeventhandle(object sender); public class customviewmodel : viewmodelbase { public event closeeventhandle closeevent; protected bool hasdata; public customviewmodel() { loadcommand = new relaycommand(() => { if (!hasdata) { threadpool.queueuserworkitem((obj) => { lock (this) { onload(); hasdata = true; } }); } }); }public relaycommand loadcommand { private set; get; } protected virtual void onload() { } protected void onclose(object sender) { if (sender != null && closeevent != null) { closeevent(sender); } } }
上面customviewmodel 继承的viewmodelbase 是mvvmlight中的viewmodelbase,至于mvvmlight用法不在本文中讨论,
1、为什么要声明loadcommand,因为大多数的时候我们会在窗体或用户控件loaded的时候去加载数据,有可能是异步加载,也有可能是同步加载,所以我们在customviewmodel中
声明省去了各个vm子类中去声明loadcommand的麻烦,使用时我们直接在xaml利用mvvmlight提供的eventtocommand 去绑定loadcommand,然后在对应的vm去重写customviewmodel基类中的onload方法就可以了。
2、closeevent 故名思议是用来在vm中关闭窗体用的(详细用法会在下文中讨论)
3、我们也可以将一些公有的数据都提炼到vm中来。
第三步 管理窗口:
在开发程序的时候我们通常要去管理窗口的如果你没用到mvvm模式 或者是传统的winform 你可以随便的去new window(),或者随便的去改window的构造函数,或者随意的去构造单例窗体,但是如果用到了mvvm模式似乎以上所说的一切都变得复杂了,刚开始的时候我也是挺伤脑筋的,后来在不断的重构代码中找到了解决方法,(ps:本人也是一名菜鸟,只想把自己在开发中的问题及解决方法分享出来,未必就是好的解决方案,所以大神们勿喷)下面上代码: 构建我们的showhelper类:
public class showhelper { private static dictionary<string, window> windowmanager = new dictionary<string, window>(); public static void showdiagloguc<t>(string title, object[] constructors = null, bool isdialog = false) where t : usercontrol { type controltype = typeof(t); string key; if (constructors == null) //如果构造参数为null { key = controltype.fullname; //key = t 的完全限定名 } else { // 如果不为空 并且 第二个构造参数为string(第二个参数代表id -->有可能是groupid 有可能是userid); if (constructors.length == 2 && constructors[1] is string) //ps:这里本人写死了可以根据需求自行修改 { key = controltype.fullname + constructors[1].tostring(); //key = 控件 完全限定名+id; } else //不满足条件 { key = controltype.fullname; //key = 限定名 } } if (windowmanager.containskey(key)) //如果包含key { windowmanager[key].topmost = true; //设置topmost return; } usercontrol content; if (constructors == null) { content = activator.createinstance(controltype) as usercontrol; } else { content = activator.createinstance(controltype, constructors) as usercontrol; } basewindow window = new basewindow(); //ps这是自己封装 的window,(可以用直接用原始的wpf widnow) window.title = title; windowmanager.add(key, window); window.closed += (sen, cloe) => { windowmanager.remove(key); }; if (isdialog) { window.showdialog(); } else { window.show(); } #region 注册关闭事件 if (content.datacontext as customviewmodel != null) { customviewmodel vm = content.datacontext as customviewmodel; vm.closeevent += (obj) => { if (content.datacontext.equals(obj)) { window.close(); } }; } #endregion } public static customdialogresult showokcancleuc<t>(string title, msgboxbtn okcancle, out object data) where t : control { type vmtype = typeof(t); control content = activator.createinstance(vmtype) as control; okcanlewindow window = new okcanlewindow(); window.showintaskbar = false; return window.showdialog(title, okcancle, content, out data); } public static customdialogresult messageboxdialog(string title, string message, msgboxbtn okcancle) { okcanlewindow window = new okcanlewindow(); window.showintaskbar = false; object none; return window.showdialog(title, okcancle, new messageuc() { message = message }, out none); } 、(1)开始剖析 public static void showdiagloguc<t>(string title, object[] constructors = null, bool isdialog = false) where t : usercontrol showdialoguc 是用来在vm中用来创建usercontrol并显示在window中的。你可能会问为啥用windowmanager 将窗口缓存起来(ps这里主要还是为了解决单例窗口的麻烦), 至于 下面这段代码,我们可以回到创建的customerviewmodel中,对这里需要注册vm中closeevent事件,这样我们在vm中就可以直接调用onclose()方法就ok了 #region 注册关闭事件 if (content.datacontext as customviewmodel != null) { customviewmodel vm = content.datacontext as customviewmodel; vm.closeevent += (obj) => { if (content.datacontext.equals(obj)) { window.close(); } }; } #region 注册关闭事件
(2)开始剖析 public static void showdiagloguc<t>(string title, object[] constructors = null, bool isdialog = false) where t : usercontrol 函数中的 constructors 参数
在开始剖析 constructors 之前先让我们 联想一下应用场景(可以先想下,qq的聊天窗口,例如群聊天吧,所有的群聊天都是相同界面,也就是说他们所对应的vm应该是统一类型的 vm,如果我们双击群,则会弹出对应相应的聊天窗口,正常的思维是会给聊天窗口传递参数也就是组id 这时候我们的vm就需要构造参数了,还有一个问题就是每个群组聊天窗口只能有一个,总不能每次双击就new一个聊天窗口了吧 所以这时候我们就需要做缓存了,) 综上constructors参数在配合viewmodelfactory中的 public static t getviewmodel<t>(object[] data,string id) where t : viewmodelbase 方法 可以解决我们vm中需要传递参数的问题,windowmanager 可以解决窗口缓存问题(如果你现在还看不明白请 仔细看上面代码(虽然代码有点渣),如果实在看不明白可以在留言板吐槽)。
1、 开始 剖析 public static customdialogresult showokcancleuc<t>(string title, msgboxbtn okcancle, out object data) where t : control
(1)开始剖析该函数前让我们 新建一个自己的带返回值的 showdialog 窗口
新建xaml窗口
<controls:basewindow x:class="common.okcanlewindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:controls="clr-namespace:controls;assembly=controls" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" title="messageboxwindow"> <grid x:name="grid"> <grid.rowdefinitions> <rowdefinition/> <rowdefinition height="50"/> </grid.rowdefinitions> <grid.columndefinitions> <columndefinition/> <columndefinition/> </grid.columndefinitions> <button content="确 定" x:name="okbtn" click="okbtn_click" grid.row="1" height="30" width="120" horizontalalignment="right" margin="0 0 10 0"/> <button content="取 消" x:name="canlebtn" click="canlebtn_click" grid.row="1" grid.column="1" height="30" width="120" horizontalalignment="left" margin="10 0 0 0"/> </grid> </controls:basewindow> 后台代码: public partial class okcanlewindow : basewindow { public okcanlewindow() { initializecomponent(); this.closed += (s, e) => { if (result == customdialogresult.none) { result = customdialogresult.cancel; } }; } private system.windows.controls.control control; customdialogresult result; public customdialogresult showdialog(string title, msgboxbtn btnstate, control uc, out object datacontext) { #region 设置控件 if (btnstate == msgboxbtn.ok) //如果为ok状态 { grid.setcolumnspan(okbtn, 2); //设置ok按钮跨两列 okbtn.horizontalalignment = system.windows.horizontalalignment.center; //设置ok按钮居中对齐 canlebtn.visibility = system.windows.visibility.collapsed; //设置cancel 按钮隐藏; if (uc != null) { control = uc; grid.setrow(uc, 0); //设置控件所在grid 的行 grid.setcolumnspan(uc, 2); //设置控件所在grid 的列 this.width = uc.width; //设置窗体宽度 this.height = uc.height + grid.rowdefinitions[1].height.value + 35; //设置窗体宽度 高度 grid.children.add(uc); //加入控件 } } if (btnstate == msgboxbtn.none) //如果为none 既没有ok 也没有 cancle { grid.rowdefinitions.removeat(1); okbtn.visibility = system.windows.visibility.collapsed; canlebtn.visibility = system.windows.visibility.hidden; if(uc !=null) { control = uc; grid.setrow(uc, 0); //设置控件所在grid 的行 grid.setcolumnspan(uc, 2); //设置控件所在grid 的列 this.width = uc.width; //设置窗体宽度 this.height = uc.height + 35; grid.children.add(uc); //加入控件 } } this.title = title; datacontext = uc.datacontext; #endregion this.showdialog();return result; } private void okbtn_click(object sender, routedeventargs e) { result = customdialogresult.ok; this.close(); } private void canlebtn_click(object sender, routedeventargs e) { result = customdialogresult.cancel; this.close(); } } public enum customdialogresult { none,ok,cancel } public enum msgboxbtn { none,ok,okcancel }
剖析 showdialog(string title, msgboxbtn btnstate, control uc, out object datacontext) 方法
在control uc 代表我们要showdialog的uc,datacontext 可以输出一些数据,另外我们要自定义一些枚举
public static customdialogresult messageboxdialog(string title, string message, msgboxbtn okcancle) 主要用来显示自定义messageboxusercontrol;和上面得方法差不多,
以上分为三大步骤对wpf 项目框架搭建的介绍,并结合代码做剖析,希望对大家有所帮助。
推荐阅读
-
如何搭建新的WPF项目框架
-
详解如何搭建mpvue框架搭配vant组件库的小程序项目
-
如何搭建一个功能复杂的前端配置化框架(一)
-
如何搭建新的WPF项目框架
-
从零开始搭建前后端分离的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的项目框架之七使用JWT生成Token(个人见解)
-
从零开始搭建前后端分离的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的项目框架之十一Swagger使用一
-
从零开始搭建前后端分离的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的项目框架之十数据库基础方法的封装
-
[Vue 牛刀小试]:第十六章 - 针对传统后端开发人员的前端项目框架搭建
-
基于webpack4搭建的react项目框架的方法
-
从零开始搭建前后端分离的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的项目框架之九如何进行用户权限控制