【我们一起写框架】MVVM的WPF框架(一)—序篇
前言
我想,有一部分程序员应该是在二三线城市的,虽然不知道占比,但想来应该不在少数。
我是这部分人群中的一份子。
我们这群人,面对的客户,大多是国内中小企业,或者政府的小部门。这类客户的特点是,资金有限,人力有限。
什么意思呢?就是你如果敢给他安一台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
----------------------------------------------------------------------------------------------------
注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!
若您觉得这篇文章还不错,请点击下方的【推荐】,非常感谢!
【我们一起写框架】MVVM的WPF框架(一)—序篇的更多相关文章
- 【我们一起写框架】MVVM的WPF框架(五)—完结篇
前言 这篇文章是WPF框架系列的最后一篇,在这里我想阐述一下我对框架设计的理解. 我对框架设计的理解是这样的: 框架设计不应该局限于任何一种设计模式,我们在设计框架时,应该将设计模式揉碎,再重组:这样 ...
- 【我们一起写框架】MVVM的WPF框架(二)—绑定
MVVM的特点之一是实现数据同步,即,前台页面修改了数据,后台的数据会同步更新. 上一篇我们已经一起编写了框架的基础结构,并且实现了ViewModel反向控制Xaml窗体. 那么现在就要开始实现数据同 ...
- 【我们一起写框架】MVVM的WPF框架(三)—数据控件
这世上,没人能一次性写出完美无缺的框架:因为,任何一个框架都需要项目的淬炼,然后才能升华,趋近完美. 所以,框架是个反复修改的东西,最终形成的东西. 如果你学了一点技术,觉得自己可以写出框架了,觉得自 ...
- 【我们一起写框架】MVVM的WPF框架(四)—DataGrid
前言 这个框架写到这里,应该有很多同学发现,框架很多地方的细节,其实是违背了MVVM的设计逻辑的. 没错,它的确是违背了. 但为什么明知道违背设计逻辑,还要这样编写框架呢? 那是因为,我们编写的是框架 ...
- Jquery如何序列化form表单数据为JSON对象 C# ADO.NET中设置Like模糊查询的参数 从客户端出现小于等于公式符号引发检测到有潜在危险的Request.Form 值 jquery调用iframe里面的方法 Js根据Ip地址自动判断是哪个城市 【我们一起写框架】MVVM的WPF框架(三)—数据控件 设计模式之简单工厂模式(C#语言描述)
jquery提供的serialize方法能够实现. $("#searchForm").serialize();但是,观察输出的信息,发现serialize()方法做的是将表单中的数 ...
- 剖析手写Vue,你也可以手写一个MVVM框架
剖析手写Vue,你也可以手写一个MVVM框架# 邮箱:563995050@qq.com github: https://github.com/xiaoqiuxiong 作者:肖秋雄(eddy) 温馨提 ...
- win10 uwp MVVM 轻量框架
如果在开发过程,遇到多个页面之间,需要传输信息,那么可能遇到设计的问题.如果因为一个页面内包含多个子页面和多个子页面之间的通信问题找不到一个好的解决方法,那么请看本文.如果因为ViewModel代码越 ...
- 前端框架MVVM是什么(整理)
前端框架MVVM是什么(整理) 一.总结 一句话总结:vm层(视图模型层)通过接口从后台m层(model层)请求数据,vm层继而和v(view层)实现数据的双向绑定. 1.我大前端应该不应该做复杂的数 ...
- MVVM、MVC框架的认识
推荐博客: https://blog.csdn.net/jia12216/article/details/55520426 https://www.cnblogs.com/sunny_z/p/7093 ...
随机推荐
- gitlab-ci-runner安装
前言 什么是CI/CD? CI (Continuous Integration) 持续集成, CD (Continuous Delivery) 持续部署 个人理解 本地开发代码, 提交远程仓库 仓库接 ...
- OsharpNS轻量级.net core快速开发框架简明入门教程-Osharp.Redis使用
OsharpNS轻量级.net core快速开发框架简明入门教程 教程目录 从零开始启动Osharp 1.1. 使用OsharpNS项目模板创建项目 1.2. 配置数据库连接串并启动项目 1.3. O ...
- 如何做好技术Team Leader
背景 互联网公司的技术团队管理通常分为2个方向:技术管理和团队管理,互联网公司的技术TL与传统软件公司的PM还是有很大的区别,传统软件公司的PM更多注重于对项目的管理包括项目任务拆解.项目进度以及风险 ...
- 腾讯云发布runC容器逃逸漏洞修复公告
尊敬的腾讯云客户,您好: 近日,腾讯云安全中心监测发现轻量级容器运行环境runc被爆存在容器逃逸漏洞,攻击者可以在利用该漏洞覆盖Host上的runc文件,从而在Host上以root权限执行代码. 为 ...
- sau交流学习社区—vue总结:使用vue的computed属性实现监控变量变化,使用vue的watch属性监控变量变化从而实现其他业务
有时候遇到这么个需求,输入框为空的时候,请求一遍接口,如果输入框不为空的时候,需要点击搜索按钮请求接口. 同步sau交流学习社区:https://www.mwcxs.top/page/464.html ...
- kube-proxy的功能
Kube-proxy的功能 我们知道POD的IP是动态分配的而且经常会变,所以为了可以通过一个不太容易变化的IP访问POD就会使用一个叫做service的东西,通过标签选择器和POD进行关联. Ser ...
- 前端笔记之NodeJS(一)初识NodeJS&内置模块&特点
一.NodeJS简介 NodeJS是开发服务器后台的东西,和PHP.JavaEE.python类似,和传统的浏览器的关注DOM的JS完全不同,将JavaScript触角伸到了服务器端.内核是Chrom ...
- [译]PEP 380--子生成器的语法
导语: PEP(Python增强提案)几乎是 Python 社区中最重要的文档,它们提供了公告信息.指导流程.新功能的设计及使用说明等内容.对于学习者来说,PEP 是非常值得一读的第一手材料,学习中遇 ...
- MongoDB【快速入门】
1.MongDB 简介 MongoDB(来自于英文单词"Humongous",中文含义为"庞大")是可以应用于各种规模的企业.各个行业以及各类应用程序的开源数据 ...
- openJDK知识整理及概念
上周同事去听了阿里openJDK的讲座,收集整理了一下.随着Oracle 撒手,Java 8 官方支持时间持续到 2020 年 12 月:对商业用户(Commercial Users),2019 年 ...