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具体代码,如下所示:

namespace SIMS.WebApi.Data
{
    public class DataContext:DbContext
    {
        public DbSet<UserEntity> Users { get; set; }

        public DbSet<MenuEntity> Menus { get; set; }

        public DbSet<RoleEntity> Roles { get; set; }

        public DbSet<UserRoleEntity> UserRoles { get; set; }

        public DbSet<RoleMenuEntity> RoleMenus { get; set; }

        /// <summary>
        /// 学生
        /// </summary>
        public DbSet<StudentEntity> Students { get; set; }

        /// <summary>
        /// 班级
        /// </summary>
        public DbSet<ClassesEntity> Classes { get; set; }

        /// <summary>
        /// 课程
        /// </summary>
        public DbSet<CourseEntity> Courses { get; set; }

        /// <summary>
        /// 成绩
        /// </summary>
        public DbSet<ScoreEntity> Scores { get; set; }

        public DataContext(DbContextOptions options) : base(options)
        {

        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<UserEntity>().ToTable("Users");
            modelBuilder.Entity<MenuEntity>().ToTable("Menus");
            modelBuilder.Entity<StudentEntity>().ToTable("Students");
            modelBuilder.Entity<RoleEntity>().ToTable("Roles");
            modelBuilder.Entity<UserRoleEntity>().ToTable("UserRoles");
            modelBuilder.Entity<RoleMenuEntity>().ToTable("RoleMenus");
        }
    }
}

数据服务Service

数据服务是对DataContext操作的封装,使得业务更加明确,当然如果通过控制器直接访问DataContext,省掉数据服务,也可以实现对应功能,只是加入数据服务层,使得结构更加清晰。

数据服务接口ICourseAppService,提供了五个接口,包括查询课程列表,查询当个课程信息,新增课程,修改课程,删除课程,具体如下所示:

namespace SIMS.WebApi.Services.Course
{
    public interface ICourseAppService
    {
        /// <summary>
        /// 查询列表
        /// </summary>
        /// <param name="courseName"></param>
        /// <param name="teacher"></param>
        /// <param name="pageNum"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public PagedRequest<CourseEntity> GetCourses(string courseName,string teacher, int pageNum,int pageSize);

        /// <summary>
        /// 通过id查询课程信息
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public CourseEntity GetCourse(int id);

        /// <summary>
        /// 新增课程
        /// </summary>
        /// <param name="course"></param>
        /// <returns></returns>
        public int AddCourse(CourseEntity course);

        /// <summary>
        /// 修改课程
        /// </summary>
        /// <param name="course"></param>
        /// <returns></returns>
        public int UpdateCourse(CourseEntity course);

        /// <summary>
        /// 删除课程
        /// </summary>
        /// <param name="id"></param>
        public int DeleteCourse(int id);
    }
}

数据服务接口实现类CourseAppService,对数据服务接口ICourseAppService的实现,即通过操作DataContext来访问数据库中的课程表,如下所示:

namespace SIMS.WebApi.Services.Course
{
    public class CourseAppService : ICourseAppService
    {
        private DataContext dataContext;

        public CourseAppService(DataContext dataContext)
        {
            this.dataContext = dataContext;
        }

        public int AddCourse(CourseEntity course)
        {
            var entry = dataContext.Courses.Add(course);
            dataContext.SaveChanges();
            return 0;
        }

        public int DeleteCourse(int id)
        {
            var entity = dataContext.Courses.FirstOrDefault(x => x.Id == id);
            if (entity != null)
            {
                dataContext.Courses.Remove(entity);
                dataContext.SaveChanges();
            }
            return 0;
        }

        /// <summary>
        /// 根据ID获取单个课程
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public CourseEntity GetCourse(int id)
        {
            var entity = dataContext.Courses.FirstOrDefault(r => r.Id == id);
            return entity;
        }

