WPF如何构建MVVM+模块化的桌面应用

这篇具有很好参考价值的文章主要介绍了WPF如何构建MVVM+模块化的桌面应用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

为何模块化

模块化是一种分治思想,不仅可以分离复杂的业务逻辑,还可以进行不同任务的分工。模块与模块之间相互独立,从而构建一种松耦合的应用程序,便于开发和维护。

开发技术

.NET 6 + WPF + Prism (v8.0.0.1909) + HandyControl (v3.4.0)

知识准备

什么是MVVM

Model-View-ViewModel 是一种软件架构设计,它是一种简化用户界面的事件驱动编程方式。Model:数据模型,用来存储数据。 View:视图界面,用来展示UI界面和响应用户交互。ViewModel:连接View和Model的中间件,起到了桥梁的作用。

什么是Prism

Prism 是一套桌面开发框架,用于在WPF和Xamarin Forms中构建松耦合、可维护、可以测试的XAML应用程序。Prism提供了一组设计模式的实现,这些模式有助于编写结构良好且可维护的XAML应用程序,包括MVVM、依赖注入、命令、事件聚合器等。

什么是HandyControl

HandyControl 是一套WPF控件库,它几乎重写了所有原生样式,同时包含80余款自定义控件。

搭建项目

假设现在有一套叫Lapis的业务系统,包含A和B两块业务。业务A含有<页面1>和<页面2>,业务B含有<页面3>。界面设计如下:

WPF如何构建MVVM+模块化的桌面应用

下面我们就按照上述要求,来搭建一套MVVM + 模块化的桌面应用程序。

首先,新建一个名为Lapis.WpfDemo的解决方案,分别创建以下四个不同项目:其中Lapis.Shell是WPF应用程序,其余是WPF类库。如图所示:

WPF如何构建MVVM+模块化的桌面应用

Lapis.Share: 是一个共享库,用来定义抽象基类和一些公共方法,供上层调用。它引用了Prism.Wpf、Prism.Core和HandyControl第三方Nuget包。BaseViewModel 是一个视图模型基类,继承自 BindableBase,分别定义了EventAggregatorRegionManagerLoadCommand 属性。代码如下:

WPF如何构建MVVM+模块化的桌面应用WPF如何构建MVVM+模块化的桌面应用
 1     /// <summary>
 2     /// 视图模型基类
 3     /// </summary>
 4     public abstract class BaseViewModel : BindableBase
 5     {
 6         private DelegateCommand _loadCommand;
 7         protected IEventAggregator EventAggregator { get; } //事件聚合器
 8         protected IRegionManager RegionManager { get; } // 区域管理器
 9         public DelegateCommand LoadCommand => _loadCommand ??= new(OnLoad); //界面加载命令
10 
11         public BaseViewModel()
12         {
13             RegionManager = ContainerLocator.Current.Resolve<IRegionManager>();
14             EventAggregator = ContainerLocator.Current.Resolve<IEventAggregator>();
15         }
16 
17         /// <summary>
18         /// 界面加载时,由Loaded事件触发
19         /// </summary>
20         protected virtual void OnLoad()
21         {
22         }
23 
24         /// <summary>
25         /// 根据区域名称查找视图
26         /// </summary>
27         /// <param name="regionName">区域名称</param>
28         protected TView TryFindView<TView>(string regionName) where TView : class
29         {
30             return RegionManager.Regions[regionName].Views
31                      .Where(v => v.GetType() == typeof(TView))
32                      .FirstOrDefault() as TView;
33         }
34     }
BaseViewModel.cs

Lapis.ModuleA 和 Lapis.ModuleB: 对应前端业务模块A和B,  模块A包含 PageOne 和 PageTwo 两个视图及视图模型,模块B只含 PageThree 一个视图及视图模型。按照Prism框架规定,视图模型最好以 视图名称 + ViewModel 来命名。如图所示:

WPF如何构建MVVM+模块化的桌面应用

 其中,ModuleA 和 ModuleB 表示模块类,用于初始化模块和注册类型。ModuleA 代码如下:

 1  [Module(ModuleName = "ModuleA", OnDemand = true)]
 2     public class ModuleA : IModule
 3     {
 4         public void OnInitialized(IContainerProvider containerProvider)
 5         {
 6             var regionManager = containerProvider.Resolve<IRegionManager>();
 7             regionManager.RegisterViewWithRegion(ModuleARegionNames.RegionOne, typeof(PageOne)); // 将页面一注册到区域一
 8             regionManager.RegisterViewWithRegion(ModuleARegionNames.RegionTwo, typeof(PageTwo)); // 将页面二注册到区域二
 9         }
10 
11         public void RegisterTypes(IContainerRegistry containerRegistry)
12         {
13         }
14     }

