首页 > 其他分享 >WPF 客户端设计(MVVM设计模式)

WPF 客户端设计(MVVM设计模式)

时间:2024-02-02 11:32:16浏览次数:31  
标签:string MVVM LogOperate VM Start 线程 WPF 设计模式 MainWindow

WPF(Windows Presentation Foundation)是微软推出的基于Windows 的用户界面框架。

在这里我设计了一份以MVVM设计模式下的纯桌面端应用架构,期间包含界面初始化流程,菜单加载及页面跳转流程等。

以下来详细说明下设计方式:

期间项目使用到了我自己上传到Nuget的包:

WPF 客户端设计(MVVM设计模式)_MVVM

目录

1:启动

2:主界面

2.1 页面跳转

2.2 对话框弹窗

3.BaseViewModel  VM基类

3.1 Command使用

4.菜单首页

5.解决方案


1:启动

在App.Xaml.cs 文件中,OnStartup方法,为避免启动两个相同的桌面程序,启动时判断是否已经有相同的程序启动了。使用System.Threading.Mutex (互斥锁)进行判断。

当前进程有效启动时,则开始加载配置文件,一些设置参数是支持文件保存,启动时进行加载。文件内容使用Json字符串。

封装了一层Config的读写类,可以对配置文件进行读写。

var res = ConfigParamOperation.ReadConfigParam(out P_Environment p);

返回的P_Environment,则为配置文件反序列后的对象。

日志的初始化:logPath为日志文件的根目录

LogOperate.InitLog(curProcressID, logPath);

日志提供的了不同的方法,如 Start、Info、Error、Web等等;调用不同的方法,会记录到不同的文件中,就按实际的业务进行处理;

如果提供的LogOperate不满足使用,则可以自定义类继承LogOperateBase,自行新增方法;

WPF 客户端设计(MVVM设计模式)_WPF_02

当基础数据初始化的工作都做完之后,则调用 静态类Operation.ThreadOperate.OnStart(),开始进行业务流程的初始化,所以业务初始化相关的代码可以写在OnStart方法里。相对应的,程序关闭时,业务结束的流程写在OnExit方法里(都在静态类Operation.hreadOperate里边)。

在App.Xaml.cs文件中,OnLoadCompleted里边增加了两个异常捕获的事件方法,用于捕获UI线程和非UI线程的异常;用于提升客户端的稳定性,当然,最好是在业务方法里边使用Try Catch去捕获异常,因为当最外层连续捕获多次异常后,还是有概率导致进程直接崩溃。

Current.DispatcherUnhandledException += App_OnDispatcherUnhandledException;
     AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

以下是OnStartup的方法代码展示:

private static string W_ID = "350088E0-800B-420F-B401-F8A68004E60A";
        private static System.Threading.Mutex mutex = null;        