        /// <summary>
        /// 获取课程列表
        /// </summary>
        /// <param name="courseName"></param>
        /// <param name="teacher"></param>
        /// <param name="pageNum"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public PagedRequest<CourseEntity> GetCourses(string courseName, string teacher, int pageNum, int pageSize)
        {
            IQueryable<CourseEntity> courses = null;
            if (!string.IsNullOrEmpty(courseName) && !string.IsNullOrEmpty(teacher))
            {
                courses = dataContext.Courses.Where(r => r.Name.Contains(courseName) && r.Teacher.Contains(teacher)).OrderBy(r => r.Id);
            }
            else if (!string.IsNullOrEmpty(courseName))
            {
                courses = dataContext.Courses.Where(r => r.Name.Contains(courseName)).OrderBy(r => r.Id);
            }
            else if (!string.IsNullOrEmpty(teacher))
            {
                courses = dataContext.Courses.Where(r => r.Teacher.Contains(teacher)).OrderBy(r => r.Id);
            }
            else
            {
                courses = dataContext.Courses.Where(r => true).OrderBy(r => r.Id);
            }
            int count = courses.Count();
            List<CourseEntity> items;
            if (pageSize > 0)
            {
                items = courses.Skip((pageNum - 1) * pageSize).Take(pageSize).ToList();
            }
            else
            {
                items = courses.ToList();
            }
            return new PagedRequest<CourseEntity>()
            {
                count = count,
                items = items
            };
        }

        public int UpdateCourse(CourseEntity course)
        {
            dataContext.Courses.Update(course);
            dataContext.SaveChanges();
            return 0;
        }
    }
}

WebApi接口控制器

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

namespace SIMS.WebApi.Controllers
{
    /// <summary>
    /// 课程控制器
    /// </summary>
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class CourseController : ControllerBase
    {
        private readonly ILogger<CourseController> logger;

        private readonly ICourseAppService courseAppService;

        public CourseController(ILogger<CourseController> logger, ICourseAppService courseAppService)
        {
            this.logger = logger;
            this.courseAppService = courseAppService;
        }

        [HttpGet]
        public PagedRequest<CourseEntity> GetCourses( int pageNum, int pageSize, string? courseName=null, string? teacher=null) {
            return courseAppService.GetCourses(courseName,teacher,pageNum,pageSize);
        }

        /// <summary>
        /// 获取课程信息
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet]
        public CourseEntity GetCourse(int id)
        {
            return courseAppService.GetCourse(id);
        }

        /// <summary>
        /// 新增课程
        /// </summary>
        /// <param name="course"></param>
        /// <returns></returns>
        [HttpPost]
        public int AddCourse(CourseEntity course)
        {
            return courseAppService.AddCourse(course);
        }

        /// <summary>
        /// 修改课程
        /// </summary>
        /// <param name="course"></param>
        /// <returns></returns>
        [HttpPut]
        public int UpdateCourse(CourseEntity course)
        {
            return courseAppService.UpdateCourse(course);
        }

        /// <summary>
        /// 删除课程
        /// </summary>
        /// <param name="id"></param>
        [HttpDelete]
        public int DeleteCourse(int id)
        {
            return courseAppService.DeleteCourse(id);
        }
    }
}

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

 客户端接口访问类HttpUtil

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

namespace SIMS.Utils.Http
{
    /// <summary>
    /// HTTP访问基类
    /// </summary>
    public class HttpUtil
    {
        private static readonly string absoluteUrl = "https://localhost:7299/";

        /// <summary>
        /// get方式
        /// </summary>
        /// <param name="url"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public static string Get(string url, Dictionary<string, object> param)
        {
            string p=string.Empty;
            if (param != null && param.Count() > 0) {
                foreach (var keypair in param)
                {
                    if (keypair.Value != null)
                    {
                        p += $"{keypair.Key}={keypair.Value}&";
                    }
                }
            }
            if (!string.IsNullOrEmpty(p)) {
                p=p.TrimEnd('&');
            }
            var httpClient = new HttpClient();
            var response = httpClient.GetAsync($"{absoluteUrl}{url}?{p}",HttpCompletionOption.ResponseContentRead).Result;
            string strJson = string.Empty;
            Stream stream = response.Content.ReadAsStream();
            using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
            {
                strJson = reader.ReadToEnd();
            }
            return strJson;
        }

