在前面随笔《循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(1)》中介绍了Mvvm 的开发,以及一些界面效果,本篇随笔继续深入探讨基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发,介绍如何整合SqlSugar框架的基础接口,通过基类继承的方式,简化实际项目的开发代码处理。

1、View模块中的XAML格式说明

在介绍MVVM几个部分内容之前,我们先连接一下View模块中的Xaml格式的说明,我们知道Xaml也是一个xml的扩展,属于标记语言的一种,编辑器为了更好的验证格式以及提出上下文的智能提示,必然需要确定对应标记元素的格式,这个就是通过Xaml的头部定义确立了,如下所示。

  1. <Page
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:local="clr-namespace:WHC.SugarProject.WpfUI.Views.Pages"
  7.  
  8. x:Class="WHC.SugarProject.WpfUI.Views.Pages.UserListPage"
  9. d:DataContext="{d:DesignInstance local:UserListPage,
  10. IsDesignTimeCreatable=False}"
  11. d:DesignHeight="450"
  12. d:DesignWidth="800"
  13. mc:Ignorable="d">

其中红色部分基本上是约定需要输入的定义了,主要是通过xmlns来定义定义XML的校验格式,类似我们常用的命名空间的引用地址了。

  1. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  2. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  3. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  4. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

而下面 xmlns:local 也是定义了本地的XML命名空间。

  1. xmlns:local="clr-namespace:WHC.SugarProject.WpfUI.Views.Pages"

便于在界面元素标记中引用对应的控件等。而x:Class则是确定XAML的后台代码文件的全名称,使用过早期ASPX的都知道,ASPX文件有一个后台代码文件,同理XAML也是类似的概念。

  1. x:Class="WHC.SugarProject.WpfUI.Views.Pages.UserListPage"

而d:开始的那些设置,是指设计样式下的一些属性定义,如下所示定义设计窗体的大小,实际运行可能和这个不一样,因为WPF是属于矢量的尺寸标记的。

  1. d:DesignHeight="450"
  2. d:DesignWidth="800"

而d:DataContext则是声明View和模型绑定的一个重要的说明,界面视图View可以绑定模型的命令Command,也可以绑定对应的集合或者属性等。其中local:UserListPage,就是通过简写命名控件local和UserListPage类的组合,实现一个全名称的快速定义。而IsDesignTimeCreatable则是说明设计状态下的数据处理方式。

  1. d:DataContext="{d:DesignInstance local:UserListPage,
  2. IsDesignTimeCreatable=False}"

有时候,我们可能在窗口或者页面视图的定义中,还会看到一些其他命名空间的定义或者属性的定义,如下代码所示。

  1. <Page
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:local="clr-namespace:WHC.SugarProject.WpfUI.Views.Pages"
  7. xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
  8. xmlns:helpers="clr-namespace:WHC.SugarProject.WpfUI.Helpers"
  9. xmlns:hc="https://handyorg.github.io/handycontrol"
  10. xmlns:Controls="clr-namespace:WHC.SugarProject.WpfUI.Controls"
  11.  
  12. x:Class="WHC.SugarProject.WpfUI.Views.Pages.UserListPage"
  13. d:DataContext="{d:DesignInstance local:UserListPage,
  14. IsDesignTimeCreatable=False}"
  15. d:DesignHeight="450"
  16. d:DesignWidth="800"
  17. mc:Ignorable="d">

例如上面的红色部分,就是一般根据实际情况增加的一些控件的命名空间的定义,便于引用对应的界面控件进行使用。

  1. <hc:UniformSpacingPanel
  2. Margin="0,0,0,20"
  3. HorizontalAlignment="Right"
  4. Orientation="Horizontal"
  5. Spacing="10">
  6. <Button
  7. Width="60"
  8. Height="40"
  9. Command="{Binding SaveCommand}"
  10. Content="保存"
  11. Style="{StaticResource ButtonPrimary}" />
  12. <Button
  13. Width="60"
  14. Height="40"
  15. Command="{Binding ViewModel.BackCommand}"
  16. CommandParameter="test"
  17. Content="关闭" />
  18. </hc:UniformSpacingPanel>