protected override void OnStartup(StartupEventArgs e)
        {
            try
            {
                curProcressID = Process.GetCurrentProcess().Id;
                curProcressName = Process.GetCurrentProcess().ProcessName;
                //初始化日志组件
                LogOperate.InitLog(curProcressID, logPath);
                LogOperate.Start("--------------------------------------------------------------------");
                LogOperate.Start("启动.." + curProcressID);
                bool flag = false;
                mutex = new System.Threading.Mutex(true, W_ID, out flag);
 
                if (!flag)
                {
                    LogOperate.Start("找到互斥线程。");
                    //等待2秒
                    System.Threading.Thread.Sleep(2000);
                    int isContinue = -1;
                    //寻找客户端进程
                    Process[] app = Process.GetProcessesByName(curProcressName);
                    LogOperate.Start("查询已启动线程:" + curProcressName + "..数量:" + app.Length.ToString());
                    if (app.Length > 0)
                    {
                        //判断是否有窗口打开,如果有进程却无窗口显示,则关闭进程后,继续启动当前程序
                        foreach (var a in app)
                        {
                            if (a.MainWindowHandle == IntPtr.Zero)
                            {
                                int killId = -1;
                                //获取线程ID
                                try
                                {
                                    killId = a.Id;
                                    if (killId == curProcressID)//当前线程是自己,跳过
                                        continue;
                                    LogOperate.Start(string.Format("获取线程[{0}]...启动时间:{1:HH:mm:ss.ffff}", killId, a.StartTime));
                                }
                                catch (Exception ex)
                                {
                                    LogOperate.Start(string.Format("获取线程ID...失败:{0}", ex.Message));
                                }
                                //杀掉线程
                                try
                                {
                                    if (killId > 0)
                                    {
                                        LogOperate.Start(string.Format("杀掉线程[{0}]...", killId));
                                        try { a.CloseMainWindow(); a.Close(); System.Threading.Thread.Sleep(1000); } catch { }
                                        a.Kill();
                                    }
                                }
                                catch (Exception ex) { LogOperate.Start(string.Format("杀掉线程[{0}]失败:{1}", killId, ex.Message)); }
                            }
                            else
                            {
                                isContinue = a.Id;
                            }
                        }
                    }
                    if (isContinue > 0)
                    {
                        LogOperate.Start(string.Format("当前进程有效[{0}],退出本次启动。", isContinue));
                        LogOperate.Save();
                        Environment.Exit(0);//退出程序  
                        return;
                    }
                }
 
                //主窗体实例化及窗口显示之前,不能进行弹窗
                //主窗体实例化
                while (GetDesktopWindow() == IntPtr.Zero)
                {
                    LogOperate.Start("未找到桌面句柄,重试...");
                    System.Threading.Thread.Sleep(100);
                }
                System.Threading.Thread.Sleep(100);
 
                Task.Run(() =>
                {
                    var res = ConfigParamOperation.ReadConfigParam(out P_Environment p);
                    if (res)
                    {
                        GlobalData.ConfigParams = p;
                    }
                    else
                    {
                        GlobalData.ConfigParams = new P_Environment();
                    }
 
                    try
                    {
                        if (!string.IsNullOrEmpty(GlobalData.ConfigParams.ThemeColor))
                        {
                            Application.Current.Resources["ThemeColor"] = (Brush)(new BrushConverter().ConvertFromString(GlobalData.ConfigParams.ThemeColor));
                        }
                    }
                    catch { }
 
                    while (true)
                    {
                        if (ViewModels.VM_MainWindow.Instance != null)
                        {
                            new System.Threading.Thread(() => Operation.ThreadOperate.OnStart()) { IsBackground = true }.Start();
 
                            break;
                        }
                        Thread.Sleep(500);
                    }
                });
            }
            catch (Exception ex)
            {
                LogOperate.Start("OnStartup_Exception Exit " + ex.ToString());
                LogOperate.Save();
                Environment.Exit(0);
            }
        }

2:主界面

主界面设计比较简单,就是用一个顶层标题栏,加一个Frame;

标题栏用于显示logo,及窗口控制按钮;Frame用于页面切换

WPF 客户端设计(MVVM设计模式)_C#_03

MainWindow 对应的VM 时类VM_MainWindow,继承于BaseViewModel (下面点会详细介绍下VM的基类)

添加了窗口基本的方法,Load,Close等等;

同时也定义了一个静态对象,用于全局可访问VM_MainWindow,用于访问界面跳转,弹窗等等的操作。

public static VM_MainWindow Instance { get; private set; }

2.1 页面跳转

VM_MainWindow.Instance.PageJump(typeof(VM_MenuHomePage));

提供了两个PageJump的方法,按需调用;两个方法区别是什么,入参不同;

一个是传VM的Type类型,然后会在PageJump方法里才创建实例,并显示

一个是传VM的对象实例,在PageJump里边直接使用实例进行页面的切换

/// <summary>
        /// 页面跳转
        /// </summary>
        /// <param name="vmtype">ViewModel 的类型 typeof()</param>
        /// <param name="args">ViewModel 的构造函数入参</param>
        internal void PageJump(Type vmtype, object[] args = null)
 
 
        /// <summary>
        /// 页面跳转,加载缓存中的vm数据
        /// </summary>
        /// <param name="vmobject">vm对象</param>
        internal void PageJump(object vmobject)

在页面切换时,会调用BeforeJumpCheck 做切换前的判断;如果当前不满足切换的条件,则会组织页面的切换。