        /// <summary>
        /// POST方式
        /// </summary>
        /// <param name="url"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public static string Post<T>(string url, T t)
        {
            var content = JsonConvert.SerializeObject(t);

            var httpContent = new StringContent(content,Encoding.UTF8,"application/json");
            var httpClient = new HttpClient();
            var response =  httpClient.PostAsync($"{absoluteUrl}{url}", httpContent).Result;

            var respContent =  response.Content.ReadAsStringAsync().Result;
            return respContent;
        }

        public static string Put<T>(string url, T t) {
            var content = JsonConvert.SerializeObject(t);

            var httpContent = new StringContent(content,Encoding.UTF8,"application/json");
            var httpClient = new HttpClient();
            var response =  httpClient.PutAsync($"{absoluteUrl}{url}", httpContent).Result;

            var respContent = response.Content.ReadAsStringAsync().Result;
            return respContent;
        }

        public static string Delete(string url, Dictionary<string, string> param) {
            string p = string.Empty;
            if (param != null && param.Count() > 0)
            {
                foreach (var keypair in param)
                {
                    p += $"{keypair.Key}={keypair.Value}&";
                }
            }
            if (!string.IsNullOrEmpty(p))
            {
                p = p.TrimEnd('&');
            }
            var httpClient = new HttpClient();
            var response =  httpClient.DeleteAsync($"{absoluteUrl}{url}?{p}").Result;
            var respContent = response.Content.ReadAsStringAsync().Result;
            return respContent;
        }


        public static T StrToObject<T>(string str) {
            var t = JsonConvert.DeserializeObject<T>(str);
            return t;
        }
    }
}

然后课课程接口访问通用类CourseHttpUtil继承了HttpUtil,以对应课程信息的操作。如下所示:

namespace SIMS.Utils.Http
{
    public class CourseHttpUtil:HttpUtil
    {
        /// <summary>
        /// 通过id查询课程信息
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static CourseEntity GetCourse(int id)
        {
            Dictionary<string, object> data = new Dictionary<string, object>();
            data["id"] = id;
            var str = Get(UrlConfig.COURSE_GETCOURSE, data);
            var course = StrToObject<CourseEntity>(str);
            return course;
        }

        public static PagedRequest<CourseEntity> GetCourses(string? courseName, string? teacher, int pageNum, int pageSize)
        {
            Dictionary<string, object> data = new Dictionary<string, object>();
            data["courseName"] = courseName;
            data["teacher"] = teacher;
            data["pageNum"] = pageNum;
            data["pageSize"] = pageSize;
            var str = Get(UrlConfig.COURSE_GETCOURSES, data);
            var courses = StrToObject<PagedRequest<CourseEntity>>(str);
            return courses;
        }

        public static bool AddCourse(CourseEntity course)
        {
            var ret = Post<CourseEntity>(UrlConfig.COURSE_ADDCOURSE, course);
            return int.Parse(ret) == 0;
        }

        public static bool UpdateCourse(CourseEntity course)
        {
            var ret = Put<CourseEntity>(UrlConfig.SCORE_UPDATESCORE, course);
            return int.Parse(ret) == 0;
        }

        public static bool DeleteCourse(int Id)
        {
            Dictionary<string, string> data = new Dictionary<string, string>();
            data["Id"] = Id.ToString();
            var ret = Delete(UrlConfig.COURSE_DELETECOURSE, data);
            return int.Parse(ret) == 0;
        }
    }
}

客户端操作

经过前面四个部分的开发,客户端就可以与数据接口进行交互,展示数据到客户端。客户端所有的开发,均采用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视图具体代码,如下所示:

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

2. CourseViewModel

CourseViewModel是页面视图的业务逻辑处理,如处理客户端的点击的命令等内容。主要分为三部分:

2.1 数据绑定
数据绑定,如查询条件的文本框内容的绑定,课程查询列表的绑定。其中数据访问采用CourseHttpUtil工具类,如下所示:

#region 属性及构造函数

/// <summary>
/// 专业
/// </summary>
private string courseName;

public string CourseName
{
    get { return courseName; }
    set { SetProperty(ref courseName, value); }
}

/// <summary>
/// 年级
/// </summary>
private string teacher;

public string Teacher
{
    get { return teacher; }
    set { SetProperty(ref teacher, value); }
}



