WPF開發學生信息管理系統【WPF+Prism+MAH+WebApi】(一)

来源:https://www.cnblogs.com/hsiang/archive/2022/05/12/16260662.html
-Advertisement-
Play Games

最近使用WPF開發項目,為了對WPF知識點進行總結,所以利用業餘時間,開發一個學生信息管理系統【Student Information Management System】。本文主要簡述如何通過WPF+Prism+MAH+WebApi進行開發基於三層架構的桌面版應用程式,僅供學習分享使用,如有不足之... ...


最近通過WPF開發項目,為了對WPF知識點進行總結,所以利用業餘時間,開發一個學生信息管理系統【Student Information Management System】。本文主要簡述如何通過WPF+Prism+MAH+WebApi進行開發基於三層架構的桌面版應用程式,僅供學習分享使用,如有不足之處,還請指正。

涉及知識點

  • WPF:WPF(Windows Presentation Foundation)是(微軟推出的)基於Windows的用戶界面框架,提供了統一的編程模型,語言和框架,做到了分離界面設計人員與開發人員的工作;WPF提供了全新的多媒體交互用戶圖形界面。相比於WinForm傳統開發,在WPF中,通過核心的MVVM設計思想,實現前後端的分離。
  • Prism:Prism是一個用於在 WPF、Xamarin Form、Uno 平臺和 WinUI 中構建鬆散耦合、可維護和可測試的 XAML 應用程式框架。通過Prism,可以簡化原生MVVM實現方式,並引入分模塊設計思想。在Prism中,每一個功能,都可以設計成一個獨立的模塊,各個模塊之間松耦合,可維護,可測試。框架中包括 MVVM、依賴註入、Command、Message Event、導航、彈窗等功能。在後續程式功能設計中,都會用到。
  • MAH:MahApps是一套基於WPF的界面組件,通過該組件,可以使用較小的開發成本實現一個相對很好的界面效果。作為後端開發,最頭疼的就是如何設計美化頁面,MAH可以讓開發人員用最少的時間來開發Metro風格的頁面。
  • WebApi:一般是指ASP.NET WebApi 用於快速開發基於REST風格的數據介面的框架。

Prism的模塊化思想

在應用程式開發中,如果不採用模塊化思想,那麼各個頁面混合在一起,看起雜亂無章,具體如下所示:

 

當我們引入模塊化思想,那麼各個模塊的界限將變得清晰,如下所示:

 

 

在本文示例的學生信息管理系統中,就是採用模塊思想,使項目的各個模塊即相對完整,又相互獨立。如下所示:

在開發中,引入模塊化思想,通過Prism進行代碼佈局,如下所示:

 

 

MVVM思想

MVVM是Model-View-ViewModel(模型-視圖-視圖模型)的縮寫形式,它通常被用於WPF或Silverlight開發。MVVM的根本思想就是界面和業務功能進行分離,View的職責就是負責如何顯示數據及發送命令,ViewModel的功能就是如何提供數據和執行命令。各司其職,互不影響。我們可以通過下圖來直觀的理解MVVM模式:

 

 

在本示例中,所有開發都將遵循MVVM思想的設計模式進行開發,如下所示:

頁面佈局

在學生信息管理系統主界面,根據傳統的佈局方式,主要分為上(Header),中【左(Navigation),右(Main Content)】,下(Footer)四個部分,如下所示:

 

 

創建一個模塊

一個模塊是一個獨立的WPF類庫,在項目中,一個普通的類實現了IModule介面,就表示一個模塊,以學生模塊為例,如下所示:

 1 using Prism.Ioc;
 2 using Prism.Modularity;
 3 using SIMS.StudentModule.ViewModels;
 4 using SIMS.StudentModule.Views;
 5 using System;
 6 
 7 namespace SIMS.StudentModule
 8 {
 9     public class StudentModule : IModule
10     {
11         public void OnInitialized(IContainerProvider containerProvider)
12         {
13             
14         }
15 
16         public void RegisterTypes(IContainerRegistry containerRegistry)
17         {
18             containerRegistry.RegisterForNavigation<Student, StudentViewModel>(nameof(Student));
19         }
20     }
21 }

註意:在模塊中,需要實現兩個介面方法。在此模塊中的RegisterTypes方法中,可以註冊導航,視窗等以及初始化工作。

