最近通过WPF开发项目,为了对WPF知识点进行总结,所以利用业余时间,开发一个学生信息管理系统【Student Information Management System】。前两篇文章进行了框架搭建和模块划分,以及后台WebApi接口编写,本文在前两篇基础之上,继续深入开发学生信息管理系统的课程管理模块,通过本篇文章,将了解如何进行数据的CRUD操作,将前后端贯穿起来开发。本文仅供学习分享使用,如有不足之处,还请指正。.
涉及知识点
在本篇文章中,虽然只介绍一个模块的开发,但是麻雀虽小,五脏俱全,而且掌握一个模块的开发方法,相当于掌握通用的开发方法,就可以举一反三,其他模块的开发也可以水到渠成。涉及到的知识点如下:
-
WPF开发中TextBlock,TextBox,DataGrid,Combox等控件的基础使用以及数据绑定等操作。
-
MAH样式的使用,在本示例中MAH主要用于统一页面风格,提高用户体验。
-
WebApi接口开发,本示例中,WebApi提供接口给客户端使用。
-
HttpClient使用,主要用于访问服务端提供的接口。
数据访问流程
一般情况下,交付给客户的都是可视化的操作页面,而不是一些只有专业开发人员才能看懂的WebApi接口。为了更好的描述数据访问流程,以具体代码模块为例,如下所示:

数据操作上下文DataContext
关于WebApi和数据库的相关操作,在上一篇文章中已有说明,本文不再赘述,主要侧重于业务代码。关于课程管理模块的数据操作上下文,有两点需要加以说明:
-
DbContext 是EntityFramwork提供的用于访问数据库中数据表的类,一个DbContext实例表示一个session,可用于查询或保存数据实体对应数据表。
-
DbSet表示一个对具体数据表中数据的操作映射,如增加,删除,修改,查询等,都是可通过DbSet类型完成。
关于DataContext具体代码,如下所示:
namespace SIMS.WebApi.Data{public class DataContext:DbContext{public DbSet<UserEntity> Users { get; set; }public DbSet<MenuEntity> Menus { get; set; }public DbSet<RoleEntity> Roles { get; set; }public DbSet<UserRoleEntity> UserRoles { get; set; }public DbSet<RoleMenuEntity> RoleMenus { get; set; }/// <summary>/// 学生/// </summary>public DbSet<StudentEntity> Students { get; set; }/// <summary>/// 班级/// </summary>public DbSet<ClassesEntity> Classes { get; set; }/// <summary>/// 课程/// </summary>public DbSet<CourseEntity> Courses { get; set; }/// <summary>/// 成绩/// </summary>public DbSet<ScoreEntity> Scores { get; set; }public DataContext(DbContextOptions options) : base(options){}protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);modelBuilder.Entity<UserEntity>().ToTable("Users");modelBuilder.Entity<MenuEntity>().ToTable("Menus");modelBuilder.Entity<StudentEntity>().ToTable("Students");modelBuilder.Entity<RoleEntity>().ToTable("Roles");modelBuilder.Entity<UserRoleEntity>().ToTable("UserRoles");modelBuilder.Entity<RoleMenuEntity>().ToTable("RoleMenus");}}}
数据服务Service
数据服务是对DataContext操作的封装,使得业务更加明确,当然如果通过控制器直接访问DataContext,省掉数据服务,也可以实现对应功能,只是加入数据服务层,使得结构更加清晰。
数据服务接口ICourseAppService,提供了五个接口,包括查询课程列表,查询当个课程信息,新增课程,修改课程,删除课程,具体如下所示:
namespace SIMS.WebApi.Services.Course{public interface ICourseAppService{/// <summary>/// 查询列表/// </summary>/// <param name="courseName"></param>/// <param name="teacher"></param>/// <param name="pageNum"></param>/// <param name="pageSize"></param>/// <returns></returns>public PagedRequest<CourseEntity> GetCourses(string courseName,string teacher, int pageNum,int pageSize);/// <summary>/// 通过id查询课程信息/// </summary>/// <param name="id"></param>/// <returns></returns>public CourseEntity GetCourse(int id);/// <summary>/// 新增课程/// </summary>/// <param name="course"></param>/// <returns></returns>public int AddCourse(CourseEntity course);/// <summary>/// 修改课程/// </summary>/// <param name="course"></param>/// <returns></returns>public int UpdateCourse(CourseEntity course);/// <summary>/// 删除课程/// </summary>/// <param name="id"></param>public int DeleteCourse(int id);}}
数据服务接口实现类CourseAppService,对数据服务接口ICourseAppService的实现,即通过操作DataContext来访问数据库中的课程表,如下所示:
namespace SIMS.WebApi.Services.Course{public class CourseAppService : ICourseAppService{private DataContext dataContext;public CourseAppService(DataContext dataContext){this.dataContext = dataContext;}public int AddCourse(CourseEntity course){var entry = dataContext.Courses.Add(course);dataContext.SaveChanges();return 0;}public int DeleteCourse(int id){var entity = dataContext.Courses.FirstOrDefault(x => x.Id == id);if (entity != null){dataContext.Courses.Remove(entity);dataContext.SaveChanges();}return 0;}/// <summary>/// 根据ID获取单个课程/// </summary>/// <param name="id"></param>/// <returns></returns>public CourseEntity GetCourse(int id){var entity = dataContext.Courses.FirstOrDefault(r => r.Id == id);return entity;}/// <summary>/// 获取课程列表/// </summary>/// <param name="courseName"></param>/// <param name="teacher"></param>/// <param name="pageNum"></param>/// <param name="pageSize"></param>/// <returns></returns>public PagedRequest<CourseEntity> GetCourses(string courseName, string teacher, int pageNum, int pageSize){IQueryable<CourseEntity> courses = null;if (!string.IsNullOrEmpty(courseName) && !string.IsNullOrEmpty(teacher)){courses = dataContext.Courses.Where(r => r.Name.Contains(courseName) && r.Teacher.Contains(teacher)).OrderBy(r => r.Id);}else if (!string.IsNullOrEmpty(courseName)){courses = dataContext.Courses.Where(r => r.Name.Contains(courseName)).OrderBy(r => r.Id);}else if (!string.IsNullOrEmpty(teacher)){courses = dataContext.Courses.Where(r => r.Teacher.Contains(teacher)).OrderBy(r => r.Id);}else{courses = dataContext.Courses.Where(r => true).OrderBy(r => r.Id);}int count = courses.Count();List<CourseEntity> items;if (pageSize > 0){items = courses.Skip((pageNum - 1) * pageSize).Take(pageSize).ToList();}else{items = courses.ToList();}return new PagedRequest<CourseEntity>(){count = count,items = items};}public int UpdateCourse(CourseEntity course){dataContext.Courses.Update(course);dataContext.SaveChanges();return 0;}}}
WebApi接口控制器
控制器是对数据服务的公开,每一个控制器的方法表示一个Action,即表示一个客户端可以访问的入口。具体如下所示:
namespace SIMS.WebApi.Controllers{/// <summary>/// 课程控制器/// </summary>[Route("api/[controller]/[action]")][ApiController]public class CourseController : ControllerBase{private readonly ILogger<CourseController> logger;private readonly ICourseAppService courseAppService;public CourseController(ILogger<CourseController> logger, ICourseAppService courseAppService){this.logger = logger;this.courseAppService = courseAppService;}[HttpGet]public PagedRequest<CourseEntity> GetCourses( int pageNum, int pageSize, string? courseName=null, string? teacher=null) {return courseAppService.GetCourses(courseName,teacher,pageNum,pageSize);}/// <summary>/// 获取课程信息/// </summary>/// <param name="id"></param>/// <returns></returns>[HttpGet]public CourseEntity GetCourse(int id){return courseAppService.GetCourse(id);}/// <summary>/// 新增课程/// </summary>/// <param name="course"></param>/// <returns></returns>[HttpPost]public int AddCourse(CourseEntity course){return courseAppService.AddCourse(course);}/// <summary>/// 修改课程/// </summary>/// <param name="course"></param>/// <returns></returns>[HttpPut]public int UpdateCourse(CourseEntity course){return courseAppService.UpdateCourse(course);}/// <summary>/// 删除课程/// </summary>/// <param name="id"></param>[HttpDelete]public int DeleteCourse(int id){return courseAppService.DeleteCourse(id);}}}
当服务运行起来后,Swagger还每一个控制器都进行归类,可以清晰的看到每一个接口对应的网址,课程管理模块对应的接口如下所示:
客户端接口访问类HttpUtil
在学生信息系统开发的过程中,发现所有的接口访问都是通用的,所以对接口访问功能提取成一个HttpUtil基类【包括GET,POST,PUT,DELETE等功能】,其他具体业务再继承基类,并细化具体业务即可。HttpUtil代码如下所示:
namespace SIMS.Utils.Http{/// <summary>/// HTTP访问基类/// </summary>public class HttpUtil{private static readonly string absoluteUrl = "https://localhost:7299/";/// <summary>/// get方式/// </summary>/// <param name="url"></param>/// <param name="param"></param>/// <returns></returns>public static string Get(string url, Dictionary<string, object> param){string p=string.Empty;if (param != null && param.Count() > 0) {foreach (var keypair in param){if (keypair.Value != null){p += $"{keypair.Key}={keypair.Value}&";}}}if (!string.IsNullOrEmpty(p)) {p=p.TrimEnd('&');}var httpClient = new HttpClient();var response = httpClient.GetAsync($"{absoluteUrl}{url}?{p}",HttpCompletionOption.ResponseContentRead).Result;string strJson = string.Empty;Stream stream = response.Content.ReadAsStream();using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)){strJson = reader.ReadToEnd();}return strJson;}/// <summary>/// POST方式/// </summary>/// <param name="url"></param>/// <param name="param"></param>/// <returns></returns>public static string Post<T>(string url, T t){var content = JsonConvert.SerializeObject(t);var httpContent = new StringContent(content,Encoding.UTF8,"application/json");var httpClient = new HttpClient();var response = httpClient.PostAsync($"{absoluteUrl}{url}", httpContent).Result;var respContent = response.Content.ReadAsStringAsync().Result;return respContent;}public static string Put<T>(string url, T t) {var content = JsonConvert.SerializeObject(t);var httpContent = new StringContent(content,Encoding.UTF8,"application/json");var httpClient = new HttpClient();var response = httpClient.PutAsync($"{absoluteUrl}{url}", httpContent).Result;var respContent = response.Content.ReadAsStringAsync().Result;return respContent;}public static string Delete(string url, Dictionary<string, string> param) {string p = string.Empty;if (param != null && param.Count() > 0){foreach (var keypair in param){p += $"{keypair.Key}={keypair.Value}&";}}if (!string.IsNullOrEmpty(p)){p = p.TrimEnd('&');}var httpClient = new HttpClient();var response = httpClient.DeleteAsync($"{absoluteUrl}{url}?{p}").Result;var respContent = response.Content.ReadAsStringAsync().Result;return respContent;}public static T StrToObject<T>(string str) {var t = JsonConvert.DeserializeObject<T>(str);return t;}}}
然后课课程接口访问通用类CourseHttpUtil继承了HttpUtil,以对应课程信息的操作。如下所示:
namespace SIMS.Utils.Http{public class CourseHttpUtil:HttpUtil{/// <summary>/// 通过id查询课程信息/// </summary>/// <param name="id"></param>/// <returns></returns>public static CourseEntity GetCourse(int id){Dictionary<string, object> data = new Dictionary<string, object>();data["id"] = id;var str = Get(UrlConfig.COURSE_GETCOURSE, data);var course = StrToObject<CourseEntity>(str);return course;}public static PagedRequest<CourseEntity> GetCourses(string? courseName, string? teacher, int pageNum, int pageSize){Dictionary<string, object> data = new Dictionary<string, object>();data["courseName"] = courseName;data["teacher"] = teacher;data["pageNum"] = pageNum;data["pageSize"] = pageSize;var str = Get(UrlConfig.COURSE_GETCOURSES, data);var courses = StrToObject<PagedRequest<CourseEntity>>(str);return courses;}public static bool AddCourse(CourseEntity course){var ret = Post<CourseEntity>(UrlConfig.COURSE_ADDCOURSE, course);return int.Parse(ret) == 0;}public static bool UpdateCourse(CourseEntity course){var ret = Put<CourseEntity>(UrlConfig.SCORE_UPDATESCORE, course);return int.Parse(ret) == 0;}public static bool DeleteCourse(int Id){Dictionary<string, string> data = new Dictionary<string, string>();data["Id"] = Id.ToString();var ret = Delete(UrlConfig.COURSE_DELETECOURSE, data);return int.Parse(ret) == 0;}}}
客户端模块CourseModule
经过前面四个部分的开发,客户端就可以与数据接口进行交互,展示数据到客户端。客户端所有的开发,均采用MVVM模式进行。
在课程管理模块中,根据功能区分,主要包含两个View视图及对应的ViewModel。如下所示:
-
Course视图,主要用于课程的查询,以及新增,修改,删除的链接入口。
-
AddEditCourse视图,主要用于课程信息的新增和修改,共用一个视图页面。
-
删除课程不需要页面,所以没有对应视图。
1. Course视图
Course视图,主要是课程的查询和新增,修改,删除的链接入口。涉及知识点如下:
-
Course视图页面布局采用Grid方式和StackPanel混合布局,即整体布局采用Grid,细微布局采用StackPanel。
-
课程采用分页列表的方式展示,需要用到DataGrid,及分页控件【WPF默认不提供分页控件,可自行编写分页控件】。
-
查询条件采用按钮Button和文本框TextBox等组成,关于基础控件的使用,不再详细论述,可参考其他文章。
-
在本系统的所有WPF视图中,均需要引入Prism和 MAH组件。
-
Course视图中,所有的数据均采用Binding的方式与ViewModel进行交互。
Course视图具体代码,如下所示:
<UserControl x:Class="SIMS.CourseModule.Views.Course"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.CourseModule.Views"xmlns:i="http://schemas.microsoft.com/xaml/behaviors"xmlns:prism="http://prismlibrary.com/"xmlns:mahApps="http://metro.mahapps.com/winfx/xaml/controls"prism:ViewModelLocator.AutoWireViewModel="True"xmlns:ctrls ="clr-namespace:SIMS.Utils.Controls;assembly=SIMS.Utils"mc:Ignorable="d"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 CourseName}"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 Teacher}"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="dgClasses"Grid.Row="2"Grid.Column="0"Margin="2"AutoGenerateColumns="False"CanUserAddRows="False"CanUserDeleteRows="False"ItemsSource="{Binding Courses}"RowHeaderWidth="0"><DataGrid.Columns><DataGridTextColumn Binding="{Binding Name}" Header="课程名称" Width="*" /><DataGridTextColumn Binding="{Binding Teacher}" 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>
2. CourseViewModel
CourseViewModel是页面视图的业务逻辑处理,如处理客户端的点击的命令等内容。主要分为三部分:
2.1 数据绑定
数据绑定,如查询条件的文本框内容的绑定,课程查询列表的绑定。其中数据访问采用CourseHttpUtil工具类,如下所示:
#region 属性及构造函数/// <summary>/// 专业/// </summary>private string courseName;public string CourseName{get { return courseName; }set { SetProperty(ref courseName, value); }}/// <summary>/// 年级/// </summary>private string teacher;public string Teacher{get { return teacher; }set { SetProperty(ref teacher, value); }}private ObservableCollection<CourseEntity> courses;public ObservableCollection<CourseEntity> Courses{get { return courses; }set { SetProperty(ref courses, value); }}private IDialogService dialogService;public CourseViewModel(IDialogService dialogService){this.dialogService = dialogService;this.pageNum = 1;this.pageSize = 20;}private void InitInfo(){Courses = new ObservableCollection<CourseEntity>();var pagedRequst = CourseHttpUtil.GetCourses(this.CourseName, this.Teacher, this.pageNum, this.pageSize);var entities = pagedRequst.items;Courses.AddRange(entities);//this.TotalCount = pagedRequst.count;this.TotalPage = ((int)Math.Ceiling(this.TotalCount * 1.0 / this.pageSize));}#endregion
2.2 命令绑定
在课程页面,查询按钮,编辑按钮,删除按钮,均与ViewModel中的命令进行绑定,如下所示:
#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("addEditCourse", 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 course = this.Courses.FirstOrDefault(r => r.Id == Id);if (course == null){MessageBox.Show("无效的课程ID");return;}IDialogParameters dialogParameters = new DialogParameters();dialogParameters.Add("course", course);this.dialogService.ShowDialog("addEditCourse", 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 course = this.Courses.FirstOrDefault(r => r.Id == Id);if (course == null){MessageBox.Show("无效的班级ID");return;}if (MessageBoxResult.Yes != MessageBox.Show("Are you sure to delete?", "Confirm", MessageBoxButton.YesNo)) {return;}bool flag = CourseHttpUtil.DeleteCourse(Id);if (flag){this.pageNum = 1;this.InitInfo();}}#endregion
2.3 分页命令与数据绑定
关于分页功能,是通用的功能,几乎每一个查询页面的分页都大同小异,可以进行复制粘贴,快速实现功能。如下所示:
#region 分页/// <summary>/// 当前页码/// </summary>private int pageNum;public int PageNum{get { return pageNum; }set { SetProperty(ref pageNum, value); }}/// <summary>/// 每页显示多少条记录/// </summary>private int pageSize;public int PageSize{get { return pageSize; }set { SetProperty(ref pageSize, value); }}/// <summary>///总条数/// </summary>private int totalCount;public int TotalCount{get { return totalCount; }set { SetProperty(ref totalCount, value); }}/// <summary>///总页数/// </summary>private int totalPage;public int TotalPage{get { return totalPage; }set { SetProperty(ref totalPage, value); }}/// <summary>/// 跳转页/// </summary>private int jumpNum;public int JumpNum{get { return jumpNum; }set { SetProperty(ref jumpNum, value); }}/// <summary>/// 首页命令/// </summary>private DelegateCommand firstPageCommand;public DelegateCommand FirstPageCommand{get{if (firstPageCommand == null){firstPageCommand = new DelegateCommand(FirstPage);}return firstPageCommand;}}private void FirstPage(){this.PageNum = 1;this.InitInfo();}/// <summary>/// 跳转页命令/// </summary>private DelegateCommand jumpPageCommand;public DelegateCommand JumpPageCommand{get{if (jumpPageCommand == null){jumpPageCommand = new DelegateCommand(JumpPage);}return jumpPageCommand;}}private void JumpPage(){if (jumpNum < 1){MessageBox.Show("请输入跳转页");return;}if (jumpNum > this.totalPage){MessageBox.Show($"跳转页面必须在[1,{this.totalPage}]之间,请确认。");return;}this.PageNum = jumpNum;this.InitInfo();}/// <summary>/// 前一页/// </summary>private DelegateCommand prevPageCommand;public DelegateCommand PrevPageCommand{get{if (prevPageCommand == null){prevPageCommand = new DelegateCommand(PrevPage);}return prevPageCommand;}}private void PrevPage(){this.PageNum--;if (this.PageNum < 1){this.PageNum = 1;}this.InitInfo();}/// <summary>/// 下一页命令/// </summary>private DelegateCommand nextPageCommand;public DelegateCommand NextPageCommand{get{if (nextPageCommand == null){nextPageCommand = new DelegateCommand(NextPage);}return nextPageCommand;}}private void NextPage(){this.PageNum++;if (this.PageNum > this.TotalPage){this.PageNum = this.TotalPage;}this.InitInfo();}#endregion
3. 新增编辑课程视图AddEditCourse
新增编辑课程视图,主要用于对课程的修改和新增,可通过查询页面的新增按钮和具体课程的编辑按钮弹出对应窗口。如下所示:
<UserControl x:Class="SIMS.CourseModule.Views.AddEditCourse"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.CourseModule.Views"mc:Ignorable="d"xmlns:i="http://schemas.microsoft.com/xaml/behaviors"xmlns:mahApps ="http://metro.mahapps.com/winfx/xaml/controls"xmlns:prism="http://prismlibrary.com/"d:DesignHeight="400" d:DesignWidth="600"><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></Grid.RowDefinitions><TextBlock Text="课程名称" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Margin="3"></TextBlock><TextBox Grid.Row="0" Grid.Column="2" MinWidth="120" Height="35" VerticalAlignment="Center" Margin="3" Text="{Binding Course.Name}"></TextBox><TextBlock Text="授课老师" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Margin="3"></TextBlock><TextBox Grid.Row="1" Grid.Column="2" MinWidth="120" Height="35" VerticalAlignment="Center" Margin="3" Text="{Binding Course.Teacher}"></TextBox><StackPanel Grid.Row="2" 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. 新增编辑课程ViewModel
AddEditCourseViewModel是对页面具体功能的业务封装,主要是对应课程信息的保存,也包括数据绑定和命令绑定等内容,如下所示:
namespace SIMS.CourseModule.ViewModels{public class AddEditCourseViewModel:BindableBase,IDialogAware{#region 属性和构造函数/// <summary>/// 班级实体/// </summary>private CourseEntity course;public CourseEntity Course{get { return course; }set { SetProperty(ref course, value); }}public AddEditCourseViewModel(){}#endregion#region Commandprivate DelegateCommand loadedCommand;public DelegateCommand LoadedCommand{get{if (loadedCommand == null){loadedCommand = new DelegateCommand(Loaded);}return loadedCommand;}}private void Loaded(){}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 (Course != null){Course.CreateTime = DateTime.Now;Course.LastEditTime = DateTime.Now;bool flag = false;if (Course.Id > 0){flag = CourseHttpUtil.UpdateCourse(Course);}else{flag = CourseHttpUtil.AddCourse(Course);}if (flag){RequestClose?.Invoke((new DialogResult(ButtonResult.OK)));}}}#endregion#region 对话框public string Title => "新增或编辑课程信息";public event Action<IDialogResult> RequestClose;public bool CanCloseDialog(){return true;}public void OnDialogClosed(){}public void OnDialogOpened(IDialogParameters parameters){if (parameters != null && parameters.ContainsKey("course")){this.Course = parameters.GetValue<CourseEntity>("course");}else{this.Course = new CourseEntity();}}#endregion}}
注意:因为新增编辑页面是弹出窗口,所以在Prism框架中,需要实现IDialogAware接口。
5. 控件注册
当页面功能开发完成后,再通过Prism进行注册,就可以通过导航栏和弹出窗口进行展示,如下所示:
namespace SIMS.CourseModule{public class CourseModule : IModule{public void OnInitialized(IContainerProvider containerProvider){}public void RegisterTypes(IContainerRegistry containerRegistry){containerRegistry.RegisterForNavigation<Course, CourseViewModel>(nameof(Course));containerRegistry.RegisterDialog<AddEditCourse, AddEditCourseViewModel>("addEditCourse");}}}
示例效果图
经过上述步骤后,课程管理模块就开发完成,运行VS后,效果如下所示:

总结
经过上述步骤,不难看出,开发一个完整的功能,涉及到前端,后端,接口访问,数据库等相关内容,麻雀虽小,五脏俱全。其实开发一个课程管理模块,就是对数据库中课程表的增删改查,也可以把所有代码都糅合在一起,简化开发流程和步骤,这样代码量将大幅度减少。但是分层,分模块并不是为了使项目复杂化,而为了更加清晰以及便于维护与扩展,也是本篇文章希望为大家传递的一种开发理念。