private ObservableCollection<CourseEntity> courses;

public ObservableCollection<CourseEntity> Courses
{
    get { return courses; }
    set { SetProperty(ref courses, value); }
}

private IDialogService dialogService;

public CourseViewModel(IDialogService dialogService)
{
    this.dialogService = dialogService;
    this.pageNum = 1;
    this.pageSize = 20;
}

private void InitInfo()
{
    Courses = new ObservableCollection<CourseEntity>();
    var pagedRequst = CourseHttpUtil.GetCourses(this.CourseName, this.Teacher, this.pageNum, this.pageSize);
    var entities = pagedRequst.items;
    Courses.AddRange(entities);
    //
    this.TotalCount = pagedRequst.count;
    this.TotalPage = ((int)Math.Ceiling(this.TotalCount * 1.0 / this.pageSize));
}

#endregion

2.2 命令绑定

在课程页面,查询按钮,编辑按钮,删除按钮,均与ViewModel中的命令进行绑定,如下所示:

#region 事件

private DelegateCommand loadedCommand;

public DelegateCommand LoadedCommand
{
    get
    {
        if (loadedCommand == null)
        {
            loadedCommand = new DelegateCommand(Loaded);
        }
        return loadedCommand;
    }
}

private void Loaded()
{
    InitInfo();
}

private DelegateCommand queryCommand;

public DelegateCommand QueryCommand
{
    get
    {
        if (queryCommand == null)
        {
            queryCommand = new DelegateCommand(Query);
        }
        return queryCommand;
    }
}

private void Query()
{
    this.pageNum = 1;
    this.InitInfo();
}

/// <summary>
/// 新增命令
/// </summary>
private DelegateCommand addCommand;

public DelegateCommand AddCommand
{
    get
    {
        if (addCommand == null)
        {
            addCommand = new DelegateCommand(Add);
        }
        return addCommand;
    }
}

private void Add()
{
    this.dialogService.ShowDialog("addEditCourse", null, AddEditCallBack, "MetroDialogWindow");
}

private void AddEditCallBack(IDialogResult dialogResult)
{
    if (dialogResult != null && dialogResult.Result == ButtonResult.OK)
    {
        //刷新列表
        this.pageNum = 1;
        this.InitInfo();
    }
}

/// <summary>
/// 编辑命令
/// </summary>
private DelegateCommand<object> editCommand;

public DelegateCommand<object> EditCommand
{
    get
    {
        if (editCommand == null)
        {
            editCommand = new DelegateCommand<object>(Edit);
        }
        return editCommand;
    }
}

private void Edit(object obj)
{
    if (obj == null)
    {
        return;
    }
    var Id = int.Parse(obj.ToString());
    var course = this.Courses.FirstOrDefault(r => r.Id == Id);
    if (course == null)
    {
        MessageBox.Show("无效的课程ID");
        return;
    }
    IDialogParameters dialogParameters = new DialogParameters();
    dialogParameters.Add("course", course);
    this.dialogService.ShowDialog("addEditCourse", dialogParameters, AddEditCallBack, "MetroDialogWindow");
}

/// <summary>
/// 编辑命令
/// </summary>
private DelegateCommand<object> deleteCommand;

public DelegateCommand<object> DeleteCommand
{
    get
    {
        if (deleteCommand == null)
        {
            deleteCommand = new DelegateCommand<object>(Delete);
        }
        return deleteCommand;
    }
}

private void Delete(object obj)
{
    if (obj == null)
    {
        return;
    }
    var Id = int.Parse(obj.ToString());
    var course = this.Courses.FirstOrDefault(r => r.Id == Id);
    if (course == null)
    {
        MessageBox.Show("无效的班级ID");
        return;
    }
    if (MessageBoxResult.Yes != MessageBox.Show("Are you sure to delete?", "Confirm", MessageBoxButton.YesNo)) {
        return;
    }
    bool flag = CourseHttpUtil.DeleteCourse(Id);
    if (flag)
    {
        this.pageNum = 1;
        this.InitInfo();
    }
}

#endregion

2.3 分页命令与数据绑定