如果不註冊為導航,而是需要註冊到某一個Region中,則可以在OnInitialized方法中進行,以導航模塊為例,如下所示:

 1 using Prism.Ioc;
 2 using Prism.Modularity;
 3 using Prism.Regions;
 4 using SIMS.NavigationModule.Views;
 5 using System;
 6 
 7 namespace SIMS.NavigationModule
 8 {
 9     public class NavigationModule : IModule
10     {
11         public void OnInitialized(IContainerProvider containerProvider)
12         {
13             var regionManager = containerProvider.Resolve<IRegionManager>();
14             regionManager.RegisterViewWithRegion("NavRegion",typeof(Navigation));
15         }
16 
17         public void RegisterTypes(IContainerRegistry containerRegistry)
18         {
19         }
20     }
21 }

View和ViewModel自動適配

View和ViewMode在註冊導航時,可以手動匹配,也可以自動匹配【需要以固定的方式命名才可以自動適配】。自動適配,需要是在UserControl中,增加一句prism:ViewModelLocator.AutoWireViewModel="True"即可,以標題頭為例,如下所示:

 1 <UserControl x:Class="SIMS.Views.Header"
 2              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
 5              xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
 6              xmlns:local="clr-namespace:SIMS.Views"
 7              mc:Ignorable="d"
 8              xmlns:prism="http://prismlibrary.com/"
 9              prism:ViewModelLocator.AutoWireViewModel="True"
10              xmlns:mahApps="http://metro.mahapps.com/winfx/xaml/controls"
11              d:DesignHeight="100" d:DesignWidth="800">
12     <UserControl.Resources>
13         <ResourceDictionary>
14             <ResourceDictionary.MergedDictionaries>
15                 <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
16             </ResourceDictionary.MergedDictionaries>
17         </ResourceDictionary>
18     </UserControl.Resources>
19 
20     <Grid Background="{DynamicResource MahApps.Brushes.Accent}">
21         <Grid.RowDefinitions>
22             <RowDefinition Height="*"></RowDefinition>
23             <RowDefinition Height="Auto"></RowDefinition>
24         </Grid.RowDefinitions>
25         <TextBlock Grid.Row="0" Text="學生信息管理系統" Foreground="White" FontSize="32" FontWeight="Bold" HorizontalAlignment="Left" VerticalAlignment="Center"  Margin="20,5"></TextBlock>
26         <StackPanel Grid.Row="1" HorizontalAlignment="Right" Orientation="Horizontal">
27             <TextBlock Text="Hello" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="3"></TextBlock>
28             <TextBlock Text="Admin" Foreground="White"  Margin="3" FontWeight="Bold"></TextBlock>
29             <TextBlock Text="|" Foreground="White"  Margin="3"></TextBlock>
30             <TextBlock Text="Logout" Foreground="White"  Margin="3" FontWeight="Bold"></TextBlock>
31         </StackPanel>
32     </Grid>
33 
34 </UserControl>

彈出模態視窗

