最近通过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接口,就表示一个模块,以学生模块为例,如下所示:
using Prism.Ioc;
using Prism.Modularity;
using SIMS.StudentModule.ViewModels;
using SIMS.StudentModule.Views;
using System;
namespace SIMS.StudentModule
{
public class StudentModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<Student, StudentViewModel>(nameof(Student));
}
}
}
注意:在模块中,需要实现两个接口方法。在此模块中的RegisterTypes方法中,可以注册导航,窗口等以及初始化工作。
如果不注册为导航,而是需要注册到某一个Region中,则可以在OnInitialized方法中进行,以导航模块为例,如下所示:
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;
using SIMS.NavigationModule.Views;
using System;
namespace SIMS.NavigationModule
{
public class NavigationModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("NavRegion",typeof(Navigation));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
}
View和ViewModel自动适配
View和ViewMode在注册导航时,可以手动匹配,也可以自动匹配【需要以固定的方式命名才可以自动适配】。自动适配,需要是在UserControl中,增加一句prism:ViewModelLocator.AutoWireViewModel="True"即可,以标题头为例,如下所示:
<UserControl x:Class="SIMS.Views.Header"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SIMS.Views"
mc:Ignorable="d"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:mahApps="http://metro.mahapps.com/winfx/xaml/controls"
d:DesignHeight="100" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid Background="{DynamicResource MahApps.Brushes.Accent}">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="学生信息管理系统" Foreground="White" FontSize="32" FontWeight="Bold" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="20,5"></TextBlock>
<StackPanel Grid.Row="1" HorizontalAlignment="Right" Orientation="Horizontal">
<TextBlock Text="Hello" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="3"></TextBlock>
<TextBlock Text="Admin" Foreground="White" Margin="3" FontWeight="Bold"></TextBlock>
<TextBlock Text="|" Foreground="White" Margin="3"></TextBlock>
<TextBlock Text="Logout" Foreground="White" Margin="3" FontWeight="Bold"></TextBlock>
</StackPanel>
</Grid>
</UserControl>
弹出模态窗口
在Prism中,模块中的视图都是以UserControl的形式存在,那么如果需要弹出窗体页面,就需要在ViewModel中,实现IDialogAware接口,以Login登录窗口为例,如下所示:
using Prism.Regions;
using Prism.Services.Dialogs;
using SIMS.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace SIMS.ViewModels
{
public class LoginViewModel : BindableBase, IDialogAware
{
private IRegionManager _regionManager;
private IContainerExtension _container;
private string userName;
public string UserName
{
get { return userName; }
set {SetProperty(ref userName , value); }
}
private string password;
public string Password
{
get { return password; }
set { SetProperty(ref password , value); }
}
public LoginViewModel(IContainerExtension container,IRegionManager regionManager)
{
this._container = container;
this._regionManager = regionManager;
}
private void InitInfo() {
var footer = _container.Resolve<Footer>();
IRegion footerRegion = _regionManager.Regions["LoginFooterRegion"];
footerRegion.Add(footer);
}
#region 命令
private DelegateCommand loadedCommand;
public DelegateCommand LoadedCommand
{
get
{
if (loadedCommand == null)
{
loadedCommand = new DelegateCommand(Loaded);
}
return loadedCommand;
}
}
private void Loaded()
{
//InitInfo();
}
private DelegateCommand loginCommand;
public DelegateCommand LoginCommand
{
get
{
if (loginCommand == null)
{
loginCommand = new DelegateCommand(Login);
}
return loginCommand;
}
}
private void Login() {
if (string.IsNullOrEmpty(UserName) || string.IsNullOrEmpty(Password)) {
MessageBox.Show("用户名或密码为空,请确认");
return;
}
if (UserName == "admin" && Password == "abc123")
{
CloseWindow();
}
else {
MessageBox.Show("用户名密码不正确,请确认");
return;
}
}
private DelegateCommand cancelCommand;
public DelegateCommand CancelCommand
{
get
{
if (cancelCommand == null)
{
cancelCommand = new DelegateCommand(Cancel);
}
return cancelCommand;
}
}
private void Cancel() {
RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel));
}
#endregion
#region DialogAware接口
public string Title => "SIMS-Login";
public event Action<IDialogResult> RequestClose;
/// <summary>
/// 成功时关闭窗口
/// </summary>
public void CloseWindow() {
RequestClose?.Invoke(new DialogResult(ButtonResult.OK));
}
public bool CanCloseDialog()
{
return true;
}
public void OnDialogClosed()
{
//当关闭时
RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel));
}
public void OnDialogOpened(IDialogParameters parameters)
{
//传递解析参数
}
#endregion
}
}
实现了IDialogAware接口,表示以窗口的形态出现,在需要弹出窗口的地方进行调用即可。如下所示:
public MainWindowViewModel(IContainerExtension container, IRegionManager regionManager, IEventAggregator eventAggregator,IDialogService dialogService) {
this._container = container;
this._regionManager = regionManager;
this.eventAggregator = eventAggregator;
this._dialogService = dialogService;
//弹出登录窗口
this._dialogService.ShowDialog("Login", null, LoginCallback, "MetroDialogWindow");
this.eventAggregator.GetEvent<NavEvent>().Subscribe(Navigation);
}
注意:MetroDialogWindow是自定义个一个Metro风格的窗口,如果为空,则采用默认窗口风格。
模块间交互
按照模块化设计思想,虽然各个模块之间相互独立,但是难免为遇到模块之间进行交互的情况,所以Prism提供了事件聚合器,通过命令的发布和订阅来实现模块间的数据交互。以导航模块为例,当点击某一个导航时,发布一个命令,在主窗口订阅此事件,当收到事件时,将此导航对应的页面渲染到主页面区域中。步骤如下:
1. 定义一个事件
定义一个事件,继承PubSubEvent基类,如下所示:
using Prism.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SIMS.Utils.Events
{
/// <summary>
/// 导航事件
/// </summary>
public class NavEvent : PubSubEvent<string>
{
}
}
2. 发布事件
用户点击导航菜单时,触发NavCommand,然后发布命令。
private DelegateCommand<object> navCommand;
public DelegateCommand<object> NavCommand
{
get
{
if (navCommand == null)
{
navCommand = new DelegateCommand<object>(Navigation);
}
return navCommand;
}
}
private void Navigation(object obj) {
var menuItem = (HamburgerMenuItem)obj;
if (menuItem != null) {
var tag = menuItem.Tag;
if (tag!=null) {
this.eventAggregator.GetEvent<NavEvent>().Publish(tag.ToString());
}
}
}
3. 订阅命令
在主窗口,订阅命令,当收到命令时,再初始化模块信息,如下所示:
namespace SIMS.ViewModels
{
public class MainWindowViewModel:BindableBase
{
private IEventAggregator eventAggregator;
private IContainerExtension _container;
private IRegionManager _regionManager;
private IDialogService _dialogService;
public MainWindowViewModel(IContainerExtension container, IRegionManager regionManager, IEventAggregator eventAggregator,IDialogService dialogService) {
this._container = container;
this._regionManager = regionManager;
this.eventAggregator = eventAggregator;
this._dialogService = dialogService;
//弹出登录窗口
this._dialogService.ShowDialog("Login", null, LoginCallback, "MetroDialogWindow");
this.eventAggregator.GetEvent<NavEvent>().Subscribe(Navigation);
}
private void LoginCallback(IDialogResult dialogResult) {
if (dialogResult.Result != ButtonResult.OK) {
Application.Current.Shutdown();
}
}
#region 事件和命令
private DelegateCommand loadedCommand;
public DelegateCommand LoadedCommand
{
get {
if (loadedCommand == null) {
loadedCommand = new DelegateCommand(Loaded);
}
return loadedCommand; }
}
private void Loaded() {
InitInfo();
}
private void InitInfo() {
var header = _container.Resolve<Header>();
IRegion headerRegion = _regionManager.Regions["HeaderRegion"];
headerRegion.Add(header);
//
var footer = _container.Resolve<Footer>();
IRegion footerRegion = _regionManager.Regions["FooterRegion"];
footerRegion.Add(footer);
var welcome = _container.Resolve<Welcome>();
IRegion welcomeRegion = _regionManager.Regions["ContentRegion"];
welcomeRegion.Add(welcome);
}
private void Navigation(string source) {
_regionManager.RequestNavigate("ContentRegion", source);
//MessageBox.Show(source);
}
#endregion
}
}
注意:一般情况下,只有在不同模块时,才使用事件聚合器进行事件的订阅和发布。如果是同一模块,则没有必要。
核心代码
模块的配置
各个模块之间相互独立,所以在主模块中进行加载时,需要先进行配置。模块加载的方式有很多种,本例采用App.config配置方式,如下所示:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="modules" type="Prism.Modularity.ModulesConfigurationSection, Prism.Wpf" />
</configSections>
<startup>
</startup>
<modules>
<module assemblyFile="SIMS.NavigationModule.dll" moduleType="SIMS.NavigationModule.NavigationModule, SIMS.NavigationModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="NavigationModule" startupLoaded="True" />
<module assemblyFile="SIMS.ScoreModule.dll" moduleType="SIMS.ScoreModule.ScoreModule, SIMS.ScoreModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ScoreModule" startupLoaded="True" />
<module assemblyFile="SIMS.StudentModule.dll" moduleType="SIMS.StudentModule.StudentModule, SIMS.StudentModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="StudentModule" startupLoaded="True" />
<module assemblyFile="SIMS.ClassesModule.dll" moduleType="SIMS.ClassesModule.ClassesModule, SIMS.ClassesModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ClassesModule" startupLoaded="True" />
<module assemblyFile="SIMS.CourseModule.dll" moduleType="SIMS.CourseModule.CourseModule, SIMS.CourseModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="CourseModule" startupLoaded="True" />
<module assemblyFile="SIMS.SysManagementModule.dll" moduleType="SIMS.SysManagementModule.SysManagementModule, SIMS.SysManagementModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="SysManagementModule" startupLoaded="True" />
</modules>
</configuration>
启动入口App.xaml,可以用来配置资源字典等初始化操作,如下所示:
<prism:PrismApplication x:Class="SIMS.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SIMS"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:prism="http://prismlibrary.com/"
>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
<ResourceDictionary Source="/Icons/Icons.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</prism:PrismApplication>
启动入口App.xaml.cs,应用程序需要继承PrismApplication基类,才是一个Prism应用程序。在此文件中初始化App.config中配置的模块。如下所示:
namespace SIMS
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterDialog<Login, LoginViewModel>(nameof(Login));
containerRegistry.Register<IDialogWindow, MetroDialogWindow>("MetroDialogWindow");
}
protected override IModuleCatalog CreateModuleCatalog()
{
return new ConfigurationModuleCatalog();
}
}
}
主窗口MainWindow.xaml页面布局,如下所示:
<mahApps:MetroWindow x:Class="SIMS.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SIMS"
mc:Ignorable="d"
xmlns:prism="http://prismlibrary.com/"
xmlns:mahApps="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
prism:ViewModelLocator.AutoWireViewModel="True"
mahApps:Title="SIMS--Student Information Management System"
mahApps:TitleCharacterCasing="Normal"
d:DesignHeight="1080" d:DesignWidth="1920"
WindowState="Maximized">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}"></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ContentControl prism:RegionManager.RegionName="HeaderRegion" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" MinHeight="80"></ContentControl>
<ContentControl prism:RegionManager.RegionName="NavRegion" Grid.Row="1" Grid.Column="0"></ContentControl>
<ContentControl prism:RegionManager.RegionName="ContentRegion" Grid.Row="1" Grid.Column="1"></ContentControl>
<ContentControl prism:RegionManager.RegionName="FooterRegion" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" MinHeight="50"></ContentControl>
</Grid>
</mahApps:MetroWindow>
主窗口MainWindowViewModel,代码如下:
namespace SIMS.ViewModels
{
public class MainWindowViewModel:BindableBase
{
private IEventAggregator eventAggregator;
private IContainerExtension _container;
private IRegionManager _regionManager;
private IDialogService _dialogService;
public MainWindowViewModel(IContainerExtension container, IRegionManager regionManager, IEventAggregator eventAggregator,IDialogService dialogService) {
this._container = container;
this._regionManager = regionManager;
this.eventAggregator = eventAggregator;
this._dialogService = dialogService;
//弹出登录窗口
this._dialogService.ShowDialog("Login", null, LoginCallback, "MetroDialogWindow");
this.eventAggregator.GetEvent<NavEvent>().Subscribe(Navigation);
}
private void LoginCallback(IDialogResult dialogResult) {
if (dialogResult.Result != ButtonResult.OK) {
Application.Current.Shutdown();
}
}
#region 事件和命令
private DelegateCommand loadedCommand;
public DelegateCommand LoadedCommand
{
get {
if (loadedCommand == null) {
loadedCommand = new DelegateCommand(Loaded);
}
return loadedCommand; }
}
private void Loaded() {
InitInfo();
}
private void InitInfo() {
var header = _container.Resolve<Header>();
IRegion headerRegion = _regionManager.Regions["HeaderRegion"];
headerRegion.Add(header);
//
var footer = _container.Resolve<Footer>();
IRegion footerRegion = _regionManager.Regions["FooterRegion"];
footerRegion.Add(footer);
var welcome = _container.Resolve<Welcome>();
IRegion welcomeRegion = _regionManager.Regions["ContentRegion"];
welcomeRegion.Add(welcome);
}
private void Navigation(string source) {
_regionManager.RequestNavigate("ContentRegion", source);
//MessageBox.Show(source);
}
#endregion
}
}
导航页面Navigation.xaml页面采用MAH的Hamburger菜单,如下所示:
<UserControl x:Class="SIMS.NavigationModule.Views.Navigation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:local="clr-namespace:SIMS.NavigationModule.Views"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
d:DesignHeight="300"
d:DesignWidth="240"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- This is the template for all menu items. In this sample we use the glyph items. -->
<DataTemplate x:Key="HamburgerMenuItem" DataType="{x:Type mah:HamburgerMenuGlyphItem}">
<DockPanel Height="48" LastChildFill="True">
<Grid x:Name="IconPart"
DockPanel.Dock="Left">
<Image Margin="12"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Source="{Binding Glyph}" />
</Grid>
<TextBlock x:Name="TextPart"
VerticalAlignment="Center"
FontSize="16"
Text="{Binding Label}" />
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="HamburgerOptionsMenuItem" DataType="{x:Type mah:HamburgerMenuIconItem}">
<DockPanel Height="48" LastChildFill="True">
<ContentControl x:Name="IconPart"
Content="{Binding Icon}"
DockPanel.Dock="Left"
Focusable="False"
IsTabStop="False" />
<TextBlock x:Name="TextPart"
VerticalAlignment="Center"
FontSize="16"
Text="{Binding Label}" />
</DockPanel>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid Background="{DynamicResource MahApps.Brushes.Accent}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<mah:HamburgerMenu x:Name="HamburgerMenuControl" Grid.Column="0"
DisplayMode="CompactOverlay"
Margin="0"
IsPaneOpen="True"
HamburgerButtonHelpText="Please click me"
HamburgerButtonClick="HamburgerMenuControl_HamburgerButtonClick"
ItemTemplate="{StaticResource HamburgerMenuItem}"
OptionsItemTemplate="{StaticResource HamburgerOptionsMenuItem}"
ItemsSource="{Binding NavItems}"
VerticalScrollBarOnLeftSide="False">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ItemInvoked">
<i:InvokeCommandAction Command="{Binding NavCommand}" CommandParameter="{Binding ElementName=HamburgerMenuControl, Path=SelectedItem}" ></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<!-- Header -->
<mah:HamburgerMenu.HamburgerMenuHeaderTemplate>
<DataTemplate>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="16"
Foreground="White"
Text="" />
</DataTemplate>
</mah:HamburgerMenu.HamburgerMenuHeaderTemplate>
<mah:HamburgerMenu.OptionsItemsSource>
<mah:HamburgerMenuItemCollection>
<mah:HamburgerMenuIconItem x:Name="AboutOption"
Label="About">
<mah:HamburgerMenuIconItem.Icon>
<iconPacks:PackIconMaterial Width="22"
Height="22"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Kind="Help" />
</mah:HamburgerMenuIconItem.Icon>
<mah:HamburgerMenuIconItem.Tag>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="28"
FontWeight="Bold">
About
</TextBlock>
</mah:HamburgerMenuIconItem.Tag>
</mah:HamburgerMenuIconItem>
</mah:HamburgerMenuItemCollection>
</mah:HamburgerMenu.OptionsItemsSource>
<!-- Content -->
<mah:HamburgerMenu.ContentTemplate>
<DataTemplate>
<Grid x:Name="ContentGrid">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition />
</Grid.RowDefinitions>
<Border Grid.Row="0"
Margin="-1 0 -1 0"
Background="#7A7A7A">
<TextBlock x:Name="Header"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="24"
Foreground="White"
Text="{Binding Label}" />
</Border>
<mah:TransitioningContentControl Grid.Row="1"
Content="{Binding}"
RestartTransitionOnContentChange="True"
Transition="Default">
<mah:TransitioningContentControl.Resources>
<DataTemplate DataType="{x:Type mah:HamburgerMenuGlyphItem}">
<Image Source="{Binding Glyph, Mode=OneWay, Converter={mah:NullToUnsetValueConverter}}" />
</DataTemplate>
<DataTemplate DataType="{x:Type mah:HamburgerMenuIconItem}">
<ContentControl Content="{Binding Tag, Mode=OneWay}"
Focusable="True"
IsTabStop="False" />
</DataTemplate>
</mah:TransitioningContentControl.Resources>
</mah:TransitioningContentControl>
</Grid>
</DataTemplate>
</mah:HamburgerMenu.ContentTemplate>
</mah:HamburgerMenu>
</Grid>
</UserControl>
导航页面NavigationViewModel页面代码,如下所示:
namespace SIMS.NavigationModule.ViewModels
{
public class NavigationViewModel : BindableBase
{
#region 属性和构造函数
private IEventAggregator eventAggregator;
private List<HamburgerMenuItemBase> navItems;
public List<HamburgerMenuItemBase> NavItems { get { return navItems; } set { SetProperty(ref navItems, value); } }
public NavigationViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
navItems = new List<HamburgerMenuItemBase>();
navItems.Add(new HamburgerMenuHeaderItem() { Label = "学生管理"});
navItems.Add(new HamburgerMenuGlyphItem() { Label="学生管理",Tag="Student" ,Glyph="/images/icon_student.png" });
navItems.Add(new HamburgerMenuGlyphItem() { Label = "班级管理",Tag="Classes" , Glyph = "/images/icon_classes.png" });
navItems.Add(new HamburgerMenuGlyphItem() { Label = "课程管理", Tag="Course" , Glyph = "/images/icon_course.png" });
navItems.Add(new HamburgerMenuGlyphItem() { Label = "成绩管理" ,Tag="Score", Glyph = "/images/icon_score.png" });
navItems.Add(new HamburgerMenuHeaderItem() { Label = "系统管理", });
navItems.Add(new HamburgerMenuGlyphItem() { Label = "个人信息",Tag="Personal", Glyph = "/images/icon_personal.png" });
navItems.Add(new HamburgerMenuGlyphItem() { Label = "用户管理",Tag="User", Glyph = "/images/icon_user.png" });
navItems.Add(new HamburgerMenuGlyphItem() { Label = "角色管理",Tag="Role", Glyph = "/images/icon_role.png" });
}
#endregion
#region 命令
private DelegateCommand<object> navCommand;
public DelegateCommand<object> NavCommand
{
get
{
if (navCommand == null)
{
navCommand = new DelegateCommand<object>(Navigation);
}
return navCommand;
}
}
private void Navigation(object obj) {
var menuItem = (HamburgerMenuItem)obj;
if (menuItem != null) {
var tag = menuItem.Tag;
if (tag!=null) {
this.eventAggregator.GetEvent<NavEvent>().Publish(tag.ToString());
}
}
}
#endregion
}
}
示例截图
本示例目前主要实现了登录,及主页面布局,导航等功能,如下所示:
备注
以上是本文介绍的关于学生信息管理系统的主要内容,旨在抛砖引玉一起学习,共同进步。
贺新郎·九日【作者】刘克庄 【朝代】宋
湛湛长空黑。更那堪、斜风细雨,乱愁如织。老眼平生空四海,赖有高楼百尺。看浩荡、千崖秋色。白发书生神州泪,尽凄凉、不向牛山滴。追往事,去无迹。
少年自负凌云笔。到而今、春华落尽,满怀萧瑟。常恨世人新意少,爱说南朝狂客。把破帽、年年拈出。若对黄花孤负酒,怕黄花、也笑人岑寂。鸿北去,日西匿。