关于分页功能,是通用的功能,几乎每一个查询页面的分页都大同小异,可以进行复制粘贴,快速实现功能。如下所示:

#region 分页

/// <summary>
/// 当前页码
/// </summary>
private int pageNum;

public int PageNum
{
    get { return pageNum; }
    set { SetProperty(ref pageNum, value); }
}

/// <summary>
/// 每页显示多少条记录
/// </summary>
private int pageSize;

public int PageSize
{
    get { return pageSize; }
    set { SetProperty(ref pageSize, value); }
}

/// <summary>
///总条数
/// </summary>
private int totalCount;

public int TotalCount
{
    get { return totalCount; }
    set { SetProperty(ref totalCount, value); }
}

/// <summary>
///总页数
/// </summary>
private int totalPage;

public int TotalPage
{
    get { return totalPage; }
    set { SetProperty(ref totalPage, value); }
}


/// <summary>
/// 跳转页
/// </summary>
private int jumpNum;

public int JumpNum
{
    get { return jumpNum; }
    set { SetProperty(ref jumpNum, value); }
}

/// <summary>
/// 首页命令
/// </summary>
private DelegateCommand firstPageCommand;

public DelegateCommand FirstPageCommand
{
    get
    {
        if (firstPageCommand == null)
        {
            firstPageCommand = new DelegateCommand(FirstPage);
        }
        return firstPageCommand;
    }

}

private void FirstPage()
{
    this.PageNum = 1;
    this.InitInfo();
}

/// <summary>
/// 跳转页命令
/// </summary>
private DelegateCommand jumpPageCommand;

public DelegateCommand JumpPageCommand
{
    get
    {
        if (jumpPageCommand == null)
        {
            jumpPageCommand = new DelegateCommand(JumpPage);
        }
        return jumpPageCommand;
    }
}

private void JumpPage()
{
    if (jumpNum < 1)
    {
        MessageBox.Show("请输入跳转页");
        return;
    }
    if (jumpNum > this.totalPage)
    {
        MessageBox.Show($"跳转页面必须在[1,{this.totalPage}]之间,请确认。");
        return;
    }
    this.PageNum = jumpNum;

    this.InitInfo();
}

/// <summary>
/// 前一页
/// </summary>
private DelegateCommand prevPageCommand;

public DelegateCommand PrevPageCommand
{
    get
    {
        if (prevPageCommand == null)
        {
            prevPageCommand = new DelegateCommand(PrevPage);
        }
        return prevPageCommand;
    }
}

private void PrevPage()
{
    this.PageNum--;
    if (this.PageNum < 1)
    {
        this.PageNum = 1;
    }
    this.InitInfo();
}

/// <summary>
/// 下一页命令
/// </summary>
private DelegateCommand nextPageCommand;

public DelegateCommand NextPageCommand
{
    get
    {
        if (nextPageCommand == null)
        {
            nextPageCommand = new DelegateCommand(NextPage);
        }
        return nextPageCommand;
    }
}

private void NextPage()
{
    this.PageNum++;
    if (this.PageNum > this.TotalPage)
    {
        this.PageNum = this.TotalPage;
    }
    this.InitInfo();
}

#endregion

3. 新增编辑课程视图AddEditCourse

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

<UserControl x:Class="SIMS.CourseModule.Views.AddEditCourse"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:SIMS.CourseModule.Views"
             mc:Ignorable="d"
             xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
             xmlns:mahApps ="http://metro.mahapps.com/winfx/xaml/controls"
             xmlns:prism="http://prismlibrary.com/"
             d:DesignHeight="400" d:DesignWidth="600">
    <prism:Dialog.WindowStyle>
        <Style TargetType="Window">
            <Setter Property="Width" Value="600"></Setter>
            <Setter Property="Height" Value="400"></Setter>
        </Style>
    </prism:Dialog.WindowStyle>
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding LoadedCommand}"></i:InvokeCommandAction>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.2*"></ColumnDefinition>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="0.2*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>


        <TextBlock Text="课程名称" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Margin="3"></TextBlock>
        <TextBox Grid.Row="0" Grid.Column="2" MinWidth="120" Height="35"  VerticalAlignment="Center" Margin="3" Text="{Binding Course.Name}"></TextBox>
        <TextBlock Text="授课老师" Grid.Row="1" Grid.Column="1"  VerticalAlignment="Center" Margin="3"></TextBlock>
        <TextBox Grid.Row="1" Grid.Column="2" MinWidth="120" Height="35"   VerticalAlignment="Center" Margin="3" Text="{Binding Course.Teacher}"></TextBox>

        <StackPanel Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="3">
            <Button Content="取消" Margin="5" MinWidth="120" Height="35" Style="{DynamicResource MahApps.Styles.Button.Square.Accent}" Command="{Binding CancelCommand}"></Button>
            <Button Content="保存" Margin="5" MinWidth="120" Height="35" Style="{DynamicResource MahApps.Styles.Button.Square.Accent}" Command="{Binding SaveCommand}"></Button>
        </StackPanel>

    </Grid>