在Prism中,模塊中的視圖都是以UserControl的形式存在,那麼如果需要彈出窗體頁面,就需要在ViewModel中,實現IDialogAware介面,以Login登錄視窗為例,如下所示:

  1 using Prism.Regions;
  2 using Prism.Services.Dialogs;
  3 using SIMS.Views;
  4 using System;
  5 using System.Collections.Generic;
  6 using System.Linq;
  7 using System.Text;
  8 using System.Threading.Tasks;
  9 using System.Windows;
 10 
 11 namespace SIMS.ViewModels
 12 {
 13     public class LoginViewModel : BindableBase, IDialogAware
 14     {
 15         private IRegionManager _regionManager;
 16         private IContainerExtension _container;
 17 
 18         private string userName;
 19 
 20         public string UserName
 21         {
 22             get { return userName; }
 23             set {SetProperty(ref userName , value); }
 24         }
 25 
 26         private string password;
 27 
 28         public string Password
 29         {
 30             get { return password; }
 31             set { SetProperty(ref  password , value); }
 32         }
 33 
 34 
 35         public LoginViewModel(IContainerExtension container,IRegionManager regionManager)
 36         {
 37             this._container = container;
 38             this._regionManager = regionManager;
 39         }
 40 
 41         private void InitInfo() {
 42             var footer = _container.Resolve<Footer>();
 43             IRegion footerRegion = _regionManager.Regions["LoginFooterRegion"];
 44             footerRegion.Add(footer);
 45         }
 46 
 47         #region 命令
 48 
 49         private DelegateCommand loadedCommand;
 50 
 51         public DelegateCommand LoadedCommand
 52         {
 53             get
 54             {
 55                 if (loadedCommand == null)
 56                 {
 57                     loadedCommand = new DelegateCommand(Loaded);
 58                 }
 59                 return loadedCommand;
 60             }
 61         }
 62 
 63         private void Loaded()
 64         {
 65             //InitInfo();
 66         }
 67 
 68         private DelegateCommand loginCommand;
 69 
 70         public DelegateCommand LoginCommand
 71         {
 72             get
 73             {
 74                 if (loginCommand == null)
 75                 {
 76                     loginCommand = new DelegateCommand(Login);
 77                 }
 78                 return loginCommand;
 79             }
 80         }
 81 
 82         private void Login() {
 83             if (string.IsNullOrEmpty(UserName) || string.IsNullOrEmpty(Password)) {
 84                 MessageBox.Show("用戶名或密碼為空,請確認");
 85                 return;
 86             }
 87             if (UserName == "admin" && Password == "abc123")
 88             {
 89                 CloseWindow();
 90             }
 91             else {
 92                 MessageBox.Show("用戶名密碼不正確,請確認");
 93                 return;
 94             }
 95         }
 96 
 97         private DelegateCommand cancelCommand;
 98 
 99         public DelegateCommand CancelCommand
100         {
101             get
102             {
103                 if (cancelCommand == null)
104                 {
105                     cancelCommand = new DelegateCommand(Cancel);
106                 }
107                 return cancelCommand;
108             }
109         }
110 
111         private void Cancel() {
112             RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel));
113         }
114 
115         #endregion
116 
117         #region DialogAware介面
118 
119         public string Title => "SIMS-Login";
120 
121         public event Action<IDialogResult> RequestClose;
122 
123         /// <summary>
124         /// 成功時關閉視窗
125         /// </summary>
126         public void CloseWindow() {
127             RequestClose?.Invoke(new DialogResult(ButtonResult.OK));
128         }
129 
130         public bool CanCloseDialog()
131         {
132             return true;
133         }
134 
135         public void OnDialogClosed()
136         {
137             //當關閉時
138             RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel));
139         }
140 
141         public void OnDialogOpened(IDialogParameters parameters)
142         {
143             //傳遞解析參數
144         }
145 
146         #endregion
147     }
148 }

實現了IDialogAware介面,表示以視窗的形態出現,在需要彈出視窗的地方進行調用即可。如下所示:

1 public MainWindowViewModel(IContainerExtension container, IRegionManager regionManager, IEventAggregator eventAggregator,IDialogService dialogService) { 
2     this._container = container;
3     this._regionManager = regionManager;
4     this.eventAggregator = eventAggregator;
5     this._dialogService = dialogService;
6     //彈出登錄視窗
7     this._dialogService.ShowDialog("Login", null, LoginCallback, "MetroDialogWindow");
8     this.eventAggregator.GetEvent<NavEvent>().Subscribe(Navigation);
9 }

註意:MetroDialogWindow是自定義個一個Metro風格的視窗,如果為空,則採用預設視窗風格。

模塊間交互

按照模塊化設計思想,雖然各個模塊之間相互獨立,但是難免為遇到模塊之間進行交互的情況,所以Prism提供了事件聚合器,通過命令的發佈和訂閱來實現模塊間的數據交互。以導航模塊為例,當點擊某一個導航時,發佈一個命令,在主視窗訂閱此事件,當收到事件時,將此導航對應的頁面渲染到主頁面區域中。步驟如下:

1. 定義一個事件

 1 using Prism.Events;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading.Tasks;
 7 
 8 namespace SIMS.Utils.Events
 9 {
10     /// <summary>
11     /// 導航事件
12     /// </summary>
13     public class NavEvent : PubSubEvent<string>
14     {
15     }
16 }

2. 發佈事件

用戶點擊導航菜單時,觸發NavCommand,然後發佈命令。

 1 private DelegateCommand<object> navCommand;
 2 
 3 public DelegateCommand<object> NavCommand
 4 {
 5     get
 6     {
 7         if (navCommand == null)
 8         {
 9 
10             navCommand = new DelegateCommand<object>(Navigation);
11         }
12         return navCommand;
13     }
14 }
15 
16 private void Navigation(object obj) {
17     var menuItem = (HamburgerMenuItem)obj;
18     if (menuItem != null) { 
19         var tag = menuItem.Tag;
20         if (tag!=null) { 
21             this.eventAggregator.GetEvent<NavEvent>().Publish(tag.ToString());
22         }
23     }
24 }

3. 訂閱命令