2.2 对话框弹窗

窗口需要谈对话框时,调用  VM_MainWindow.Instance.Popup 和VM_MainWindow.Instance.Popup2 方法;

Popup 是单按钮对话框,一直返回true

Popup2 是 双按钮对话框,返回true/false;

/// <summary>
        /// 弹出单按钮对话框 永远返回true
        /// </summary>
        /// <param name="title"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public static bool Popup(string title = "提示", string message = "", string comfirm = "确认")
 
 
        /// <summary>
        /// 弹出双按钮对话框
        /// </summary>
        /// <param name="title"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public static bool Popup2(string title = "提示", string message = "", string comfirm = "确认", string cancel = "取消")

BackHome,决定返回首页会触发方法,也就是首页的界面显示

/// <summary>
        /// 返回首页
        /// </summary>
        public void BackHome()
        {
            VM_MainWindow.Instance.PageJump(typeof(VM_MenuHomePage));
        }

3.BaseViewModel  VM基类

此类的代码在Nuget下载的LS.WPF.MVVM的包中

VM类都继承BaseViewModel; 构造函数定义方法有两种:

第一种:空构造函数,但在base里传入绑定的UI界面的Type

例如:      public VM_InitPage() : base(typeof(InitPage))

第二种(一般只用于主界面初始化):入参为UI界面的对象实例

例如:    public VM_MainWindow(MainWindow win) : base(win)

继承了BaseViewModel,会在VM初始化时,同时初始化UI对象(UIElement),所以在VM中需要访问UI界面属性时,可以使用UIElement进行访问。

PageLoad及PageUnLoad 的方法已在基类中绑定,重写想要的方法即可。

同时还提供了UI线程运行方法和非UI线程运行方法,当有些代码必须要使用UI线程进行操作时,可以使用  DoMenthodByDispatcher 进行操作,isAsync标识为异步执行和同步执行,默认为异步执行。

public void DoMenthodByDispatcher<T>(Action<T> action, T obj, bool isAsync = true)

还包含了Bitmap转ImageSource的方法,方便用于图片控件的属性绑定

internal static ImageSource GetImageSource(Bitmap b)

3.1 Command使用

当需要绑定Command方法时,架构提供了一个封装类 DelegateCommand

使用方法如下:

/// <summary>
        /// 操作方法
        /// </summary>
        public DelegateCommand OperationCommand
        {
            get { return new DelegateCommand(Operation); }
        }
 
        private void Operation(Object obj)
        {
        }

Xaml中就直接使用  Command="{Binding OperationCommand}" 即可

4.菜单首页

VM_MainWindow 中,提供Frame用于切换界面;可自由定义。

此处提供了一个左侧菜单栏的首页设计;

MenuHomePage;

WPF 客户端设计(MVVM设计模式)_WPF_04

使用LSMenu 自定义菜单控件;

<uc:LSMenu.MenuSource>
                <uc:LSMenuItem
                    Header="首页"
                    ImagePath="/Asset/Images/MenuImg/Home.png"
                    ItemTag="Home" />
                <uc:LSMenuItem
                    Header="设置"
                    ImagePath="/Asset/Images/MenuImg/Setting.png"
                    ItemTag="Setting" />
                <uc:LSMenuItem
                    Header="退出"
                    ImagePath="/Asset/Images/MenuImg/Exit.png"
                    ItemTag="Exit" />
 </uc:LSMenu.MenuSource>

再对定义的菜单子项做相应的处理。

private void MenuClick(object obj)
        {
            if(obj!=null)
            {
                LogOperate.ClickLog("点击了-菜单【" + obj.ToString() + "】");
                switch (obj.ToString())
                {
                    case "Home":
                        VM_MainWindow.Instance.BackHome();
                        break;
                    case "Setting":
                        break;
                    case "Exit":
                        VM_MainWindow.Instance.Close();
                        break;
                }
            }
        }

5.解决方案

整体结构简单明了;

Asset---图片等资源存放

Models -- 数据模型定义区

Operation -- 业务操作方法定义

Tools -- 工具帮助类

UCControls -- 自定义控件区