</UserControl>

4. 新增编辑课程ViewModel

AddEditCourseViewModel是对页面具体功能的业务封装,主要是对应课程信息的保存,也包括数据绑定和命令绑定等内容,如下所示:

namespace SIMS.CourseModule.ViewModels
{
    public class AddEditCourseViewModel:BindableBase,IDialogAware
    {
        #region 属性和构造函数

        /// <summary>
        /// 班级实体
        /// </summary>
        private CourseEntity course;

        public CourseEntity Course
        {
            get { return course; }
            set { SetProperty(ref course, value); }
        }

        public AddEditCourseViewModel()
        {

        }

        #endregion

        #region Command

        private DelegateCommand loadedCommand;

        public DelegateCommand LoadedCommand
        {
            get
            {
                if (loadedCommand == null)
                {
                    loadedCommand = new DelegateCommand(Loaded);
                }
                return loadedCommand;
            }
        }

        private void Loaded()
        {
        }

        private DelegateCommand cancelCommand;

        public DelegateCommand CancelCommand
        {
            get
            {
                if (cancelCommand == null)
                {
                    cancelCommand = new DelegateCommand(Cancel);
                }
                return cancelCommand;
            }
        }

        private void Cancel()
        {
            RequestClose?.Invoke((new DialogResult(ButtonResult.Cancel)));
        }

        private DelegateCommand saveCommand;

        public DelegateCommand SaveCommand
        {
            get
            {
                if (saveCommand == null)
                {
                    saveCommand = new DelegateCommand(Save);
                }
                return saveCommand;
            }
        }

        private void Save()
        {
            if (Course != null)
            {
                Course.CreateTime = DateTime.Now;
                Course.LastEditTime = DateTime.Now;

                bool flag = false;
                if (Course.Id > 0)
                {
                    flag = CourseHttpUtil.UpdateCourse(Course);
                }
                else
                {
                    flag = CourseHttpUtil.AddCourse(Course);
                }
                if (flag)
                {
                    RequestClose?.Invoke((new DialogResult(ButtonResult.OK)));
                }
            }
        }


        #endregion

        #region 对话框

        public string Title => "新增或编辑课程信息";

        public event Action<IDialogResult> RequestClose;

        public bool CanCloseDialog()
        {
            return true;
        }

        public void OnDialogClosed()
        {

        }

        public void OnDialogOpened(IDialogParameters parameters)
        {
            if (parameters != null && parameters.ContainsKey("course"))
            {
                this.Course = parameters.GetValue<CourseEntity>("course");
            }
            else
            {
                this.Course = new CourseEntity();
            }
        }

        #endregion

    }
}

注意:因为新增编辑页面是弹出窗口,所以在Prism框架中,需要实现IDialogAware接口。

5. 控件注册

当页面功能开发完成后,在通过Prism进行注册,就可以通过导航栏和弹出窗口进行展示,如下所示:

namespace SIMS.CourseModule
{
    public class CourseModule : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {

        }

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterForNavigation<Course, CourseViewModel>(nameof(Course));
            containerRegistry.RegisterDialog<AddEditCourse, AddEditCourseViewModel>("addEditCourse");
        }
    }
}

示例效果图

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

总结

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

备注

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

江上往来人,但爱鲈鱼美。

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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老码识途呀

写作不易,多谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值