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後,效果如下所示:
總結
經過上述步驟,不難看出,開發一個完整的功能,涉及到前端,後端,介面訪問,資料庫等相關內容,麻雀雖小,五臟俱全。其實開發一個課程管理模組,就是對資料庫中課程表的增刪改查,也可以把所有程式碼都糅合在一起,簡化開發流程和步驟,這樣程式碼量將大幅度減少。但是分層,分模組並不是為了使專案複雜化,而為了更加清晰以及便於維護與擴充套件,也是本篇文章希望為大家傳遞的一種開發理念。
備註
江上漁者【作者】范仲淹
江上往來人,但愛鱸魚美。
君看一葉舟,出沒風波里。