- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
模块化是一种分治思想,不仅可以分离复杂的业务逻辑,还可以进行不同任务的分工。模块与模块之间相互独立,从而构建一种松耦合的应用程序,便于开发和维护.
.Net 6 + WPF + Prism (v8.0.0.1909) + HandyControl (v3.4.0) 。
Model-View-ViewModel 是一种软件架构设计,它是一种简化用户界面的事件驱动编程方式。Model:数据模型,用来存储数据。 View:视图界面,用来展示UI界面和响应用户交互。ViewModel:连接View和Model的中间件,起到了桥梁的作用.
Prism 是一套桌面开发框架,用于在WPF和Xamarin Forms中构建松耦合、可维护、可以测试的XAML应用程序。Prism提供了一组设计模式的实现,这些模式有助于编写结构良好且可维护的XAML应用程序,包括MVVM、依赖注入、命令、事件聚合器等.
HandyControl 是一套WPF控件库,它几乎重写了所有原生样式,同时包含80余款自定义控件.
假设现在有一套叫Lapis的业务系统,包含 A和 B两块业务。业务A含有< 页面1>和<页面2>,业务B含有<页面3>。界面设计如下:
下面我们就按照上述要求,来搭建一套MVVM + 模块化的桌面应用程序.
首先,新建一个名为Lapis.WpfDemo的解决方案,分别创建以下四个不同项目:其中Lapis.Shell是WPF应用程序,其余是WPF类库。如图所示:
Lapis.Share: 是一个共享库,用来定义抽象基类和一些公共方法,供上层调用。它引用了Prism.Wpf、Prism.Core和HandyControl第三方Nuget包。 BaseViewModel 是一个视图模型基类,继承自 BindableBase ,分别定义了 EventAggregator 、 RegionManager 、 LoadCommand 属性。代码如下:
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 }
Lapis.ModuleA 和 Lapis.ModuleB: 对应前端业务模块A和B, 模块A包含 PageOne 和 PageTwo 两个视图及视图模型,模块B只含 PageThree 一个视图及视图模型。按照Prism框架规定,视图模型最好以 视图名称 + ViewModel 来命名。如图所示:
其中, 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 之间传递消息,起到解耦作用。如图所示:
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页面了。代码如下:
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 }
App 是WPF应用启动入口,由于使用了第三方Prism框架和HandyControl控件库,我们需要对 App.xaml 和 App.xaml.cs 两个文件做一些修改。代码如下:
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 >
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 }
接下来,要做的就是左侧菜单和Tab页面之间的交互动作。不同于传统Winform的事件驱动机制,我们使用MVVM模式将视图和UI逻辑分离。因此一般情况下,所有的界面逻辑都应该在 ViewModel 里完成。 SideMenuContentViewModel 通过事件聚合器发布页面选中事件, TabPagesContentViewModel 则通过订阅该事件来进行页面切换,代码如下:
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 }
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 }
整个UI交互过程,如图所示:
。
。
至此,整个桌面前端应用就基本完成了。界面如图所示:
欢迎使用HandyControl | HandyOrg 。
Introduction to Prism | Prism (prismlibrary.com) 。
.NET Core 3 WPF MVVM框架 Prism系列文章索引 - RyzenAdorer - 博客园 (cnblogs.com) 。
最后此篇关于WPF如何构建MVVM+模块化的桌面应用的文章就讲到这里了,如果你想了解更多关于WPF如何构建MVVM+模块化的桌面应用的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
这显然有可能导致一场激烈的 war ,但无论如何,我试一试……(目前)还没有任何明确的答案。 在我的一台机器上,我确实被切换/升级到 Matlab 2012b。这是一个漂亮的新桌面,好吧。但出于很多原
当我的 Docker 容器启动时,我收到以下通知: Docker Desktop has detected that you shared a Windows file into a WSL 2 co
我希望能够(从服务器)连接到远程 PC 并能够访问其文件。我正在尝试使用 PHP 或 JavaScript 来完成此操作。 所以我想知道 1) 是否可能以及 2) 如何做到。 有人有任何示例/解决方案
我想使用 DirectX 为 Windows 7 制作动画桌面背景。我正在使用 C#、SlimDX 和几个 Windows API 函数的 P/Invoke 导入。我对 native Windows
是否可以为 Mac 开发动态桌面?我所说的“动态”是指其图像采用系统参数(例如时间和作业信息)并使用此信息来更新图像的桌面。有关图像状态的信息也将在 session 之间保留。我不是在谈论电影或随机但
我正在 LibGDX 中构建一个仅限桌面的应用程序。该游戏内置了一个 map 编辑器,您可以切换到并更改游戏 map 。我想添加一个功能,如果用户单击关闭窗口并且有未保存的编辑,它会提示您是否要在关闭
有没有像样的Windows桌面JSON lines (JSONL)格式查看器? 这种格式也称为: 行分隔的 JSON (LDJSON) 换行符分隔的 JSON (NDJSON) 我正在寻找至少能够从
我有一个UITableView,它在单元格中显示大量图像,并且我对滚动性能并不满意。我的 UITableView 类似于 iPhone 上的照片应用程序。有谁知道为什么 iphone 照片应用程序滚动
let img1 = document.createElement('img'); img1.setAttribute('src', 'https://caniuse.com/img/browsers
我在同一窗体上有两个不同的网格控件。它们共享相同的上下文菜单。当我选择上下文菜单项时,我无法确定哪个控件是所有者。 最佳答案 ContextMenuStrip 类的 SourceControl 属性将
我已经安装了 Docker Desktop ( version : 2.3.0.4 ) 并启用了 Kubernetes . 我部署了几个 PODS,一切正常,从昨天开始,我遇到了下面提到的一个奇怪的问
我正在制作需要实现TableView的应用程序。 我想将背景图像应用到桌面 View 。 谁能帮我解决这个问题。我正在使用下面的代码来创建 TableView Titanium.UI.setBackg
我在同一窗体上有两个不同的网格控件。它们共享相同的上下文菜单。当我选择上下文菜单项时,我无法确定哪个控件是所有者。 最佳答案 ContextMenuStrip 类的 SourceControl 属性将
您能建议一些库在我的桌面 Java 应用程序中创建雷达图吗? 谢谢 最佳答案 似乎JFreeChart提供了SpiderWebPlot 。 images for which看起来像雷达图。 关于jav
这个问题已经有答案了: 已关闭10 年前。 Possible Duplicate: Not possible to launch a file on a network using Java Desk
就目前情况而言,这个问题不太适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、民意调查或扩展讨论。如果您觉得这个问题可以改进并可能重新开放,visit
我想知道是否可以(使用JAVA)识别用于发出请求的计算机类型,例如:服务器、台式机、PDA(平板电脑、手机等)? 谢谢! 最佳答案 取决于您使用什么来接受请求。对于 http 请求,信息位于请求 he
我添加了一个事件处理程序,用于滚动到我的桌面 View ,但它仅在我使用滚轮滚动时才起作用,但我也需要它在您单击并拖动滚动条时起作用。任何想法有什么问题吗?我尝试使用不同的方法来处理该事件,但没有一个
我想开始开发桌面 Java 应用程序,并且我想知道 Java 社区使用什么。 我从 Swing 开始,但发现它非常初级。 我习惯于在 Visual Studio 2010 中使用 .net c# WP
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,
我是一名优秀的程序员,十分优秀!