第7和第8行代码:分别将 PageOne 和 PageTwo 注册到 RegionOne 和 RegionTwo。为了方便,区域名称用字符串常量表示。

Lapis.Shell: 是一个启动模块,负责启动/初始化应用程序(加载模块和资源),它包含App启动类、主窗口、侧边菜单和Tab页内容视图及对应的视图模型等。其中 PageSelectedEvent 是一个页面选中事件,用于 ViewModel 之间传递消息,起到解耦作用。如图所示:

WPF如何构建MVVM+模块化的桌面应用

MainWindow 此处作为启动窗口/主窗口。为了让 MainWindow 代码保持简洁,我们只把它当作布局页面来使用。代码片段如下:

 1     <Grid>
 2         <Grid.ColumnDefinitions>
 3             <ColumnDefinition Width="auto" />
 4             <ColumnDefinition />
 5         </Grid.ColumnDefinitions>
 6         <!--  侧边菜单栏内容  -->
 7         <ContentControl Name="sideMenuContentControl" Width="200px" Margin="5" />
 8         <!--  Tab页主内容  -->
 9         <ContentControl Name="tabPagesContentControl" Grid.Column="1" Margin="0,5,5,5" />
10     </Grid>

第7和第9行代码:sideMenuContentControl 和 tabPagesContentControl 是两个内容控件,用来呈现左侧菜单和Tab页面视图。看到这里,大家一定会问:ContentControl 是通过什么来关联视图的?没错,就是上面提到的Region,我们可以在MainWindow.cs中进行区域设置,代码如下:

1     public partial class MainWindow : Window
2     {
3         public MainWindow()
4         {
5             InitializeComponent();
6             RegionManager.SetRegionName(this.sideMenuContentControl, ShellRegionNames.SideMenuContentRegion);
7             RegionManager.SetRegionName(this.tabPagesContentControl, ShellRegionNames.TabPagesContentRegion);
8         }
9     }

然后,同样在 ShellModule 类里对 SideMenuContent 和 TabPagesContent 视图进行区域注册,这样主窗口就能显示左侧菜单和Tab页面了。代码如下:

WPF如何构建MVVM+模块化的桌面应用WPF如何构建MVVM+模块化的桌面应用
 1     [Module(ModuleName = "ShellModule", OnDemand = true)]
 2     public class ShellModule : IModule
 3     {
 4         public void OnInitialized(IContainerProvider containerProvider)
 5         {
 6             var regionManager = containerProvider.Resolve<IRegionManager>();
 7             regionManager.RegisterViewWithRegion(ShellRegionNames.SideMenuContentRegion, typeof(SideMenuContent)); // 注册侧边菜单内容视图
 8             regionManager.RegisterViewWithRegion(ShellRegionNames.TabPagesContentRegion, typeof(TabPagesContent)); // 注册Tab页面内容视图
 9         }
10 
11         public void RegisterTypes(IContainerRegistry containerRegistry)
12         {
13         }
14     }
ShellModule.cs

App 是WPF应用启动入口,由于使用了第三方Prism框架和HandyControl控件库,我们需要对 App.xaml 和 App.xaml.cs 两个文件做一些修改。代码如下:

WPF如何构建MVVM+模块化的桌面应用WPF如何构建MVVM+模块化的桌面应用
 1 <unity:PrismApplication
 2     x:Class="Lapis.Shell.App"
 3     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 4     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 5     xmlns:local="clr-namespace:Lapis.Shell"
 6     xmlns:unity="http://prismlibrary.com/">
 7     <Application.Resources>
 8         <ResourceDictionary>
 9             <ResourceDictionary.MergedDictionaries>
10                 <ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml" />
11                 <ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" />
12             </ResourceDictionary.MergedDictionaries>
13         </ResourceDictionary>
14     </Application.Resources>
15 </unity:PrismApplication>
App.xaml
WPF如何构建MVVM+模块化的桌面应用WPF如何构建MVVM+模块化的桌面应用
 1     public partial class App : PrismApplication
 2     {
 3         protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
 4         {
 5             base.ConfigureModuleCatalog(moduleCatalog);
 6             //
 7             moduleCatalog.AddModule<ShellModule>();      //添加宿主模块
 8             moduleCatalog.AddModule<ModuleA.ModuleA>();  //添加业务模块A
 9             moduleCatalog.AddModule<ModuleB.ModuleB>();  //添加业务模块B
10         }
11 
12         protected override Window CreateShell()
13         {
14             return Container.Resolve<MainWindow>(); //返回主窗体
15         }
16 
17         protected override void RegisterTypes(IContainerRegistry containerRegistry)
18         {
19         }
20     }
App.xaml.cs