有时候可能还会看到一些属性设置,如背景色,以及是否滚动页面等设置。

  1. Background="{DynamicResource RegionBrush}"
  2. ScrollViewer.CanContentScroll="true"

2、MVVM应用中包括Model、View、ViewModel三者内容中的处理

前面我们见到,View视图中绑定ViewModel模型的时候,使用d:DataConext进行定义设置,确定视图模型的内容,我们刚才介绍的定义是如下所示。

  1. d:DataContext="{d:DesignInstance local:UserListPage,
  2. IsDesignTimeCreatable=False}"

这里你看到的它映射向其本身,而非具体的Viewmodel,这里没有错误,有些MVVM的设置指向具体的ViewModel定义。

我们来看看这个UserListPage的后端类的定义,如下所示。

首先我们看到一个如下所示。

  1. namespace WHC.SugarProject.WpfUI.Views.Pages;
  2.  
  3. /// <summary>
  4. /// UserListPage.xaml 交互逻辑
  5. /// </summary>
  6. public partial class UserListPage : INavigableView<UserListViewModel>
  7. {
  8. /// <summary>
  9. /// 视图模型对象
  10. /// </summary>
  11. public UserListViewModel ViewModel { get; }
  12.  
  13. /// <summary>
  14. /// 构造函数
  15. /// </summary>
  16. /// <param name="viewModel">视图模型对象</param>
  17. public UserListPage(UserListViewModel viewModel)
  18. {
  19. ViewModel = viewModel;
  20. DataContext = this;
  21.  
  22. InitializeComponent();
  23. }
  24. }

这里通过泛型接口的方式,定义一个具体类型的视图模型给视图View使用。

其中INavigableView接口的定义如下。

  1. public interface INavigableView<out T>
  2. {
  3. /// <summary>
  4. /// 视图的 ViewModel
  5. /// </summary>
  6. T ViewModel { get; }
  7. }

这样我们在Xaml视图里面,就可以绑定属性,也可以绑定相应的Command命令了,可以是类的,也可以是ViewModel的内容。

例如我们在绑定查询条件处理数据查询的操作的时候,

其中界面查询条件定义了一个文本框名称的控件,如下所示。

  1. <TextBox
  2. Margin="5"
  3. hc:TitleElement.Title="用户账号"
  4. hc:TitleElement.TitlePlacement="Left"
  5. Style="{StaticResource TextBoxExtend}"
  6. Text="{Binding ViewModel.PageDto.Name, UpdateSourceTrigger=PropertyChanged}">
  7. <TextBox.InputBindings>
  8. <KeyBinding Key="Enter" Command="{Binding ViewModel.SearchCommand}" />
  9. </TextBox.InputBindings>
  10. </TextBox>

我们通过 {Binding ViewModel.PageDto.Name, UpdateSourceTrigger=PropertyChanged} 的方式绑定文本的界面显示内容和后端模型属性的,并且在绑定的控件属性变化的时候触发更新。这样只要界面上输入的内部发生变化,后端绑定的查询属性马上得到更新,我们就可以即时的通过查询触发获得相应条件的记录了。

另外通过 InputBindings 的方式,接收Enter内容后马上触发查询处理的命令。

查询的时候,我们在视图模型上定义一个RelayCommand的特性标注,声明这个生成的Command为视图提供处理命令的。

  1. /// <summary>
  2. /// 触发查询处理命令
  3. /// </summary>
  4. /// <returns></returns>
  5. [RelayCommand]
  6. private async Task Search()
  7. {
  8. //切换第一页
  9. this.PagerInfo.CurrentPageIndex = 1;
  10.  
  11. //转换下分页信息
  12. ConvertPagingInfo();
  13. //查询更新
  14. await GetData();
  15. }

然后我们通过转换分页条件的信息,获得查询条件后进行服务接口的调用,获取相应条件的数据即可。

这个接口的后端就是SqlSugar框架的标准请求接口了。

  1. /// <summary>
  2. /// 根据分页和查询条件查询,请求数据
  3. /// </summary>
  4. /// <returns></returns>
  5. public virtual async Task GetData()
  6. {
  7. var result = await service.GetListAsync(this.PageDto);
  8. if (result != null)
  9. {
  10. this.Items = result.Items?.ToList();
  11. this.PagerInfo.RecordCount = result.TotalCount;
  12. }
  13. }