ViewModels -- VM类存放区

Views -- Xaml UI界面存放区

GlobalData -- 全局静态类

WPF 客户端设计(MVVM设计模式)_WPF_05


标签:string,MVVM,LogOperate,VM,Start,线程,WPF,设计模式,MainWindow
From: https://blog.51cto.com/u_16544892/9546065

相关文章

  • 【设计模式】原型模式——JDK源码中的原型模式
    原型模式在JDK源码中有广泛的应用,想找原型模式的实现也比较容易,只要找Cloneable接口的子类即可。因为JDK源码中Cloneable的子类太多,本文只讨论最典型的几种。ArrayList首先看ArrayList和原型模式有关的代码:publicclassArrayList<E>extendsAbstractList<E>implementsL......
  • WPF界面魔法:探秘Template奇妙世界,个性化定制你的UI
     概述:WPF中的Template机制为界面定制提供了强大工具,包括控件模板、ItemsPresenter、ItemsPanel、和ItemContainerStyle。通过这些功能,开发者能精确定义控件外观和布局,个性化每个项的样式,实现灵活而美观的用户界面。WPF中各种Template功能用途:Template(控件模板):用途: 控件......
  • WPF魔法:轻松实现依赖注入与控制反转提升代码优雅性与可维护性
     概述:在WPF中实现依赖注入和控制反转,通过定义接口、实现类,配置容器,实现组件解耦、提高可维护性。什么是依赖注入和控制反转?依赖注入(DependencyInjection,DI): 是一种设计模式,旨在减少组件之间的耦合度。通过依赖注入,对象不再自行创建或查找依赖对象,而是通过外部注入的方式提......
  • WPF创建一个类似聊天框的MQTT报文收发界面
    界面的xaml代码如下<ListViewx:Name="LvmqttMsg"Background="Transparent"ItemsSource="{BindingMqttMsgItems}"ScrollViewer.CanContentScroll="False"><ListView.ItemContainerStyle><......
  • WPF 上位机 柱子智控系统
    WPF上位机柱子智控 无边框圆角自定义设计页面左侧菜单,自定义最小化,自定义最大化,自定义关闭按钮C#WPF.net6.0数据库sqlserver2012消息队列redis模拟数据modbus 银柱网-李银柱个人博客http://www.liyinzhu.com ......
  • 23种设计模式
    https://www.bilibili.com/video/BV1Yr4y157Ci?p=26&spm_id_from=pageDriver&vd_source=26936cf2df4b6c321f63de2ec139cfdc八大原则依赖倒置原则(DIP)•高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定)。•抽象(稳定)不应该依赖于实现细节(变化),实现细......
  • 【设计模式】设计模式系列总目
    不断更新中……创建型设计模式单例模式【设计模式】单例模式(1)什么是单例模式【设计模式】单例模式(2)单例模式的几种写法【设计模式】单例模式(3)如何用单例模式解决实际问题【设计模式】单例模式(4)单例模式被破坏的情景【设计模式】单例模式(5)JDK源码中的单例模式【设计模式】单例模式(6)A......
  • 一个 WPF + MudBlazor 的项目模板(附:多项目模板制作方法)
    最近做了几个WPF+MudBlazor的小东西,每次从头搭建环境比较繁琐,然鹅搭建过程还没啥技术含量,索性就直接做了个模板,方便以后使用。1.介绍一个用来创建.NET8+WPF+MudBlazor的项目模板适用于VS2022用法:vs插件市场下载or自己通过Github源码编译2.模板打包方......
  • 通过Demo学WPF—数据绑定(二)
    准备今天学习的Demo是DataBinding中的Linq:创建一个空白解决方案,然后添加现有项目,选择Linq,解决方案如下所示:查看这个Demo的效果:开始学习这个Demoxaml部分查看MainWindow.xaml:<Windowx:Class="Linq.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006......
  • Mybatis 源码系列:领略设计模式在 Mybatis 其中的应用
    目录一、Builder模式二、工厂模式三、单例模式四、代理模式五、组合模式六、模板方式模式七、适配器模式八、装饰器模式九、迭代器模式虽然我们都知道有23种设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应......