接下来,要做的就是左侧菜单和Tab页面之间的交互动作。不同于传统Winform的事件驱动机制,我们使用MVVM模式将视图和UI逻辑分离。因此一般情况下,所有的界面逻辑都应该在 ViewModel 里完成。SideMenuContentViewModel 通过事件聚合器发布页面选中事件,TabPagesContentViewModel 则通过订阅该事件来进行页面切换,代码如下:

WPF如何构建MVVM+模块化的桌面应用WPF如何构建MVVM+模块化的桌面应用
 1     /// <summary>
 2     /// 侧边菜单内容视图模型
 3     /// </summary>
 4     public class SideMenuContentViewModel : BaseViewModel
 5     {
 6         private DelegateCommand<string> _menuSelectedCommand;
 7 
 8         private List<PageInfo> _pages = new()
 9         {
10             new PageInfo { Id = "1" ,RegionName = "RegionOne", DisplayName = "子菜单1" },
11             new PageInfo { Id = "2", RegionName = "RegionTwo", DisplayName = "子菜单2" },
12             new PageInfo { Id = "3", RegionName = "RegionThree", DisplayName = "子菜单3" },
13         };
14 
15         public DelegateCommand<string> MenuSelectedCommand => _menuSelectedCommand ??= new DelegateCommand<string>(ExecuteMenuSelectedCommand);
16 
17         private void ExecuteMenuSelectedCommand(string id)
18         {
19             var info = _pages.Find(x => x.Id == id);
20             if (info != null)
21             {
22                 EventAggregator.GetEvent<PageSelectedEvent>().Publish(info);
23             }
24         }
25     }
SideMenuContentViewModel.cs
WPF如何构建MVVM+模块化的桌面应用WPF如何构建MVVM+模块化的桌面应用
 1     /// <summary>
 2     /// Tab页面内容视图模型
 3     /// </summary>
 4     public class TabPagesContentViewModel : BaseViewModel
 5     {
 6         private TabControl _tabControl;
 7 
 8         protected override void OnLoad()
 9         {
10             _tabControl = TryFindView<TabPagesContent>(ShellRegionNames.TabPagesContentRegion)?.FindName("tabControl") as TabControl;
11 
12             EventAggregator.GetEvent<PageSelectedEvent>().Subscribe(OnPageSelected);
13         }
14 
15         /// <summary>
16         /// 页面选中事件处理
17         /// </summary>
18         /// <param name="page"></param>
19         private void OnPageSelected(PageInfo page)
20         {
21             try
22             {
23                 var existItem = FindItem(_tabControl, page.RegionName);
24                 if (existItem != null)
25                 {
26                     existItem.IsSelected = true;
27                 }
28                 else
29                 {
30                     // 创建页面区域控件
31                     var pageContentControl = new ContentControl();
32                     pageContentControl.SetRegionName(page.RegionName);
33 
34                     var item = new TabItem
35                     {
36                         Name = page.RegionName,     // 区域名称,如:RegionOne、RegionTwo
37                         Header = page.DisplayName,  // 页面名称
38                         IsSelected = true,
39                         Content = pageContentControl
40                     };
41 
42                     _tabControl.Items.Add(item);
43                 }
44             }
45             catch { }
46         }
47 
48         private TabItem FindItem(TabControl tc, string name)
49         {
50             foreach (TabItem item in tc.Items)
51             {
52                 if (item.Name == name)
53                 {
54                     return item;
55                 }
56             }
57             return null;
58         }
59     }
TabPagesContentViewModel.cs

整个UI交互过程,如图所示:

 

WPF如何构建MVVM+模块化的桌面应用

 

至此,整个桌面前端应用就基本完成了。界面如图所示:

WPF如何构建MVVM+模块化的桌面应用

参考资料

欢迎使用HandyControl | HandyOrg

Introduction to Prism | Prism (prismlibrary.com)

.NET Core 3 WPF MVVM框架 Prism系列文章索引 - RyzenAdorer - 博客园 (cnblogs.com)文章来源地址https://www.toymoban.com/news/detail-648471.html

