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


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

    涉及知识点

    在本篇文章中,虽然只介绍一个模块的开发,但是麻雀虽小,五脏俱全,而且掌握一个模块的开发方法,相当于掌握通用的开发方法,就可以举一反三,其他模块的开发也可以水到渠成。涉及到的知识点如下:

    1. WPF开发中TextBlock,TextBox,DataGrid,Combox等控件的基础使用以及数据绑定等操作。
    2. MAH样式的使用,在本示例中MAH主要用于统一页面风格,提高用户体验。
    3. WebApi接口开发,本示例中,WebApi提供接口共客户端使用。
    4. HttpClient使用,主要用于访问服务端提供的接口。

    数据访问流程

    一般情况下,交付给客户的都是可视化的操作页面,而不是一些只有专业开发人员才能看懂的WebApi接口。为了更好的描述数据访问流程,以具体代码模块为例,如下所示:

    数据操作上下文DataContext

    关于WebApi和数据库的相关操作,在上一篇文章中已有说明,本文不再赘述,主要侧重于业务代码。关于课程管理模块的数据操作上下文,有两点需要加以说明:

    1. DbContext 是EntityFramwork提供的用于访问数据库中数据表的类,一个DbContext实例表示一个session,可用于查询或保存数据实体对应数据表。
    2. 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。如下所示:

    1. Course视图,主要用于课程的查询,以及新增,修改,删除的链接入口。
    2. AddEditCourse视图,主要用于课程信息的新增和修改,共用一个视图页面。
    3. 删除课程不需要页面,所以没有对应视图。

    1. Course视图

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

    1. Course视图页面布局采用Grid方式和StackPanel混合布局,即整体布局采用Grid,细微布局采用StackPanel。
    2. 课程采用分页列表的方式展示,需要用到DataGrid,及分页控件【WPF默认不提供分页控件,可自行编写分页控件】。
    3. 查询条件采用按钮Button和文本框TextBox等组成,关于基础控件的使用,不再详细论述,可参考其他文章。
    4. 在本系统的所有WPF视图中,均需要引入Prism和 MAH组件。
    5. 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后,效果如下所示:

    总结

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

    备注

    江上渔者【作者】范仲淹 【朝代】宋

    江上往来人,鲈鱼美。

    君看一叶舟,出没风波里。

  • 相关阅读:
    基础才是重中之重~B/S系统中的事件订阅
    将不确定变为确实~请自己搞清楚异常页面的设置方法(网上那些资料说的基本都有问题!)
    基础才是重中之重~延迟初始化
    批量替换sqlserver数据库TEXT字段类型的数据
    12554 A Special "Happy Birthday" Song!!!
    Linux socket 网络编程常用函数总结
    新浪微博Python SDK笔记——发微博(一)
    在Ubuntu上下载、编译和安装Android 4.2 最新内核源代码(Linux Kernel)
    20、SQL Server 数据修改之Update
    19、SQL Server 数据修改之Insert into
  • 原文地址:https://www.cnblogs.com/hsiang/p/16322653.html
Copyright © 2020-2023  润新知