我们看到,由于框架的通用性抽象处理,因此上面的接口应该是可以实现更高层次的抽象处理的,因此我们设计了几个视图基类,用于减少代码的处理。

其中 ObservableObject 基类是CommunityToolkit.Mvvm模块里面定义的视图模型基类,我们定义的通用基类也是基于它继承过来即可。

其中BaseViewModel用来处理一些通用的视图模型操作方式,如跳转或者返回等。

而BaseListViewModel则是根据查询列表处理的操作界面所需要的视图模型处理封装。

我们看到类定义和我们SqlSugar很多基类定义类似,都是需要通过泛型传入一些相关的参数,实现更加通用的控制的。

  1. public abstract partial class BaseListViewModel<TEntity, TKey, TGetListInput> : BaseViewModel, INavigationAware
  2. where TEntity : class, IEntity<TKey>, new()
  3. where TGetListInput : IPagedAndSortedResultRequest, new()

由于.net的开发方式,现在基本上都是以接口注入方式来处理,这个也是一样,我们通过注入一个常规服务接口的类,来实现一些常规的请求处理。视图模型类的接口注入如下所示。

  1. /// <summary>
  2. /// 通用基础操作接口
  3. /// </summary>
  4. protected IMyCrudService<TEntity, TKey, TGetListInput> service { get; set; }
  5.  
  6. /// <summary>
  7. /// 构造函数
  8. /// </summary>
  9. /// <param name="service">操作接口</param>
  10. /// <param name="navigationService">视图导航服务接口</param>
  11. public BaseListViewModel(IMyCrudService<TEntity, TKey, TGetListInput> service)
  12. {
  13. this.service = service;
  14. }

这样我们在分页控件的页码变化的时候,就可以触发这个基类的命令处理了。

  1. /// <summary>
  2. /// 触发的分页处理命令
  3. /// </summary>
  4. /// <param name="info"></param>
  5. /// <returns></returns>
  6. [RelayCommand]
  7. private async Task PageUpdated(FunctionEventArgs<int> info)
  8. {
  9. //根据分页页码展示
  10. this.PagerInfo.CurrentPageIndex = info.Info;
  11.  
  12. //转换下分页信息
  13. ConvertPagingInfo();
  14. //查询更新
  15. await GetData();
  16. }

它的分页控件部分的视图界面的代码如下所示。

  1. <hc:Pagination
  2. Margin="0,10,0,10"
  3. DataCountPerPage="{Binding ViewModel.PagerInfo.PageSize}"
  4. IsJumpEnabled="True"
  5. MaxPageCount="{Binding ViewModel.PagerInfo.MaxPageCount}"
  6. MaxPageInterval="5"
  7. PageIndex="{Binding ViewModel.PagerInfo.CurrentPageIndex}">
  8. <hc:Interaction.Triggers>
  9. <hc:EventTrigger EventName="PageUpdated">
  10. <hc:EventToCommand Command="{Binding ViewModel.PageUpdatedCommand}" PassEventArgsToCommand="True" />
  11. </hc:EventTrigger>
  12. </hc:Interaction.Triggers>
  13. </hc:Pagination>

而我们查询命令,也可以通过下面的基类函数来处理了。

  1. /// <summary>
  2. /// 触发查询处理命令
  3. /// </summary>
  4. /// <returns></returns>
  5. [RelayCommand]
  6. private async Task Search()
  7. {
  8. //切换第一页
  9. this.PagerInfo.CurrentPageIndex = 1;
  10.  
  11. //转换下分页信息
  12. ConvertPagingInfo();
  13. //查询更新
  14. await GetData();
  15. }

这样我们在前面介绍的文本框的TextBox的InputBindings处理就可以直接使用这个RelayCommand声明的命令函数了。

  1. <TextBox.InputBindings>
  2. <KeyBinding Key="Enter" Command="{Binding ViewModel.SearchCommand}" />
  3. </TextBox.InputBindings>

