WPF开发学生信息管理系统【WPF+Prism+MAH+WebApi】(完)

最近通过WPF开发项目,为了对WPF知识点进行总结,所以利用业余时间,开发一个学生信息管理系统【Student Information Management System】。前四篇文章进行了框架搭建和模块划分,后台WebApi接口编写,以及课程管理模块,班级管理模块,学生管理模块的开发,本文在前四篇基础之上,继续深入开发学生信息管理系统的成绩管理和系统管理模块,通过本篇文章,将继续巩固之前的知识点,本文仅供学习分享使用,如有不足之处,还请指正。.

注意:源码下载在文章底部哦。

涉及知识点

学生信息管理系统SIMS属于一个小型的完整系统开发,涉及的知识点比较,具体如下所示:

  1. WPF开发中TextBlock,TextBox,DataGrid,Combox,TabControl等控件的基础使用以及数据绑定等操作。

  2. MAH样式的使用,在本示例中MAH主要用于统一页面风格,提高用户体验。

  3. HttpClient使用,主要用于访问服务端提供的接口。

业务逻辑

前面几篇文章,由浅入深,逐步介绍了课程管理模块,班级管理模块,学生管理模块,今天继续介绍成绩管理模块,业务逻辑关系如下:

  1. 学生属于某一班级之学生,所以学生中包含班级信息。

  2. 班级中存在班长,同时班长又属于学生的一个实体。

  3. 成绩是某一学生的成绩,且一名学生有各门课程的成绩。所以成绩和学生有关,且和课程有关。

实体E-R图

学生表,成绩表,班级表,课程表,各个数据表之间的E-R图,如下所示:

WPF开发学生信息管理系统【WPF+Prism+MAH+WebApi】(完)

由此可见,成绩表与课程和学生表,都有关联,所以放在最后。

成绩管理

成绩管理主要用于录入各个学生各个课程的成绩,包含成绩表的增删改查功能。

1. 成绩管理后台服务Service

IScoreAppService接口是对成绩管理的抽象,如下所示:

namespace SIMS.WebApi.Services.Score{    public interface IScoreAppService    {        public PagedRequest<ScoreEntity> GetScores(string studentName,string courseName,int pageNum,int pageSize);
        /// <summary>        /// 通过id查询成绩信息        /// </summary>        /// <param name="id"></param>        /// <returns></returns>        public ScoreEntity GetScore(int id);
        /// <summary>        /// 新增成绩        /// </summary>        /// <param name="score"></param>        /// <returns></returns>        public int AddScore(ScoreEntity score);
        /// <summary>        /// 修改成绩        /// </summary>        /// <param name="score"></param>        /// <returns></returns>        public int UpdateScore(ScoreEntity score);
        /// <summary>        /// 删除成绩        /// </summary>        /// <param name="id"></param>        public int DeleteScore(int id);    }}

服务实现类ScoreAppService,是对接口的实现,具体如下所示:

namespace SIMS.WebApi.Services.Score{    public class ScoreAppService : IScoreAppService    {        private DataContext dataContext;
        public ScoreAppService(DataContext dataContext)        {            this.dataContext = dataContext;        }
        public int AddScore(ScoreEntity score)        {            var entity = this.dataContext.Scores.Add(score);            this.dataContext.SaveChanges();            return 0;        }
        public int DeleteScore(int id)        {            var entity = dataContext.Scores.FirstOrDefault(x => x.Id == id);            if (entity != null)            {                dataContext.Scores.Remove(entity);                dataContext.SaveChanges();            }            return 0;        }
        public ScoreEntity GetScore(int id)        {            var entity = dataContext.Scores.FirstOrDefault(r => r.Id == id);            return entity;        }
        /// <summary>        /// 按条件查询成绩列表        /// </summary>        /// <param name="studentName"></param>        /// <param name="courseName"></param>        /// <param name="pageNum"></param>        /// <param name="pageSize"></param>        /// <returns></returns>        public PagedRequest<ScoreEntity> GetScores(string studentName, string courseName, int pageNum, int pageSize)        {            IQueryable<ScoreEntity> scores = null;            if (!string.IsNullOrEmpty(studentName) && !string.IsNullOrEmpty(courseName))            {                var students = this.dataContext.Students.Where(r => r.Name.Contains(studentName));                var courses = this.dataContext.Courses.Where(r => r.Name.Contains(courseName));                scores = this.dataContext.Scores.Where(r => students.Select(t => t.Id).Contains(r.StudentId)).Where(r => courses.Select(t => t.Id).Contains(r.CourseId));            }            else if (!string.IsNullOrEmpty(studentName))            {                var students = this.dataContext.Students.Where(r => r.Name.Contains(studentName));                scores = this.dataContext.Scores.Where(r => students.Select(t => t.Id).Contains(r.StudentId));            }            else if (!string.IsNullOrEmpty(courseName))            {                var courses = this.dataContext.Courses.Where(r => r.Name.Contains(courseName));                scores = this.dataContext.Scores.Where(r => courses.Select(t => t.Id).Contains(r.CourseId));            }            else {                scores = dataContext.Scores.Where(r => true).OrderBy(r => r.Id);            }            int count = scores.Count();            List<ScoreEntity> items;            if (pageSize > 0)            {                items = scores.Skip((pageNum - 1) * pageSize).Take(pageSize).ToList();            }            else            {                items = scores.ToList();            }            return new PagedRequest<ScoreEntity>()            {                count = count,                items = items            };        }
        public int UpdateScore(ScoreEntity score)        {            dataContext.Scores.Update(score);            dataContext.SaveChanges();            return 0;        }    }}

2. 成绩管理WebApi接口控制器

控制器是对数据服务的公开,每一个控制器的方法表示一个Action,即表示一个客户端可以访问的入口。具体如下所示:

namespace SIMS.WebApi.Controllers{    /// <summary>    /// 成绩控制器    /// </summary>    [Route("api/[controller]/[action]")]    [ApiController]    public class ScoreController : ControllerBase    {        private readonly ILogger<ScoreController> logger;
        private readonly IScoreAppService scoreAppService;
        public ScoreController(ILogger<ScoreController> logger, IScoreAppService scoreAppService)        {            this.logger = logger;            this.scoreAppService = scoreAppService;        }
        /// <summary>        /// 获取成绩信息        /// </summary>        /// <param name="id"></param>        /// <returns></returns>        [HttpGet]        public PagedRequest<ScoreEntity> GetScores(string? studentName, string? courseName, int pageNum, int pageSize)        {            return scoreAppService.GetScores(studentName, courseName, pageNum, pageSize);        }
        /// <summary>        /// 获取成绩信息        /// </summary>        /// <param name="id"></param>        /// <returns></returns>        [HttpGet]        public ScoreEntity GetScore(int id)        {            return scoreAppService.GetScore(id);        }
        /// <summary>        /// 新增成绩        /// </summary>        /// <param name="score"></param>        /// <returns></returns>        [HttpPost]        public int AddScore(ScoreEntity score)        {            return scoreAppService.AddScore(score);        }
        /// <summary>        /// 修改成绩        /// </summary>        /// <param name="score"></param>        /// <returns></returns>        [HttpPut]        public int UpdateScore(ScoreEntity score)        {            return scoreAppService.UpdateScore(score);        }
        /// <summary>        /// 删除成绩        /// </summary>        /// <param name="id"></param>        [HttpDelete]        public int DeleteScore(int id)        {            return scoreAppService.DeleteScore(id);        }    }}

当服务运行起来后,Swagger还每一个控制器都进行归类,可以清晰的看到每一个接口对应的网址,成绩管理模块对应的接口如下所示:

3. 成绩管理客户端接口访问类HttpUtil

在学生信息系统开发的过程中,发现所有的接口访问都是通用的,所以对接口访问功能提取成一个HttpUtil基类【包括GET,POST,PUT,DELETE等功能】,其他具体业务再继承基类,并细化具体业务即可。ScoreHttpUtil代码如下所示:

namespace SIMS.Utils.Http{    public class ScoreHttpUtil:HttpUtil    {        /// <summary>        /// 通过id查询成绩信息        /// </summary>        /// <param name="id"></param>        /// <returns></returns>        public static ScoreEntity GetScore(int id)        {            Dictionary<string, object> data = new Dictionary<string, object>();            data["id"] = id;            var str = Get(UrlConfig.SCORE_GETSCORE, data);            var socre = StrToObject<ScoreEntity>(str);            return socre;        }
        /// <summary>        ///        /// </summary>        /// <param name="studentName"></param>        /// <param name="courseName"></param>        /// <param name="pageNum"></param>        /// <param name="pageSize"></param>        /// <returns></returns>        public static PagedRequest<ScoreEntity> GetScores(string? studentName, string? courseName, int pageNum, int pageSize)        {            Dictionary<string, object> data = new Dictionary<string, object>();            data["courseName"] = courseName;            data["studentName"] = studentName;            data["pageNum"] = pageNum;            data["pageSize"] = pageSize;            var str = Get(UrlConfig.SCORE_GETSCORES, data);            var socres = StrToObject<PagedRequest<ScoreEntity>>(str);            return socres;        }
        public static bool AddScore(ScoreEntity socre)        {            var ret = Post<ScoreEntity>(UrlConfig.SCORE_ADDSCORE, socre);            return int.Parse(ret) == 0;        }
        public static bool UpdateScore(ScoreEntity socre)        {            var ret = Put<ScoreEntity>(UrlConfig.SCORE_UPDATESCORE, socre);            return int.Parse(ret) == 0;        }
        public static bool DeleteScore(int Id)        {            Dictionary<string, string> data = new Dictionary<string, string>();            data["Id"] = Id.ToString();            var ret = Delete(UrlConfig.SCORE_DELETESCORE, data);            return int.Parse(ret) == 0;        }    }}

4. 成绩管理客户端操作

经过前面四个部分的开发,客户端就可以与数据接口进行交互,展示数据到客户端。客户端所有的开发,均采用MVVM模式进行。

在成绩管理模块中,根据功能区分,主要包含两个View视图及对应的ViewModel。如下所示:

  1. Score视图,主要用于成绩的查询,以及新增,修改,删除的链接入口。

  2. AddEditScore视图,主要用于成绩信息的新增和修改,共用一个视图页面。

  3. 删除成绩不需要页面,所以没有对应视图。

4.1. Score视图

Score视图,主要是成绩的查询和新增,修改,删除的链接入口。涉及知识点如下:

  1. Score视图页面布局采用Grid方式和StackPanel混合布局,即整体布局采用Grid,细微布局采用StackPanel。

  2. 成绩采用分页列表的方式展示,需要用到DataGrid,及分页控件【WPF默认不提供分页控件,可自行编写分页控件】。

  3. 查询条件采用按钮Button和文本框TextBox等组成,关于基础控件的使用,不再详细论述,可参考其他文章。

  4. 在本系统的所有WPF视图中,均需要引入Prism和 MAH组件。

  5. Score视图中,所有的数据均采用Binding的方式与ViewModel进行交互。

Score视图具体代码,如下所示:

<UserControl x:Class="SIMS.ScoreModule.Views.Score"             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:i="http://schemas.microsoft.com/xaml/behaviors"             xmlns:prism="http://prismlibrary.com/"             xmlns:local="clr-namespace:SIMS.ScoreModule.Views"             mc:Ignorable="d"             xmlns:mahApps="http://metro.mahapps.com/winfx/xaml/controls"             xmlns:ctrls ="clr-namespace:SIMS.Utils.Controls;assembly=SIMS.Utils"             prism:ViewModelLocator.AutoWireViewModel="True"             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>        <ResourceDictionary>            <ResourceDictionary.MergedDictionaries>                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />                <ResourceDictionary>                    <Style x:Key="LinkButton" TargetType="Button">                        <Setter Property="Background" Value="White"></Setter>                        <Setter Property="Cursor" Value="Hand"></Setter>                        <Setter Property="Margin" Value="3"></Setter>                        <Setter Property="MinWidth" Value="80"></Setter>                        <Setter Property="MinHeight" Value="25"></Setter>                        <Setter Property="BorderThickness" Value="0 0 0 0"></Setter></Style>                </ResourceDictionary>            </ResourceDictionary.MergedDictionaries>        </ResourceDictionary>    </UserControl.Resources>    <i:Interaction.Triggers>        <i:EventTrigger EventName="Loaded">            <i:InvokeCommandAction Command="{Binding LoadedCommand}"></i:InvokeCommandAction>        </i:EventTrigger>    </i:Interaction.Triggers>    <Grid>        <Grid.RowDefinitions>            <RowDefinition Height="Auto"></RowDefinition>            <RowDefinition Height="Auto"></RowDefinition>            <RowDefinition Height="*"></RowDefinition>            <RowDefinition Height="Auto"></RowDefinition>        </Grid.RowDefinitions>        <TextBlock Text="成绩信息" FontSize="20" Background="AliceBlue" Margin="2"></TextBlock>        <StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Center">            <TextBlock Text="学生名称" VerticalAlignment="Center" Margin="2"></TextBlock>            <TextBox Margin="4" MinWidth="120" Height="30"                     Text="{Binding StudentName}"                             HorizontalContentAlignment="Stretch"                             mahApps:TextBoxHelper.ClearTextButton="True"                             mahApps:TextBoxHelper.Watermark="学生名称"                             mahApps:TextBoxHelper.WatermarkAlignment="Left"                             SpellCheck.IsEnabled="True" />            <TextBlock Text="课程名称" VerticalAlignment="Center" Margin="2"></TextBlock>            <TextBox Margin="4" MinWidth="120" Height="30"                     Text="{Binding CourseName}"                             HorizontalContentAlignment="Stretch"                             mahApps:TextBoxHelper.ClearTextButton="True"                             mahApps:TextBoxHelper.Watermark="课程名称"                             mahApps:TextBoxHelper.WatermarkAlignment="Left"                             SpellCheck.IsEnabled="True" />            <Button Content="查询" Style="{DynamicResource MahApps.Styles.Button.Square.Accent}" Width="120" Height="30" Margin="3" Command="{Binding QueryCommand}"></Button>            <Button Content="新增" Style="{DynamicResource MahApps.Styles.Button.Square.Accent}" Width="120" Height="30" Margin="3" Command="{Binding AddCommand}"></Button>        </StackPanel>        <DataGrid x:Name="dgScores"                  Grid.Row="2"                  Grid.Column="0"                  Margin="2"                  AutoGenerateColumns="False"                  CanUserAddRows="False"                  CanUserDeleteRows="False"                  ItemsSource="{Binding Scores}"                  RowHeaderWidth="0">            <DataGrid.Columns>                <DataGridTextColumn Binding="{Binding Student.Name}" Header="学生" Width="*" />                <DataGridTextColumn Binding="{Binding Course.Name}" Header="课程" Width="*"/>                <DataGridTextColumn Binding="{Binding Score}" Header="成绩" Width="*"/>                <DataGridTextColumn Binding="{Binding CreateTime, StringFormat=yyyy-MM-dd HH:mm:ss}" Header="创建时间" Width="*"/>                <DataGridTextColumn Binding="{Binding LastEditTime,StringFormat=yyyy-MM-dd HH:mm:ss}" Header="最后修改时间" Width="*"/>                <DataGridTemplateColumn Header="操作" Width="*">                    <DataGridTemplateColumn.CellTemplate>                        <DataTemplate>                            <StackPanel Orientation="Horizontal">                                <Button  Content="Edit" Style="{StaticResource LinkButton}" Command="{Binding RelativeSource={RelativeSource  AncestorType=DataGrid,  Mode=FindAncestor}, Path=DataContext.EditCommand}" CommandParameter="{Binding Id}">                                    <Button.Template>                                        <ControlTemplate TargetType="Button">                                            <TextBlock TextDecorations="Underline" HorizontalAlignment="Center">                                                <ContentPresenter />                                            </TextBlock>                                        </ControlTemplate>                                    </Button.Template>                                </Button>                                <Button Content="Delete" Style="{StaticResource LinkButton}" Command="{Binding RelativeSource={RelativeSource  AncestorType=DataGrid,  Mode=FindAncestor}, Path=DataContext.DeleteCommand}" CommandParameter="{Binding Id}">                                    <Button.Template>                                        <ControlTemplate TargetType="Button">                                            <TextBlock TextDecorations="Underline" HorizontalAlignment="Center">                                                <ContentPresenter />                                            </TextBlock>                                        </ControlTemplate>                                    </Button.Template>                                </Button>                            </StackPanel>                        </DataTemplate>                    </DataGridTemplateColumn.CellTemplate>                </DataGridTemplateColumn>            </DataGrid.Columns>        </DataGrid>        <ctrls:PageControl Grid.Row="3" DataContext="{Binding}" ></ctrls:PageControl>    </Grid></UserControl>

4.2. ScoreViewModel

ScoreViewModel是页面视图的业务逻辑处理,如处理客户端的点击的命令等内容。具体代码如下所示:

namespace SIMS.ScoreModule.ViewModels{    public class ScoreViewModel : BindableBase    {
        #region 属性或构造方法
        /// <summary>        /// 课程名称        /// </summary>        private string courseName;
        public string CourseName        {            get { return courseName; }            set { SetProperty(ref courseName, value); }        }
        /// <summary>        /// 学生姓名        /// </summary>        private string studentName;
        public string StudentName        {            get { return studentName; }            set { SetProperty(ref studentName, value); }        }
        private ObservableCollection<ScoreInfo> scores;
        public ObservableCollection<ScoreInfo> Scores        {            get { return scores; }            set { SetProperty(ref scores, value); }        }
        private IDialogService dialogService;
        public ScoreViewModel(IDialogService dialogService)        {            this.dialogService = dialogService;            this.pageNum = 1;            this.pageSize = 20;        }
        private void InitInfo()        {            Scores = new ObservableCollection<ScoreInfo>();            var pagedRequst = ScoreHttpUtil.GetScores(this.StudentName, this.CourseName, this.pageNum, this.pageSize);            var entities = pagedRequst.items;            Scores.AddRange(entities.Select(r=>new ScoreInfo(r)));            //            this.TotalCount = pagedRequst.count;            this.TotalPage = ((int)Math.Ceiling(this.TotalCount * 1.0 / this.pageSize));        }
        #endregion
        #region 事件
        private DelegateCommand loadedCommand;
        public DelegateCommand LoadedCommand        {            get            {                if (loadedCommand == null)                {                    loadedCommand = new DelegateCommand(Loaded);                }                return loadedCommand;            }        }
        private void Loaded()        {            InitInfo();        }
        private DelegateCommand queryCommand;
        public DelegateCommand QueryCommand        {            get            {                if (queryCommand == null)                {                    queryCommand = new DelegateCommand(Query);                }                return queryCommand;            }        }
        private void Query()        {            this.pageNum = 1;            this.InitInfo();        }
        /// <summary>        /// 新增命令        /// </summary>        private DelegateCommand addCommand;
        public DelegateCommand AddCommand        {            get            {                if (addCommand == null)                {                    addCommand = new DelegateCommand(Add);                }                return addCommand;            }        }
        private void Add()        {            this.dialogService.ShowDialog("addEditScore", null, AddEditCallBack, "MetroDialogWindow");        }
        private void AddEditCallBack(IDialogResult dialogResult)        {            if (dialogResult != null && dialogResult.Result == ButtonResult.OK)            {                //刷新列表                this.pageNum = 1;                this.InitInfo();            }        }
        /// <summary>        /// 编辑命令        /// </summary>        private DelegateCommand<object> editCommand;
        public DelegateCommand<object> EditCommand        {            get            {                if (editCommand == null)                {                    editCommand = new DelegateCommand<object>(Edit);                }                return editCommand;            }        }
        private void Edit(object obj)        {            if (obj == null)            {                return;            }            var Id = int.Parse(obj.ToString());            var score = this.Scores.FirstOrDefault(r => r.Id == Id);            if (score == null)            {                MessageBox.Show("无效的成绩ID");                return;            }            IDialogParameters dialogParameters = new DialogParameters();            dialogParameters.Add("score", score);            this.dialogService.ShowDialog("addEditScore", dialogParameters, AddEditCallBack, "MetroDialogWindow");        }
        /// <summary>        /// 编辑命令        /// </summary>        private DelegateCommand<object> deleteCommand;
        public DelegateCommand<object> DeleteCommand        {            get            {                if (deleteCommand == null)                {                    deleteCommand = new DelegateCommand<object>(Delete);                }                return deleteCommand;            }        }
        private void Delete(object obj)        {            if (obj == null)            {                return;            }            var Id = int.Parse(obj.ToString());            var score = this.Scores.FirstOrDefault(r => r.Id == Id);            if (score == null)            {                MessageBox.Show("无效的成绩ID");                return;            }            if (MessageBoxResult.Yes != MessageBox.Show("Are you sure to delete?", "Confirm", MessageBoxButton.YesNo))            {                return;            }            bool flag = ScoreHttpUtil.DeleteScore(Id);            if (flag)            {                this.pageNum = 1;                this.InitInfo();            }        }
        #endregion    }}

注意:关于分页功能,与其他模块代码通用,所以此处略去。

4. 3. 新增编辑成绩视图AddEditScore

新增编辑成绩视图,主要用于对成绩的修改和新增,可通过查询页面的新增按钮和具体成绩的编辑按钮弹出对应窗口。如下所示:

<UserControl x:Class="SIMS.ScoreModule.Views.AddEditScore"             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.ScoreModule.Views"             xmlns:i="http://schemas.microsoft.com/xaml/behaviors"             xmlns:mahApps ="http://metro.mahapps.com/winfx/xaml/controls"             xmlns:prism="http://prismlibrary.com/"             mc:Ignorable="d"             d:DesignHeight="450" d:DesignWidth="800">    <prism:Dialog.WindowStyle>        <Style TargetType="Window">            <Setter Property="Width" Value="600"></Setter>            <Setter Property="Height" Value="400"></Setter></Style>    </prism:Dialog.WindowStyle>    <UserControl.Resources>        <ResourceDictionary>            <ResourceDictionary.MergedDictionaries>                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />            </ResourceDictionary.MergedDictionaries>        </ResourceDictionary>    </UserControl.Resources>    <i:Interaction.Triggers>        <i:EventTrigger EventName="Loaded">            <i:InvokeCommandAction Command="{Binding LoadedCommand}"></i:InvokeCommandAction>        </i:EventTrigger>    </i:Interaction.Triggers>    <Grid>        <Grid.ColumnDefinitions>            <ColumnDefinition Width="0.2*"></ColumnDefinition>            <ColumnDefinition Width="Auto"></ColumnDefinition>            <ColumnDefinition Width="*"></ColumnDefinition>            <ColumnDefinition Width="0.2*"></ColumnDefinition>        </Grid.ColumnDefinitions>        <Grid.RowDefinitions>            <RowDefinition></RowDefinition>            <RowDefinition></RowDefinition>            <RowDefinition></RowDefinition>            <RowDefinition></RowDefinition>        </Grid.RowDefinitions>        <TextBlock Text="学生" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Margin="3"></TextBlock>        <ComboBox Grid.Row="0" Grid.Column="2" MinWidth="120" Height="35" ItemsSource="{Binding Students}" mahApps:TextBoxHelper.ClearTextButton="True" SelectedItem="{Binding Student}">            <ComboBox.ItemTemplate>                <DataTemplate>                    <TextBlock Text="{Binding Name}"></TextBlock>                </DataTemplate>            </ComboBox.ItemTemplate>        </ComboBox>        <TextBlock Text="课程" Grid.Row="1" Grid.Column="1"  VerticalAlignment="Center" Margin="3"></TextBlock>        <ComboBox Grid.Row="1" Grid.Column="2" MinWidth="120" Height="35" ItemsSource="{Binding Courses}" mahApps:TextBoxHelper.ClearTextButton="True" SelectedItem="{Binding Course}">            <ComboBox.ItemTemplate>                <DataTemplate>                    <TextBlock Text="{Binding Name}"></TextBlock>                </DataTemplate>            </ComboBox.ItemTemplate>        </ComboBox>        <TextBlock Text="成绩" Grid.Row="2" Grid.Column="1"   VerticalAlignment="Center" Margin="3"></TextBlock>        <TextBox Grid.Row="2" Grid.Column="2" MinWidth="120" Height="35"   VerticalAlignment="Center" Margin="3" Text="{Binding Score.Score}"></TextBox>        <StackPanel Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="3">            <Button Content="取消" Margin="5" MinWidth="120" Height="35" Style="{DynamicResource MahApps.Styles.Button.Square.Accent}" Command="{Binding CancelCommand}"></Button>            <Button Content="保存" Margin="5" MinWidth="120" Height="35" Style="{DynamicResource MahApps.Styles.Button.Square.Accent}" Command="{Binding SaveCommand}"></Button>        </StackPanel>    </Grid></UserControl>

4.4. 新增编辑成绩ViewModel

AddEditScoreViewModel是对页面具体功能的业务封装,主要是对应成绩信息的保存,也包括数据绑定和命令绑定等内容,与其他模块不同之处,在于此模块关联学生和课程信息,需要绑定下拉框数据源。具体如下所示:

namespace SIMS.ScoreModule.ViewModels{    public class AddEditScoreViewModel : BindableBase, IDialogAware    {
        #region 属性和构造函数
        /// <summary>        /// 当前实体        /// </summary>        private ScoreEntity score;
        public ScoreEntity Score        {            get { return score; }            set { SetProperty(ref score , value); }        }
        /// <summary>        /// 下拉框选择的学生        /// </summary>        private StudentEntity student;
        public StudentEntity Student        {            get { return student; }            set { SetProperty(ref student , value); }        }
        /// <summary>        /// 学生列表        /// </summary>        private List<StudentEntity> students;
        public List<StudentEntity> Students        {            get { return students; }            set { SetProperty(ref students, value); }        }
        private CourseEntity course;
        public CourseEntity Course        {            get { return course; }            set {SetProperty(ref course , value); }        }
        /// <summary>        /// 课程列表        /// </summary>        private List<CourseEntity> courses;
        public List<CourseEntity> Courses        {            get { return courses; }            set { SetProperty(ref courses, value); }        }
        public AddEditScoreViewModel() {
        }
        #endregion
        #region Command
        private DelegateCommand loadedCommand;
        public DelegateCommand LoadedCommand        {            get            {                if (loadedCommand == null)                {                    loadedCommand = new DelegateCommand(Loaded);                }                return loadedCommand;            }        }
        private void Loaded()        {            LoadStudents();            LoadCourses();
            //如果有班长,则为班长赋值            if (Score.StudentId > 0)            {                this.Student = this.Students?.FirstOrDefault(r => r.Id == Score.StudentId);            }            if (Score.CourseId > 0) {                this.Course = this.Courses?.FirstOrDefault(r=>r.Id == Score.CourseId);            }        }
        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)));        }
        private DelegateCommand saveCommand;
        public DelegateCommand SaveCommand        {            get            {                if (saveCommand == null)                {                    saveCommand = new DelegateCommand(Save);                }                return saveCommand;            }        }
        private void Save()        {            if (Score != null)            {                Score.CreateTime = DateTime.Now;                Score.LastEditTime = DateTime.Now;                if (Student != null)                {                    Score.StudentId = Student.Id;                }                if (Course != null) {                    Score.CourseId = Course.Id;                }                bool flag = false;                if (Score.Id > 0)                {                    flag = ScoreHttpUtil.UpdateScore(Score);                }                else                {                    flag = ScoreHttpUtil.AddScore(Score);                }                if (flag)                {                    RequestClose?.Invoke((new DialogResult(ButtonResult.OK)));                }            }        }
        #endregion
        #region 函数
        /// <summary>        /// 加载学生列表        /// </summary>        private void LoadStudents() {            this.Students = new List<StudentEntity>();            var pagedRequst = StudentHttpUtil.GetStudents(null, null, 1, -1);            var entities = pagedRequst.items;            Students.AddRange(entities);        }
        /// <summary>        /// 加载课程列表        /// </summary>        private void LoadCourses() {            this.Courses = new List<CourseEntity>();            var pagedRequst = CourseHttpUtil.GetCourses(null, null, 1, -1);            var entities = pagedRequst.items;            Courses.AddRange(entities);        }
        #endregion    }}

注意:弹出窗口实现方法与其他模块通用,所以此处略去。

5. 成绩管理示例效果图

经过上述步骤后,成绩管理模块就开发完成,运行VS后,效果如下所示:

WPF开发学生信息管理系统【WPF+Prism+MAH+WebApi】(完)

系统管理模块

1. 系统管理模块核心代码

系统管理模块,主要包含四个部分,用户管理,角色管理,菜单管理,个人信息。因篇幅有限,暂时仅列出主要内容:

从数据库读取用户所属的权限,代码如下所示:​​​​​​​

public List<UserRight> GetUserRights(int? userId){    if (userId != null)    {        var query = from u in dataContext.UserRoles                    join r in dataContext.Roles on u.RoleId equals r.Id                    join x in dataContext.RoleMenus on r.Id equals x.RoleId                    join m in dataContext.Menus on x.MenuId equals m.Id                    where u.UserId == userId                    select new UserRight { Id = m.Id, RoleName = r.Name, MenuName = m.Name, Url = m.Url,Icon=m.Icon, ParentId = m.ParentId, SortId = m.SortId };
        return query.ToList();    }    return null;}

在客户端获取后,转换成导航菜单对象即可,如下所示:

​​​​​
public NavigationViewModel(IEventAggregator eventAggregator){    this.eventAggregator = eventAggregator;    navItems = new List<HamburgerMenuItemBase>();    var userRights = RoleHttpUtil.GetUserRights(UserInfo.Instance.Id);    var parents = userRights.Where(x => x.ParentId == null).OrderBy(r=>r.SortId);    foreach (var parent in parents) {        navItems.Add(new HamburgerMenuHeaderItem() { Label = parent.MenuName });        var subItems = userRights.Where(r=>r.ParentId==parent.Id).OrderBy(r=>r.SortId);        foreach (var subItem in subItems) {            navItems.Add(new HamburgerMenuGlyphItem() { Label = subItem.MenuName, Tag = subItem.Url, Glyph = subItem.Icon });        }    }    UserInfo.Instance.Roles = String.Join(',', userRights.Select(r=>r.RoleName).Distinct().ToList());}

2. 系统管理模块示例截图

关于系统管理模块示例截图如下所示:

个人信息,显示个人基础信息,如下所示:

WPF开发学生信息管理系统【WPF+Prism+MAH+WebApi】(完)

 用户管理,比其他列表多了一个授权按钮,主要用于为用户分配角色如下所示:

角色管理模块,比其他列表多了一个分配按钮,主要用于为分配角色对应的菜单如下所示:

菜单管理,菜单管理模块,主要用于管理菜单信息,与其他模块不同的是,需要配置图标,如下所示:

WPF开发学生信息管理系统【WPF+Prism+MAH+WebApi】(完)​​​​​

总结

通过本篇文章的成绩管理模块,系统管理模块,以及前两篇文章中的课程管理模块,班级管理模块,学生管理模块,不难发现,每一个模块的开发都是由列表DataGrid,文本框TextBox,下拉框Combox,单选按钮RadioButton,按钮Button等组成的,虽功能略有差异,但总归万变不离其宗。开发方法也大同小异,复杂的功能都是普通的功能累加起来的。这也是本系列文章由浅入深的渐进安排。希望能够抛砖引玉,不局限于某一功能,而是能够举一反三,自我理解,以达到自我开发的能力。

至此,整个学生信息管理系统系列已完毕。

关于源码

关于源码下载,可点击CSDN上的链接 或者关注个人公众号【老码识途】进行下载,如下所示:

WPF开发学生信息管理系统【WPF+Prism+MAH+WebApi】(完)WPF开发学生信息管理系统【WPF+Prism+MAH+WebApi】(完)

学习编程,从关注【老码识途】开始!!!