WPF开发学生信息管理系统【WPF+Prism+MAH+WebApi】(三)
最近通过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具体代码,如下所示:
1 namespace SIMS.WebApi.Data 2 { 3 public class DataContext:DbContext 4 { 5 public DbSet<UserEntity> Users { get; set; } 6 7 public DbSet<MenuEntity> Menus { get; set; } 8 9 public DbSet<RoleEntity> Roles { get; set; } 10 11 public DbSet<UserRoleEntity> UserRoles { get; set; } 12 13 public DbSet<RoleMenuEntity> RoleMenus { get; set; } 14 15 /// <summary> 16 /// 学生 17 /// </summary> 18 public DbSet<StudentEntity> Students { get; set; } 19 20 /// <summary> 21 /// 班级 22 /// </summary> 23 public DbSet<ClassesEntity> Classes { get; set; } 24 25 /// <summary> 26 /// 课程 27 /// </summary> 28 public DbSet<CourseEntity> Courses { get; set; } 29 30 /// <summary> 31 /// 成绩 32 /// </summary> 33 public DbSet<ScoreEntity> Scores { get; set; } 34 35 public DataContext(DbContextOptions options) : base(options) 36 { 37 38 } 39 40 protected override void OnModelCreating(ModelBuilder modelBuilder) 41 { 42 base.OnModelCreating(modelBuilder); 43 modelBuilder.Entity<UserEntity>().ToTable("Users"); 44 modelBuilder.Entity<MenuEntity>().ToTable("Menus"); 45 modelBuilder.Entity<StudentEntity>().ToTable("Students"); 46 modelBuilder.Entity<RoleEntity>().ToTable("Roles"); 47 modelBuilder.Entity<UserRoleEntity>().ToTable("UserRoles"); 48 modelBuilder.Entity<RoleMenuEntity>().ToTable("RoleMenus"); 49 } 50 } 51 }
数据服务Service
数据服务是对DataContext操作的封装,使得业务更加明确,当然如果通过控制器直接访问DataContext,省掉数据服务,也可以实现对应功能,只是加入数据服务层,使得结构更加清晰。
数据服务接口ICourseAppService,提供了五个接口,包括查询课程列表,查询当个课程信息,新增课程,修改课程,删除课程,具体如下所示:
1 namespace SIMS.WebApi.Services.Course 2 { 3 public interface ICourseAppService 4 { 5 /// <summary> 6 /// 查询列表 7 /// </summary> 8 /// <param name="courseName"></param> 9 /// <param name="teacher"></param> 10 /// <param name="pageNum"></param> 11 /// <param name="pageSize"></param> 12 /// <returns></returns> 13 public PagedRequest<CourseEntity> GetCourses(string courseName,string teacher, int pageNum,int pageSize); 14 15 /// <summary> 16 /// 通过id查询课程信息 17 /// </summary> 18 /// <param name="id"></param> 19 /// <returns></returns> 20 public CourseEntity GetCourse(int id); 21 22 /// <summary> 23 /// 新增课程 24 /// </summary> 25 /// <param name="course"></param> 26 /// <returns></returns> 27 public int AddCourse(CourseEntity course); 28 29 /// <summary> 30 /// 修改课程 31 /// </summary> 32 /// <param name="course"></param> 33 /// <returns></returns> 34 public int UpdateCourse(CourseEntity course); 35 36 /// <summary> 37 /// 删除课程 38 /// </summary> 39 /// <param name="id"></param> 40 public int DeleteCourse(int id); 41 } 42 }
数据服务接口实现类CourseAppService,对数据服务接口ICourseAppService的实现,即通过操作DataContext来访问数据库中的课程表,如下所示:
1 namespace SIMS.WebApi.Services.Course 2 { 3 public class CourseAppService : ICourseAppService 4 { 5 private DataContext dataContext; 6 7 public CourseAppService(DataContext dataContext) 8 { 9 this.dataContext = dataContext; 10 } 11 12 public int AddCourse(CourseEntity course) 13 { 14 var entry = dataContext.Courses.Add(course); 15 dataContext.SaveChanges(); 16 return 0; 17 } 18 19 public int DeleteCourse(int id) 20 { 21 var entity = dataContext.Courses.FirstOrDefault(x => x.Id == id); 22 if (entity != null) 23 { 24 dataContext.Courses.Remove(entity); 25 dataContext.SaveChanges(); 26 } 27 return 0; 28 } 29 30 /// <summary> 31 /// 根据ID获取单个课程 32 /// </summary> 33 /// <param name="id"></param> 34 /// <returns></returns> 35 public CourseEntity GetCourse(int id) 36 { 37 var entity = dataContext.Courses.FirstOrDefault(r => r.Id == id); 38 return entity; 39 } 40 41 /// <summary> 42 /// 获取课程列表 43 /// </summary> 44 /// <param name="courseName"></param> 45 /// <param name="teacher"></param> 46 /// <param name="pageNum"></param> 47 /// <param name="pageSize"></param> 48 /// <returns></returns> 49 public PagedRequest<CourseEntity> GetCourses(string courseName, string teacher, int pageNum, int pageSize) 50 { 51 IQueryable<CourseEntity> courses = null; 52 if (!string.IsNullOrEmpty(courseName) && !string.IsNullOrEmpty(teacher)) 53 { 54 courses = dataContext.Courses.Where(r => r.Name.Contains(courseName) && r.Teacher.Contains(teacher)).OrderBy(r => r.Id); 55 } 56 else if (!string.IsNullOrEmpty(courseName)) 57 { 58 courses = dataContext.Courses.Where(r => r.Name.Contains(courseName)).OrderBy(r => r.Id); 59 } 60 else if (!string.IsNullOrEmpty(teacher)) 61 { 62 courses = dataContext.Courses.Where(r => r.Teacher.Contains(teacher)).OrderBy(r => r.Id); 63 } 64 else 65 { 66 courses = dataContext.Courses.Where(r => true).OrderBy(r => r.Id); 67 } 68 int count = courses.Count(); 69 List<CourseEntity> items; 70 if (pageSize > 0) 71 { 72 items = courses.Skip((pageNum - 1) * pageSize).Take(pageSize).ToList(); 73 } 74 else 75 { 76 items = courses.ToList(); 77 } 78 return new PagedRequest<CourseEntity>() 79 { 80 count = count, 81 items = items 82 }; 83 } 84 85 public int UpdateCourse(CourseEntity course) 86 { 87 dataContext.Courses.Update(course); 88 dataContext.SaveChanges(); 89 return 0; 90 } 91 } 92 }
WebApi接口控制器
控制器是对数据服务的公开,每一个控制器的方法表示一个Action,即表示一个客户端可以访问的入口。具体如下所示:
1 namespace SIMS.WebApi.Controllers 2 { 3 /// <summary> 4 /// 课程控制器 5 /// </summary> 6 [Route("api/[controller]/[action]")] 7 [ApiController] 8 public class CourseController : ControllerBase 9 { 10 private readonly ILogger<CourseController> logger; 11 12 private readonly ICourseAppService courseAppService; 13 14 public CourseController(ILogger<CourseController> logger, ICourseAppService courseAppService) 15 { 16 this.logger = logger; 17 this.courseAppService = courseAppService; 18 } 19 20 [HttpGet] 21 public PagedRequest<CourseEntity> GetCourses( int pageNum, int pageSize, string? courseName=null, string? teacher=null) { 22 return courseAppService.GetCourses(courseName,teacher,pageNum,pageSize); 23 } 24 25 /// <summary> 26 /// 获取课程信息 27 /// </summary> 28 /// <param name="id"></param> 29 /// <returns></returns> 30 [HttpGet] 31 public CourseEntity GetCourse(int id) 32 { 33 return courseAppService.GetCourse(id); 34 } 35 36 /// <summary> 37 /// 新增课程 38 /// </summary> 39 /// <param name="course"></param> 40 /// <returns></returns> 41 [HttpPost] 42 public int AddCourse(CourseEntity course) 43 { 44 return courseAppService.AddCourse(course); 45 } 46 47 /// <summary> 48 /// 修改课程 49 /// </summary> 50 /// <param name="course"></param> 51 /// <returns></returns> 52 [HttpPut] 53 public int UpdateCourse(CourseEntity course) 54 { 55 return courseAppService.UpdateCourse(course); 56 } 57 58 /// <summary> 59 /// 删除课程 60 /// </summary> 61 /// <param name="id"></param> 62 [HttpDelete] 63 public int DeleteCourse(int id) 64 { 65 return courseAppService.DeleteCourse(id); 66 } 67 } 68 }
当服务运行起来后,Swagger还每一个控制器都进行归类,可以清晰的看到每一个接口对应的网址,课程管理模块对应的接口如下所示:
客户端接口访问类HttpUtil
在学生信息系统开发的过程中,发现所有的接口访问都是通用的,所以对接口访问功能提取成一个HttpUtil基类【包括GET,POST,PUT,DELETE等功能】,其他具体业务再继承基类,并细化具体业务即可。HttpUtil代码如下所示:
1 namespace SIMS.Utils.Http 2 { 3 /// <summary> 4 /// HTTP访问基类 5 /// </summary> 6 public class HttpUtil 7 { 8 private static readonly string absoluteUrl = "https://localhost:7299/"; 9 10 /// <summary> 11 /// get方式 12 /// </summary> 13 /// <param name="url"></param> 14 /// <param name="param"></param> 15 /// <returns></returns> 16 public static string Get(string url, Dictionary<string, object> param) 17 { 18 string p=string.Empty; 19 if (param != null && param.Count() > 0) { 20 foreach (var keypair in param) 21 { 22 if (keypair.Value != null) 23 { 24 p += $"{keypair.Key}={keypair.Value}&"; 25 } 26 } 27 } 28 if (!string.IsNullOrEmpty(p)) { 29 p=p.TrimEnd('&'); 30 } 31 var httpClient = new HttpClient(); 32 var response = httpClient.GetAsync($"{absoluteUrl}{url}?{p}",HttpCompletionOption.ResponseContentRead).Result; 33 string strJson = string.Empty; 34 Stream stream = response.Content.ReadAsStream(); 35 using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) 36 { 37 strJson = reader.ReadToEnd(); 38 } 39 return strJson; 40 } 41 42 /// <summary> 43 /// POST方式 44 /// </summary> 45 /// <param name="url"></param> 46 /// <param name="param"></param> 47 /// <returns></returns> 48 public static string Post<T>(string url, T t) 49 { 50 var content = JsonConvert.SerializeObject(t); 51 52 var httpContent = new StringContent(content,Encoding.UTF8,"application/json"); 53 var httpClient = new HttpClient(); 54 var response = httpClient.PostAsync($"{absoluteUrl}{url}", httpContent).Result; 55 56 var respContent = response.Content.ReadAsStringAsync().Result; 57 return respContent; 58 } 59 60 public static string Put<T>(string url, T t) { 61 var content = JsonConvert.SerializeObject(t); 62 63 var httpContent = new StringContent(content,Encoding.UTF8,"application/json"); 64 var httpClient = new HttpClient(); 65 var response = httpClient.PutAsync($"{absoluteUrl}{url}", httpContent).Result; 66 67 var respContent = response.Content.ReadAsStringAsync().Result; 68 return respContent; 69 } 70 71 public static string Delete(string url, Dictionary<string, string> param) { 72 string p = string.Empty; 73 if (param != null && param.Count() > 0) 74 { 75 foreach (var keypair in param) 76 { 77 p += $"{keypair.Key}={keypair.Value}&"; 78 } 79 } 80 if (!string.IsNullOrEmpty(p)) 81 { 82 p = p.TrimEnd('&'); 83 } 84 var httpClient = new HttpClient(); 85 var response = httpClient.DeleteAsync($"{absoluteUrl}{url}?{p}").Result; 86 var respContent = response.Content.ReadAsStringAsync().Result; 87 return respContent; 88 } 89 90 91 public static T StrToObject<T>(string str) { 92 var t = JsonConvert.DeserializeObject<T>(str); 93 return t; 94 } 95 } 96 }
然后课课程接口访问通用类CourseHttpUtil继承了HttpUtil,以对应课程信息的操作。如下所示:
1 namespace SIMS.Utils.Http 2 { 3 public class CourseHttpUtil:HttpUtil 4 { 5 /// <summary> 6 /// 通过id查询课程信息 7 /// </summary> 8 /// <param name="id"></param> 9 /// <returns></returns> 10 public static CourseEntity GetCourse(int id) 11 { 12 Dictionary<string, object> data = new Dictionary<string, object>(); 13 data["id"] = id; 14 var str = Get(UrlConfig.COURSE_GETCOURSE, data); 15 var course = StrToObject<CourseEntity>(str); 16 return course; 17 } 18 19 public static PagedRequest<CourseEntity> GetCourses(string? courseName, string? teacher, int pageNum, int pageSize) 20 { 21 Dictionary<string, object> data = new Dictionary<string, object>(); 22 data["courseName"] = courseName; 23 data["teacher"] = teacher; 24 data["pageNum"] = pageNum; 25 data["pageSize"] = pageSize; 26 var str = Get(UrlConfig.COURSE_GETCOURSES, data); 27 var courses = StrToObject<PagedRequest<CourseEntity>>(str); 28 return courses; 29 } 30 31 public static bool AddCourse(CourseEntity course) 32 { 33 var ret = Post<CourseEntity>(UrlConfig.COURSE_ADDCOURSE, course); 34 return int.Parse(ret) == 0; 35 } 36 37 public static bool UpdateCourse(CourseEntity course) 38 { 39 var ret = Put<CourseEntity>(UrlConfig.SCORE_UPDATESCORE, course); 40 return int.Parse(ret) == 0; 41 } 42 43 public static bool DeleteCourse(int Id) 44 { 45 Dictionary<string, string> data = new Dictionary<string, string>(); 46 data["Id"] = Id.ToString(); 47 var ret = Delete(UrlConfig.COURSE_DELETECOURSE, data); 48 return int.Parse(ret) == 0; 49 } 50 } 51 }
客户端操作
经过前面四个部分的开发,客户端就可以与数据接口进行交互,展示数据到客户端。客户端所有的开发,均采用MVVM模式进行。
在课程管理模块中,根据功能区分,主要包含两个View视图及对应的ViewModel。如下所示:
- Course视图,主要用于课程的查询,以及新增,修改,删除的链接入口。
- AddEditCourse视图,主要用于课程信息的新增和修改,共用一个视图页面。
- 删除课程不需要页面,所以没有对应视图。
1. Course视图
Course视图,主要是课程的查询和新增,修改,删除的链接入口。涉及知识点如下:
- Course视图页面布局采用Grid方式和StackPanel混合布局,即整体布局采用Grid,细微布局采用StackPanel。
- 课程采用分页列表的方式展示,需要用到DataGrid,及分页控件【WPF默认不提供分页控件,可自行编写分页控件】。
- 查询条件采用按钮Button和文本框TextBox等组成,关于基础控件的使用,不再详细论述,可参考其他文章。
- 在本系统的所有WPF视图中,均需要引入Prism和 MAH组件。
- Course视图中,所有的数据均采用Binding的方式与ViewModel进行交互。
Course视图具体代码,如下所示:
1 <UserControl x:Class="SIMS.CourseModule.Views.Course" 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.CourseModule.Views" 7 xmlns:i="http://schemas.microsoft.com/xaml/behaviors" 8 xmlns:prism="http://prismlibrary.com/" 9 xmlns:mahApps="http://metro.mahapps.com/winfx/xaml/controls" 10 prism:ViewModelLocator.AutoWireViewModel="True" 11 xmlns:ctrls ="clr-namespace:SIMS.Utils.Controls;assembly=SIMS.Utils" 12 mc:Ignorable="d" 13 d:DesignHeight="450" d:DesignWidth="800"> 14 <UserControl.Resources> 15 <ResourceDictionary> 16 <ResourceDictionary.MergedDictionaries> 17 <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" /> 18 <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" /> 19 <ResourceDictionary> 20 <Style x:Key="LinkButton" TargetType="Button"> 21 <Setter Property="Background" Value="White"></Setter> 22 <Setter Property="Cursor" Value="Hand"></Setter> 23 <Setter Property="Margin" Value="3"></Setter> 24 <Setter Property="MinWidth" Value="80"></Setter> 25 <Setter Property="MinHeight" Value="25"></Setter> 26 <Setter Property="BorderThickness" Value="0 0 0 0"></Setter> 27 </Style> 28 </ResourceDictionary> 29 </ResourceDictionary.MergedDictionaries> 30 </ResourceDictionary> 31 </UserControl.Resources> 32 <i:Interaction.Triggers> 33 <i:EventTrigger EventName="Loaded"> 34 <i:InvokeCommandAction Command="{Binding LoadedCommand}"></i:InvokeCommandAction> 35 </i:EventTrigger> 36 </i:Interaction.Triggers> 37 <Grid> 38 <Grid.RowDefinitions> 39 <RowDefinition Height="Auto"></RowDefinition> 40 <RowDefinition Height="Auto"></RowDefinition> 41 <RowDefinition Height="*"></RowDefinition> 42 <RowDefinition Height="Auto"></RowDefinition> 43 </Grid.RowDefinitions> 44 <TextBlock Text="课程信息" FontSize="20" Background="AliceBlue" Margin="2"></TextBlock> 45 <StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Center"> 46 <TextBlock Text="课程名称" VerticalAlignment="Center" Margin="2"></TextBlock> 47 <TextBox Margin="4" MinWidth="120" Height="30" 48 Text="{Binding CourseName}" 49 HorizontalContentAlignment="Stretch" 50 mahApps:TextBoxHelper.ClearTextButton="True" 51 mahApps:TextBoxHelper.Watermark="课程名称" 52 mahApps:TextBoxHelper.WatermarkAlignment="Left" 53 SpellCheck.IsEnabled="True" /> 54 <TextBlock Text="老师" VerticalAlignment="Center" Margin="2"></TextBlock> 55 <TextBox Margin="4" MinWidth="120" Height="30" 56 Text="{Binding Teacher}" 57 HorizontalContentAlignment="Stretch" 58 mahApps:TextBoxHelper.ClearTextButton="True" 59 mahApps:TextBoxHelper.Watermark="老师" 60 mahApps:TextBoxHelper.WatermarkAlignment="Left" 61 SpellCheck.IsEnabled="True" /> 62 <Button Content="查询" Style="{DynamicResource MahApps.Styles.Button.Square.Accent}" Width="120" Height="30" Margin="3" Command="{Binding QueryCommand}"></Button> 63 <Button Content="新增" Style="{DynamicResource MahApps.Styles.Button.Square.Accent}" Width="120" Height="30" Margin="3" Command="{Binding AddCommand}"></Button> 64 </StackPanel> 65 <DataGrid x:Name="dgClasses" 66 Grid.Row="2" 67 Grid.Column="0" 68 Margin="2" 69 AutoGenerateColumns="False" 70 CanUserAddRows="False" 71 CanUserDeleteRows="False" 72 ItemsSource="{Binding Courses}" 73 RowHeaderWidth="0"> 74 <DataGrid.Columns> 75 <DataGridTextColumn Binding="{Binding Name}" Header="课程名称" Width="*" /> 76 <DataGridTextColumn Binding="{Binding Teacher}" Header="老师" Width="*"/> 77 <DataGridTextColumn Binding="{Binding CreateTime, StringFormat=yyyy-MM-dd HH:mm:ss}" Header="创建时间" Width="*"/> 78 <DataGridTextColumn Binding="{Binding LastEditTime,StringFormat=yyyy-MM-dd HH:mm:ss}" Header="最后修改时间" Width="*"/> 79 <DataGridTemplateColumn Header="操作" Width="*"> 80 <DataGridTemplateColumn.CellTemplate> 81 <DataTemplate> 82 <StackPanel Orientation="Horizontal"> 83 <Button Content="Edit" Style="{StaticResource LinkButton}" Command="{Binding RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}, Path=DataContext.EditCommand}" CommandParameter="{Binding Id}"> 84 <Button.Template> 85 <ControlTemplate TargetType="Button"> 86 <TextBlock TextDecorations="Underline" HorizontalAlignment="Center"> 87 <ContentPresenter /> 88 </TextBlock> 89 </ControlTemplate> 90 </Button.Template> 91 </Button> 92 <Button Content="Delete" Style="{StaticResource LinkButton}" Command="{Binding RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}, Path=DataContext.DeleteCommand}" CommandParameter="{Binding Id}"> 93 <Button.Template> 94 <ControlTemplate TargetType="Button"> 95 <TextBlock TextDecorations="Underline" HorizontalAlignment="Center"> 96 <ContentPresenter /> 97 </TextBlock> 98 </ControlTemplate> 99 </Button.Template> 100 </Button> 101 </StackPanel> 102 </DataTemplate> 103 </DataGridTemplateColumn.CellTemplate> 104 </DataGridTemplateColumn> 105 </DataGrid.Columns> 106 </DataGrid> 107 <ctrls:PageControl Grid.Row="3" DataContext="{Binding}" ></ctrls:PageControl> 108 </Grid> 109 </UserControl>
2. CourseViewModel
CourseViewModel是页面视图的业务逻辑处理,如处理客户端的点击的命令等内容。主要分为三部分:
数据绑定
数据绑定,如查询条件的文本框内容的绑定,课程查询列表的绑定。其中数据访问采用CourseHttpUtil工具类,如下所示:
1 #region 属性及构造函数 2 3 /// <summary> 4 /// 专业 5 /// </summary> 6 private string courseName; 7 8 public string CourseName 9 { 10 get { return courseName; } 11 set { SetProperty(ref courseName, value); } 12 } 13 14 /// <summary> 15 /// 年级 16 /// </summary> 17 private string teacher; 18 19 public string Teacher 20 { 21 get { return teacher; } 22 set { SetProperty(ref teacher, value); } 23 } 24 25 26 27 private ObservableCollection<CourseEntity> courses; 28 29 public ObservableCollection<CourseEntity> Courses 30 { 31 get { return courses; } 32 set { SetProperty(ref courses, value); } 33 } 34 35 private IDialogService dialogService; 36 37 public CourseViewModel(IDialogService dialogService) 38 { 39 this.dialogService = dialogService; 40 this.pageNum = 1; 41 this.pageSize = 20; 42 } 43 44 private void InitInfo() 45 { 46 Courses = new ObservableCollection<CourseEntity>(); 47 var pagedRequst = CourseHttpUtil.GetCourses(this.CourseName, this.Teacher, this.pageNum, this.pageSize); 48 var entities = pagedRequst.items; 49 Courses.AddRange(entities); 50 // 51 this.TotalCount = pagedRequst.count; 52 this.TotalPage = ((int)Math.Ceiling(this.TotalCount * 1.0 / this.pageSize)); 53 } 54 55 #endregion
命令绑定
在课程页面,查询按钮,编辑按钮,删除按钮,均与ViewModel中的命令进行绑定,如下所示:
1 #region 事件 2 3 private DelegateCommand loadedCommand; 4 5 public DelegateCommand LoadedCommand 6 { 7 get 8 { 9 if (loadedCommand == null) 10 { 11 loadedCommand = new DelegateCommand(Loaded); 12 } 13 return loadedCommand; 14 } 15 } 16 17 private void Loaded() 18 { 19 InitInfo(); 20 } 21 22 private DelegateCommand queryCommand; 23 24 public DelegateCommand QueryCommand 25 { 26 get 27 { 28 if (queryCommand == null) 29 { 30 queryCommand = new DelegateCommand(Query); 31 } 32 return queryCommand; 33 } 34 } 35 36 private void Query() 37 { 38 this.pageNum = 1; 39 this.InitInfo(); 40 } 41 42 /// <summary> 43 /// 新增命令 44 /// </summary> 45 private DelegateCommand addCommand; 46 47 public DelegateCommand AddCommand 48 { 49 get 50 { 51 if (addCommand == null) 52 { 53 addCommand = new DelegateCommand(Add); 54 } 55 return addCommand; 56 } 57 } 58 59 private void Add() 60 { 61 this.dialogService.ShowDialog("addEditCourse", null, AddEditCallBack, "MetroDialogWindow"); 62 } 63 64 private void AddEditCallBack(IDialogResult dialogResult) 65 { 66 if (dialogResult != null && dialogResult.Result == ButtonResult.OK) 67 { 68 //刷新列表 69 this.pageNum = 1; 70 this.InitInfo(); 71 } 72 } 73 74 /// <summary> 75 /// 编辑命令 76 /// </summary> 77 private DelegateCommand<object> editCommand; 78 79 public DelegateCommand<object> EditCommand 80 { 81 get 82 { 83 if (editCommand == null) 84 { 85 editCommand = new DelegateCommand<object>(Edit); 86 } 87 return editCommand; 88 } 89 } 90 91 private void Edit(object obj) 92 { 93 if (obj == null) 94 { 95 return; 96 } 97 var Id = int.Parse(obj.ToString()); 98 var course = this.Courses.FirstOrDefault(r => r.Id == Id); 99 if (course == null) 100 { 101 MessageBox.Show("无效的课程ID"); 102 return; 103 } 104 IDialogParameters dialogParameters = new DialogParameters(); 105 dialogParameters.Add("course", course); 106 this.dialogService.ShowDialog("addEditCourse", dialogParameters, AddEditCallBack, "MetroDialogWindow"); 107 } 108 109 /// <summary> 110 /// 编辑命令 111 /// </summary> 112 private DelegateCommand<object> deleteCommand; 113 114 public DelegateCommand<object> DeleteCommand 115 { 116 get 117 { 118 if (deleteCommand == null) 119 { 120 deleteCommand = new DelegateCommand<object>(Delete); 121 } 122 return deleteCommand; 123 } 124 } 125 126 private void Delete(object obj) 127 { 128 if (obj == null) 129 { 130 return; 131 } 132 var Id = int.Parse(obj.ToString()); 133 var course = this.Courses.FirstOrDefault(r => r.Id == Id); 134 if (course == null) 135 { 136 MessageBox.Show("无效的班级ID"); 137 return; 138 } 139 if (MessageBoxResult.Yes != MessageBox.Show("Are you sure to delete?", "Confirm", MessageBoxButton.YesNo)) { 140 return; 141 } 142 bool flag = CourseHttpUtil.DeleteCourse(Id); 143 if (flag) 144 { 145 this.pageNum = 1; 146 this.InitInfo(); 147 } 148 } 149 150 #endregion
分页命令与数据绑定
关于分页功能,是通用的功能,几乎每一个查询页面的分页都大同小异,可以进行复制粘贴,快速实现功能。如下所示:
1 #region 分页 2 3 /// <summary> 4 /// 当前页码 5 /// </summary> 6 private int pageNum; 7 8 public int PageNum 9 { 10 get { return pageNum; } 11 set { SetProperty(ref pageNum, value); } 12 } 13 14 /// <summary> 15 /// 每页显示多少条记录 16 /// </summary> 17 private int pageSize; 18 19 public int PageSize 20 { 21 get { return pageSize; } 22 set { SetProperty(ref pageSize, value); } 23 } 24 25 /// <summary> 26 ///总条数 27 /// </summary> 28 private int totalCount; 29 30 public int TotalCount 31 { 32 get { return totalCount; } 33 set { SetProperty(ref totalCount, value); } 34 } 35 36 /// <summary> 37 ///总页数 38 /// </summary> 39 private int totalPage; 40 41 public int TotalPage 42 { 43 get { return totalPage; } 44 set { SetProperty(ref totalPage, value); } 45 } 46 47 48 /// <summary> 49 /// 跳转页 50 /// </summary> 51 private int jumpNum; 52 53 public int JumpNum 54 { 55 get { return jumpNum; } 56 set { SetProperty(ref jumpNum, value); } 57 } 58 59 /// <summary> 60 /// 首页命令 61 /// </summary> 62 private DelegateCommand firstPageCommand; 63 64 public DelegateCommand FirstPageCommand 65 { 66 get 67 { 68 if (firstPageCommand == null) 69 { 70 firstPageCommand = new DelegateCommand(FirstPage); 71 } 72 return firstPageCommand; 73 } 74 75 } 76 77 private void FirstPage() 78 { 79 this.PageNum = 1; 80 this.InitInfo(); 81 } 82 83 /// <summary> 84 /// 跳转页命令 85 /// </summary> 86 private DelegateCommand jumpPageCommand; 87 88 public DelegateCommand JumpPageCommand 89 { 90 get 91 { 92 if (jumpPageCommand == null) 93 { 94 jumpPageCommand = new DelegateCommand(JumpPage); 95 } 96 return jumpPageCommand; 97 } 98 } 99 100 private void JumpPage() 101 { 102 if (jumpNum < 1) 103 { 104 MessageBox.Show("请输入跳转页"); 105 return; 106 } 107 if (jumpNum > this.totalPage) 108 { 109 MessageBox.Show($"跳转页面必须在[1,{this.totalPage}]之间,请确认。"); 110 return; 111 } 112 this.PageNum = jumpNum; 113 114 this.InitInfo(); 115 } 116 117 /// <summary> 118 /// 前一页 119 /// </summary> 120 private DelegateCommand prevPageCommand; 121 122 public DelegateCommand PrevPageCommand 123 { 124 get 125 { 126 if (prevPageCommand == null) 127 { 128 prevPageCommand = new DelegateCommand(PrevPage); 129 } 130 return prevPageCommand; 131 } 132 } 133 134 private void PrevPage() 135 { 136 this.PageNum--; 137 if (this.PageNum < 1) 138 { 139 this.PageNum = 1; 140 } 141 this.InitInfo(); 142 } 143 144 /// <summary> 145 /// 下一页命令 146 /// </summary> 147 private DelegateCommand nextPageCommand; 148 149 public DelegateCommand NextPageCommand 150 { 151 get 152 { 153 if (nextPageCommand == null) 154 { 155 nextPageCommand = new DelegateCommand(NextPage); 156 } 157 return nextPageCommand; 158 } 159 } 160 161 private void NextPage() 162 { 163 this.PageNum++; 164 if (this.PageNum > this.TotalPage) 165 { 166 this.PageNum = this.TotalPage; 167 } 168 this.InitInfo(); 169 } 170 171 #endregion
3. 新增编辑课程视图AddEditCourse
新增编辑课程视图,主要用于对课程的修改和新增,可通过查询页面的新增按钮和具体课程的编辑按钮弹出对应窗口。如下所示:
1 <UserControl x:Class="SIMS.CourseModule.Views.AddEditCourse" 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.CourseModule.Views" 7 mc:Ignorable="d" 8 xmlns:i="http://schemas.microsoft.com/xaml/behaviors" 9 xmlns:mahApps ="http://metro.mahapps.com/winfx/xaml/controls" 10 xmlns:prism="http://prismlibrary.com/" 11 d:DesignHeight="400" d:DesignWidth="600"> 12 <prism:Dialog.WindowStyle> 13 <Style TargetType="Window"> 14 <Setter Property="Width" Value="600"></Setter> 15 <Setter Property="Height" Value="400"></Setter> 16 </Style> 17 </prism:Dialog.WindowStyle> 18 <UserControl.Resources> 19 <ResourceDictionary> 20 <ResourceDictionary.MergedDictionaries> 21 <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" /> 22 <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" /> 23 </ResourceDictionary.MergedDictionaries> 24 </ResourceDictionary> 25 </UserControl.Resources> 26 <i:Interaction.Triggers> 27 <i:EventTrigger EventName="Loaded"> 28 <i:InvokeCommandAction Command="{Binding LoadedCommand}"></i:InvokeCommandAction> 29 </i:EventTrigger> 30 </i:Interaction.Triggers> 31 <Grid> 32 <Grid.ColumnDefinitions> 33 <ColumnDefinition Width="0.2*"></ColumnDefinition> 34 <ColumnDefinition Width="Auto"></ColumnDefinition> 35 <ColumnDefinition Width="*"></ColumnDefinition> 36 <ColumnDefinition Width="0.2*"></ColumnDefinition> 37 </Grid.ColumnDefinitions> 38 <Grid.RowDefinitions> 39 <RowDefinition></RowDefinition> 40 <RowDefinition></RowDefinition> 41 <RowDefinition></RowDefinition> 42 </Grid.RowDefinitions> 43 44 45 <TextBlock Text="课程名称" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Margin="3"></TextBlock> 46 <TextBox Grid.Row="0" Grid.Column="2" MinWidth="120" Height="35" VerticalAlignment="Center" Margin="3" Text="{Binding Course.Name}"></TextBox> 47 <TextBlock Text="授课老师" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Margin="3"></TextBlock> 48 <TextBox Grid.Row="1" Grid.Column="2" MinWidth="120" Height="35" VerticalAlignment="Center" Margin="3" Text="{Binding Course.Teacher}"></TextBox> 49 50 <StackPanel Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="3"> 51 <Button Content="取消" Margin="5" MinWidth="120" Height="35" Style="{DynamicResource MahApps.Styles.Button.Square.Accent}" Command="{Binding CancelCommand}"></Button> 52 <Button Content="保存" Margin="5" MinWidth="120" Height="35" Style="{DynamicResource MahApps.Styles.Button.Square.Accent}" Command="{Binding SaveCommand}"></Button> 53 </StackPanel> 54 55 </Grid> 56 </UserControl>
4. 新增编辑课程ViewModel
AddEditCourseViewModel是对页面具体功能的业务封装,主要是对应课程信息的保存,也包括数据绑定和命令绑定等内容,如下所示:
1 namespace SIMS.CourseModule.ViewModels 2 { 3 public class AddEditCourseViewModel:BindableBase,IDialogAware 4 { 5 #region 属性和构造函数 6 7 /// <summary> 8 /// 班级实体 9 /// </summary> 10 private CourseEntity course; 11 12 public CourseEntity Course 13 { 14 get { return course; } 15 set { SetProperty(ref course, value); } 16 } 17 18 public AddEditCourseViewModel() 19 { 20 21 } 22 23 #endregion 24 25 #region Command 26 27 private DelegateCommand loadedCommand; 28 29 public DelegateCommand LoadedCommand 30 { 31 get 32 { 33 if (loadedCommand == null) 34 { 35 loadedCommand = new DelegateCommand(Loaded); 36 } 37 return loadedCommand; 38 } 39 } 40 41 private void Loaded() 42 { 43 } 44 45 private DelegateCommand cancelCommand; 46 47 public DelegateCommand CancelCommand 48 { 49 get 50 { 51 if (cancelCommand == null) 52 { 53 cancelCommand = new DelegateCommand(Cancel); 54 } 55 return cancelCommand; 56 } 57 } 58 59 private void Cancel() 60 { 61 RequestClose?.Invoke((new DialogResult(ButtonResult.Cancel))); 62 } 63 64 private DelegateCommand saveCommand; 65 66 public DelegateCommand SaveCommand 67 { 68 get 69 { 70 if (saveCommand == null) 71 { 72 saveCommand = new DelegateCommand(Save); 73 } 74 return saveCommand; 75 } 76 } 77 78 private void Save() 79 { 80 if (Course != null) 81 { 82 Course.CreateTime = DateTime.Now; 83 Course.LastEditTime = DateTime.Now; 84 85 bool flag = false; 86 if (Course.Id > 0) 87 { 88 flag = CourseHttpUtil.UpdateCourse(Course); 89 } 90 else 91 { 92 flag = CourseHttpUtil.AddCourse(Course); 93 } 94 if (flag) 95 { 96 RequestClose?.Invoke((new DialogResult(ButtonResult.OK))); 97 } 98 } 99 } 100 101 102 #endregion 103 104 #region 对话框 105 106 public string Title => "新增或编辑课程信息"; 107 108 public event Action<IDialogResult> RequestClose; 109 110 public bool CanCloseDialog() 111 { 112 return true; 113 } 114 115 public void OnDialogClosed() 116 { 117 118 } 119 120 public void OnDialogOpened(IDialogParameters parameters) 121 { 122 if (parameters != null && parameters.ContainsKey("course")) 123 { 124 this.Course = parameters.GetValue<CourseEntity>("course"); 125 } 126 else 127 { 128 this.Course = new CourseEntity(); 129 } 130 } 131 132 #endregion 133 134 } 135 }
注意:因为新增编辑页面是弹出窗口,所以在Prism框架中,需要实现IDialogAware接口。
5. 控件注册
当页面功能开发完成后,在通过Prism进行注册,就可以通过导航栏和弹出窗口进行展示,如下所示:
1 namespace SIMS.CourseModule 2 { 3 public class CourseModule : IModule 4 { 5 public void OnInitialized(IContainerProvider containerProvider) 6 { 7 8 } 9 10 public void RegisterTypes(IContainerRegistry containerRegistry) 11 { 12 containerRegistry.RegisterForNavigation<Course, CourseViewModel>(nameof(Course)); 13 containerRegistry.RegisterDialog<AddEditCourse, AddEditCourseViewModel>("addEditCourse"); 14 } 15 } 16 }
示例效果图
经过上述步骤后,课程管理模块就开发完成,运行VS后,效果如下所示:
总结
经过上述步骤,不难看出,开发一个完整的功能,涉及到前端,后端,接口访问,数据库等相关内容,麻雀虽小,五脏俱全。其实开发一个课程管理模块,就是对数据库中课程表的增删改查,也可以把所有代码都糅合在一起,简化开发流程和步骤,这样代码量将大幅度减少。但是分层,分模块并不是为了使项目复杂化,而为了更加清晰以及便于维护与扩展,也是本篇文章希望为大家传递的一种开发理念。
备注
江上渔者【作者】范仲淹
江上往来人,但爱鲈鱼美。
君看一叶舟,出没风波里。
作者:老码识途
出处:http://www.cnblogs.com/hsiang/
本文版权归作者和博客园共有,写文不易,支持原创,欢迎转载【点赞】,转载请保留此段声明,且在文章页面明显位置给出原文连接,谢谢。
关注个人公众号,定时同步更新技术及职场文章