当然,这个基类里面还可以定义其他通用用到的一些常规处理,如导入导出、删除和批量删除等常规的接口。

而我们在这个用户业务模型里面所需要做的就是做一下继承关系的处理即可,如下代码所示。

  1. namespace WHC.SugarProject.WpfUI.ViewModels;
  2.  
  3. /// <summary>
  4. /// 用户列表-视图模型对象
  5. /// </summary>
  6. public partial class UserListViewModel : BaseListViewModel<UserInfo, int, UserPagedDto>
  7. {
  8. /// <summary>
  9. /// 构造函数
  10. /// </summary>
  11. /// <param name="service">业务服务接口</param>
  12. public UserListViewModel(IUserService service) : base(service)
  13. {
  14. }
  15. }

这样 UserListViewModel 就具有了一些通用的业务处理对象和命令了,包括常规的查询、删除、批量删除、导入、导出的处理,都可以了。

同理,对于编辑具体单个记录的处理,我们也可以使用通用的抽象业务类封装来实现,如下代码所示。

  1. /// <summary>
  2. /// 用户新增、编辑-视图模型
  3. /// </summary>
  4. public partial class UserEditViewModel : BaseEditViewModel<UserInfo, int, UserPagedDto>
  5. {
  6. /// <summary>
  7. /// 构造函数
  8. /// </summary>
  9. /// <param name="service"></param>
  10. public UserEditViewModel(IUserService service) : base(service)
  11. {
  12. this.Title = "用户信息";
  13. }
  14. }

支持,整个视图模型ViewModel的继承关系如下所示。对于不同的业务类,我们也只需要根据实际情况,生成对应的业务视图模型类即可。

3、界面的统一处理和代码生成处理

对于界面上的处理,我们常规的列表和编辑/新增界面,基本上可以满足大多数的要求,如下是列表界面的效果,包括查询条件、常规处理按钮、列表展示、分页信息展示等几个部分,如下所示。

对于一些编辑界面,也是类似下面的布局即可。

当然可以根据数据库字段进行设置展示那些输入信息最好了。如对于系统参数设置模块的信息,我们界面如下所示。

这些内容相对比较标准和统一,可以结合数据库表信息使用统一的方式快速生成即可,因此我把它的生成规则结合到我们的代码生成工具(Database2Sharp)中进行生成处理,通过选择那些字段来实现更加精确的界面处理。

视图界面代码如下所示,包括Xaml和后端类的代码。

对应有两个视图模型ViewModel的继承类。

这样实现,可以极大程度的减少子类的代码,以及通过代码生成工具快速定义生成的方式,又可以极大的提高开发效率,双管齐下,可以提高整个项目的开发效率。