在主視窗,訂閱命令,當收到命令時,再初始化模塊信息,如下所示:

 1 namespace SIMS.ViewModels
 2 {
 3     public class MainWindowViewModel:BindableBase
 4     {
 5 
 6         private IEventAggregator eventAggregator;
 7         private IContainerExtension _container;
 8         private IRegionManager _regionManager;
 9         private IDialogService _dialogService;
10         public MainWindowViewModel(IContainerExtension container, IRegionManager regionManager, IEventAggregator eventAggregator,IDialogService dialogService) { 
11             this._container = container;
12             this._regionManager = regionManager;
13             this.eventAggregator = eventAggregator;
14             this._dialogService = dialogService;
15             //彈出登錄視窗
16             this._dialogService.ShowDialog("Login", null, LoginCallback, "MetroDialogWindow");
17             this.eventAggregator.GetEvent<NavEvent>().Subscribe(Navigation);
18         }
19 
20         private void LoginCallback(IDialogResult dialogResult) {
21             if (dialogResult.Result != ButtonResult.OK) {
22                 Application.Current.Shutdown();
23             }
24         }
25 
26         #region 事件和命令
27 
28         private DelegateCommand loadedCommand;
29 
30         public DelegateCommand LoadedCommand
31         {
32             get {
33                 if (loadedCommand == null) {
34                     loadedCommand = new DelegateCommand(Loaded);
35                 }
36                 return loadedCommand; }
37         }
38 
39         private void Loaded() {
40             InitInfo();
41         }
42 
43 
44         
45 
46         private void InitInfo() {
47             var header = _container.Resolve<Header>();
48             IRegion headerRegion = _regionManager.Regions["HeaderRegion"];
49             headerRegion.Add(header);
50             //
51             var footer = _container.Resolve<Footer>();
52             IRegion footerRegion = _regionManager.Regions["FooterRegion"];
53             footerRegion.Add(footer);
54 
55             var welcome = _container.Resolve<Welcome>();
56             IRegion welcomeRegion = _regionManager.Regions["ContentRegion"];
57             welcomeRegion.Add(welcome);
58         }
59 
60         private void Navigation(string source) {
61             _regionManager.RequestNavigate("ContentRegion", source);
62             //MessageBox.Show(source);
63         }
64 
65 

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 大麥網是中國綜合類現場娛樂票務營銷平臺,業務覆蓋演唱會、 話劇、音樂劇、體育賽事等領域今天,我們要用代碼來實現他的購票過程 先來看看完成後的效果是怎麼樣的 對於本篇文章有疑問的同學可以加【資料白嫖、解答交流群:753182387】 開發環境 版 本:anaconda(python3.8.8) ...
  • 以前我們定義類都是用class關鍵詞,但從Java 16開始,我們將多一個關鍵詞record,它也可以用來定義類。record關鍵詞的引入,主要是為了提供一種更為簡潔、緊湊的final類的定義方式。 下麵就來具體瞭解record類的細節。配套視頻教程:Java 16 新特性:使用record聲明類 ...
  • Spring Ioc源碼分析系列--Ioc容器BeanFactoryPostProcessor後置處理器分析 前言 上一篇文章Spring Ioc源碼分析系列--Ioc源碼入口分析已經介紹到Ioc容器的入口refresh()方法,並且分析了refresh()方法裡面的前三個子方法分析了一下。還記得分 ...
  • Predicate<T>:常用的四個方法 boolean test(T t):對給定的參數進行判斷(判斷邏輯由Lambda表達式實現),返回一個布爾值 default Predicate<T>negate():返回一個邏輯的否定,對應邏輯非 default Predicate<T>and(Predi ...
  • 停更這些天,業餘時間和粉絲群的幾個大佬合作寫了一個基於Spring Authorization Server的OAuth2授權伺服器的管理控制台項目Id Server,我覺得這個項目能夠大大降低OAuth2授權伺服器使用難度。可以讓你很方便地去管理OAuth2客戶端信息,甚至可以一鍵生成OAuth2 ...
  • 前言 刷題地址:https://buuoj.cn/challenges 首先打開是一個笑臉,查看源代碼,如下圖發現了,一個文件 一.代碼分析 發現是一堆代碼,需要PHP代碼審計,全部代碼如下。 1 <?php 2 highlight_file(lxx_file); 3 class emmm 4 { ...
  • Python內置函數 | V3.9.1 | 共計155個 還沒學完, 還沒記錄完, 不知道自己能不能堅持記錄下去 1.ArithmeticError 2.AssertionError 3.AttributeError 4.BaseException 5.BlockingIOError 6.Broke ...
  • 1、while迴圈 當指定的條件為真時迴圈執行代碼塊 while 語法: while (condition) { statement; } 比如: <?php $i=1; while($i<=5) { echo "The number is " . $i . "<br>"; $i++; } ?> d ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...