到了这里,关于WPF如何构建MVVM+模块化的桌面应用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 大型Android项目架构:基于组件化+模块化+Kotlin+协程+Flow+Retrofit+Jetpack+MVVM架构实现WanAndroid客户端

    前言:苟有恒,何必三更眠五更起;最无益,莫过一日曝十日寒。 之前一直想写个 WanAndroid 项目来巩固自己对 Kotlin+Jetpack+协程 等知识的学习,但是一直没有时间。这里重新行动起来,从项目搭建到完成前前后后用了两个月时间,平常时间比较少,基本上都是只能利用零碎的

    2024年02月09日
    浏览(54)
  • Rust之构建命令行程序(三):重构改进模块化和错误处理

    Windows 10 Rust 1.74.1   VS Code 1.85.1 这次创建了新的工程minigrep. 为了改进我们的程序,我们将修复与程序结构及其处理潜在错误的方式有关的四个问题。首先,我们的 main 函数现在执行两项任务:解析参数和读取文件。随着我们程序的增长, main 处理的独立任务的数量也会增加。随

    2024年01月18日
    浏览(48)
  • 一个基于.NET Core构建的简单、跨平台、模块化的商城系统

    今天大姚给大家分享一个基于.NET Core构建的简单、跨平台、模块化、完全开源免费(MIT License)的商城系统:Module Shop。 商品:分类、品牌、单位、选项(销售属性)、属性、属性模板、属性组。 销售:订单、物流。 内容:首页配置、评论、回复。 配置:国家、用户、仓库

    2024年03月27日
    浏览(211)
  • LabVIEW应用开发——VI模块化

            我们在写C语言的时候,一些模块化的功能或者多次调用的功能,我们一般会用一个函数封装起来,方便使用并且让代码看起来更加的简洁。这种函数一般都会包含这几个概念, 输入参数 、 输出参数 和 返回值 。而LabVIEW的VI就可以当作是一个函数, 输入参数 就是

    2024年02月07日
    浏览(51)
  • TIMC 驱动的模块化区块链 Axiomesh:致力于构建「Mass Commercial Adoption」新范式

    撰文:JIN,Techub News 在区块链技术的演进历程中,我们正见证历史性的转折点:「Mass Adoption」即区块链的大规模普及,指的是区块链技术从封闭的小范围应用扩展到广泛的行业和市场的过程。它是区块链技术跨越早期采用者的界限,渗透到普通消费者生活的每个角落,成为

    2024年03月24日
    浏览(49)
  • Java模块化应用实践之精简JRE(内含开源)

    Java9及以后的版本引入了模块化特性,但是直到今天JDK21都发布了,依然没有被大量使用起来,那么这个特性就真的没啥意义了吗? 别忘了,Java本身可是把模块化做到了极致的,所以可以利用这个特性对JRE本身进行定制化或者精简化。 由于平时偶尔会开发一些Java的客户端应

    2024年02月07日
    浏览(37)
  • Flask 高级应用:使用蓝图模块化应用和 JWT 实现安全认证

    本文将探讨 Flask 的两个高级特性:蓝图(Blueprints)和 JSON Web Token(JWT)认证。蓝图让我们可以将应用模块化,以便更好地组织代码;而 JWT 认证是现代 Web 应用中常见的一种安全机制。 在大型应用中,一个单独的 Python 文件可能无法容纳所有的路由和视图函数。这时,Flask 的

    2024年02月13日
    浏览(46)
  • Flask中的Blueprints:模块化和组织大型Web应用【第142篇—Web应用】

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 在构建大型Web应用时,良好的组织结构和模块化是至关重要的。Flask提供了Blueprints(蓝图)这一功能,可以帮助我们更有效地组织应用程序的

    2024年04月15日
    浏览(73)
  • 从单体架构向微服务迁移:模块化单体是如何帮助的

    你开始构建一个漂亮的单体系统。也许是一个模块化的单体系统。 随着时间的推移,系统不断增长,需求也在不断变化。渐渐地,系统开始出现裂痕。 这可能是出于组织原因,需要在团队之间分配工作。也可能是由于扩展性问题和性能瓶颈。 你开始评估可能的解决方案,以

    2024年01月16日
    浏览(60)
  • 如何在Vue中进行单元测试?什么是Vue的模块化开发?

    在Vue中进行单元测试可以提高代码的可维护性和可读性,同时也能够帮助开发者更快地找到代码中的问题和潜在的错误。下面是一些在Vue中进行单元测试的步骤: 安装单元测试工具 首先需要安装一个单元测试工具,例如Jest或Mocha。可以使用npm或yarn进行安装。 创建测试文件

    2024年02月12日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包