循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(2)的更多相关文章

  1. 基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发

    我喜欢在一个项目开发模式成熟的时候,使用代码生成工具Database2Sharp来配套相关的代码生成,对于我介绍的基于SqlSugar的开发框架,从整体架构确定下来后,我就着手为它们量身定做相关的代码 ...

  2. 推荐一个基于Vue2.0的的一款移动端开发的UI框架,特别好用。。。

    一丶YDUI 一只注重审美,且性能高效的移动端&微信UI. 下面为地址自己研究去吧! 我的项目正在用,以前用的Mint-ui但是现在感觉还是这个好一点,官方给出的解释很清楚,很实用. 官方地址 ...

  3. 基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理

    我们在设计数据库表的时候,往往为了方便,主键ID一般采用字符串类型或者GUID类型,这样对于数据库表记录的迁移非常方便,而且有时候可以在处理关联记录的时候,提前对应的ID值.但有时候进行数据记录插入的 ...

  4. 基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转

    在前面随笔,我们介绍过这个基于SqlSugar的开发框架,我们区分Interface.Modal.Service三个目录来放置不同的内容,其中Modal是SqlSugar的映射实体,Interface ...

  5. 基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注入用户身份信息接口

    在基于SqlSugar的开发框架中,我们设计了一些系统服务层的基类,在基类中会有很多涉及到相关的数据处理操作的,如果需要跟踪具体是那个用户进行操作的,那么就需要获得当前用户的身份信息,包括在Web A ...

  6. 基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录

    在我们对数据进行重要修改调整的时候,往往需要跟踪记录好用户操作日志.一般来说,如对重要表记录的插入.修改.删除都需要记录下来,由于用户操作日志会带来一定的额外消耗,因此我们通过配置的方式来决定记录那些 ...

  7. 基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理

    在早期的随笔就介绍过,把常规页面的内容拆分为几个不同的组件,如普通的页面,包括列表查询.详细资料查看.新增资料.编辑资料.导入资料等页面场景,这些内容相对比较独立,而有一定的代码量,本篇随笔介绍基于V ...

  8. 基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用

    在我们实际项目开发过程中,往往需要根据实际情况,对组件进行封装,以更简便的在界面代码中使用,在实际的前端应用中,适当的组件封装,可以减少很多重复的界面代码,并且能够非常简便的使用,本篇随笔介绍基于El ...

  9. 基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用

    刚完成一些前端项目的开发,腾出精力来总结一些前端开发的技术点,以及继续完善基于SqlSugar的开发框架循序渐进介绍的系列文章,本篇随笔主要介绍一下基于Vue3+TypeScript的全局对象的注入和 ...

  10. 基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理

    在一个应用系统的开发框架中,往往很多地方需要用到缓存的处理,有些地方是为了便于记录用户的数据,有些地方是为了提高系统的响应速度,如有时候我们在发送一个短信验证码的时候,可以在缓存中设置几分钟的过期时间 ...

随机推荐

  1. 记录一些不知道哪里冒出来的 idea(可能已存在)

    2022/7/4 给定一张 \(n\) 个点的有权无向图和 \(m\) 个关键点,求其生成树满足每个点到离它最近的关键点的距离之和最小. 输出该生成树边权和. 加强一下: 给出一个 \(N\) 个点 ...

  2. RT_Device

    以上图片来自网页,非原创

  3. SQL Server 2008/2012 完整数据库备份+差异备份+事务日志备份 数据库完整还原(一)

    还原方案 数据库级(数据库完整还原) 还原和恢复整个数据库.数据库在还原和恢复操作期间会处于离线状态.SQL SERVER不允许用户备份或还原单个表.还原方案是指从一个或多个备份中还原数据.继而恢复数 ...

  4. 数位DP?记忆化罢了!

    我看了半天的数位 DP,DP 没学会,人倒是麻了. 解决什么 一般用于求解给你一个区间 \([l,r]\),问你其中满足条件的数有多少个. 这种题目还是蛮常见的,我们一般情况下暴力只能拿一少部分分,之 ...

  5. 深入理解Go语言接口

    1. 引言 接口是一种定义了软件组件之间交互规范的重要概念,其促进了代码的解耦.模块化和可扩展性,提供了多态性和抽象的能力,简化了依赖管理和替换,方便进行单元测试和集成测试.这些特性使得接口成为构建可 ...

  6. 看懂java序列化,这篇就够了

    前言 相信大家日常开发中,经常看到 Java 对象 "implements Serializable".那么,它到底有什么用呢?本文带你全方位的解读序列化与反序列化这一块知识点. ...

  7. 【后端面经-Java】AQS详解

    目录 1. AQS是什么? 2. AQS核心思想 2.1 基本框架 2.1.1 资源state 2.1.2 CLH双向队列 2.2 AQS模板 3. 源码分析 3.1 acquire(int) 3.1 ...

  8. 固定型思维 VS 成长型思维

    回顾进入职场工作以来,对比曾经的学生时代,如果让我讲一个对自己影响最大的改变,那就是思维模式的一个转变. 具体来说,就是从一个典型的固定型思维转变成一个具备有成长型思维的人. 当然,我不敢妄称自己已经 ...

  9. HTML前端js

    ajax请求方法书写 $.ajax({ type:"POST", url:CONTEXT_PATH+"/appAudit/insertSnDocCountAdmin&qu ...

  10. 【Mybatis】学习

    Mybatis 学习 环境搭建 pom.xml <!--log4j--> <dependency> <groupId>org.slf4j</groupId&g ...