【我们一起写框架】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


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

目录
相关文章
|
1月前
|
设计模式 前端开发 C#
使用 Prism 框架实现导航.NET 6.0 + WPF
使用 Prism 框架实现导航.NET 6.0 + WPF
86 10
|
2月前
|
设计模式 前端开发 C#
WPF 项目中 MVVM模式 的简单例子说明
本文通过WPF项目中的加法操作示例,讲解了MVVM模式的结构和实现方法,包括数据模型、视图、视图模型的创建和数据绑定,以及命令的实现和事件通知机制。
|
3月前
|
测试技术 C# 开发者
“代码守护者:详解WPF开发中的单元测试策略与实践——从选择测试框架到编写模拟对象,全方位保障你的应用程序质量”
【8月更文挑战第31天】单元测试是确保软件质量的关键实践,尤其在复杂的WPF应用中更为重要。通过为每个小模块编写独立测试用例,可以验证代码的功能正确性并在早期发现错误。本文将介绍如何在WPF项目中引入单元测试,并通过具体示例演示其实施过程。首先选择合适的测试框架如NUnit或xUnit.net,并利用Moq模拟框架隔离外部依赖。接着,通过一个简单的WPF应用程序示例,展示如何模拟`IUserRepository`接口并验证`MainViewModel`加载用户数据的正确性。这有助于确保代码质量和未来的重构与扩展。
81 0
|
3月前
|
前端开发 C# 设计模式
“深度剖析WPF开发中的设计模式应用:以MVVM为核心,手把手教你重构代码结构,实现软件工程的最佳实践与高效协作”
【8月更文挑战第31天】设计模式是在软件工程中解决常见问题的成熟方案。在WPF开发中,合理应用如MVC、MVVM及工厂模式等能显著提升代码质量和可维护性。本文通过具体案例,详细解析了这些模式的实际应用,特别是MVVM模式如何通过分离UI逻辑与业务逻辑,实现视图与模型的松耦合,从而优化代码结构并提高开发效率。通过示例代码展示了从模型定义、视图模型管理到视图展示的全过程,帮助读者更好地理解并应用这些模式。
96 0
|
3月前
|
前端开发 开发者 C#
WPF开发者必读:MVVM模式实战,轻松实现现代桌面应用架构,让你的代码更上一层楼!
【8月更文挑战第31天】在WPF应用程序开发中,MVVM(Model-View-ViewModel)模式通过分离应用程序的逻辑和界面,提高了代码的可维护性和可扩展性。本文介绍了MVVM模式的三个核心组件:Model(数据模型)、View(用户界面)和ViewModel(处理数据绑定和逻辑),并通过示例代码展示了如何在WPF项目中实现MVVM模式。通过这种方式,开发者可以构建更加高效和可扩展的桌面应用程序。
160 0
|
3月前
|
前端开发 C# 开发者
WPF开发者必读:MVVM模式实战,轻松构建可维护的应用程序,让你的代码更上一层楼!
【8月更文挑战第31天】在WPF应用程序开发中,MVVM(Model-View-ViewModel)模式通过分离关注点,提高了代码的可维护性和可扩展性。本文详细介绍了MVVM模式的三个核心组件:Model(数据模型)、View(用户界面)和ViewModel(处理数据绑定与逻辑),并通过示例代码展示了如何在WPF项目中实现MVVM模式。通过这种模式,开发者可以更高效地构建桌面应用程序。希望本文能帮助你在WPF开发中更好地应用MVVM模式。
182 0
|
3月前
|
C# Windows 开发者
超越选择焦虑:深入解析WinForms、WPF与UWP——谁才是打造顶级.NET桌面应用的终极利器?从开发效率到视觉享受,全面解读三大框架优劣,助你精准匹配项目需求,构建完美桌面应用生态系统
【8月更文挑战第31天】.NET框架为开发者提供了多种桌面应用开发选项,包括WinForms、WPF和UWP。WinForms简单易用,适合快速开发基本应用;WPF提供强大的UI设计工具和丰富的视觉体验,支持XAML,易于实现复杂布局;UWP专为Windows 10设计,支持多设备,充分利用现代硬件特性。本文通过示例代码详细介绍这三种框架的特点,帮助读者根据项目需求做出明智选择。以下是各框架的简单示例代码,便于理解其基本用法。
166 0
|
3月前
|
设计模式 前端开发 C#
WPF/C#:理解与实现WPF中的MVVM模式
WPF/C#:理解与实现WPF中的MVVM模式
200 0
|
3月前
|
开发框架 JSON 前端开发
WPF应用框架中工作流模块的介绍
WPF应用框架中工作流模块的介绍
|
设计模式 开发框架 前端开发
深入理解WPF中MVVM的设计思想
近些年来,随着WPF在生产,制造,工业控制等领域应用越来越广发,很多企业对WPF开发的需求也逐渐增多,使得很多人看到潜在机会,不断从Web,WinForm开发转向了WPF开发,但是WPF开发也有很多新的概念及设计思想,如:数据驱动,数据绑定,依赖属性,命令,控件模板,数据模板,MVVM等,与传统WinForm,ASP.NET WebForm开发,有很大的差异,今天就以一个简单的小例子,简述WPF开发中MVVM设计思想及应用。
103 0