循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(2)

简介: 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(2)

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

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

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

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WHC.SugarProject.WpfUI.Views.Pages"
    x:Class="WHC.SugarProject.WpfUI.Views.Pages.UserListPage"
    d:DataContext="{d:DesignInstance local:UserListPage,
                                     IsDesignTimeCreatable=False}"
    d:DesignHeight="450"
    d:DesignWidth="800"
    mc:Ignorable="d">

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

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

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

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

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

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

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

 

d:DesignHeight="450"
    d:DesignWidth="800"

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

 

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

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

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WHC.SugarProject.WpfUI.Views.Pages"
    xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
    xmlns:helpers="clr-namespace:WHC.SugarProject.WpfUI.Helpers"
    xmlns:hc="https://handyorg.github.io/handycontrol"
    xmlns:Controls="clr-namespace:WHC.SugarProject.WpfUI.Controls"
    x:Class="WHC.SugarProject.WpfUI.Views.Pages.UserListPage"
    d:DataContext="{d:DesignInstance local:UserListPage,
                                     IsDesignTimeCreatable=False}"
    d:DesignHeight="450"
    d:DesignWidth="800"
    mc:Ignorable="d">

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

<hc:UniformSpacingPanel
    Margin="0,0,0,20"
    HorizontalAlignment="Right"
    Orientation="Horizontal"
    Spacing="10">
    <Button
        Width="60"
        Height="40"
        Command="{Binding SaveCommand}"
        Content="保存"
        Style="{StaticResource ButtonPrimary}" />
    <Button
        Width="60"
        Height="40"
        Command="{Binding ViewModel.BackCommand}"
        CommandParameter="test"
        Content="关闭" />
</hc:UniformSpacingPanel>

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

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

 

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

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

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

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

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

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

namespace WHC.SugarProject.WpfUI.Views.Pages;
/// <summary>
/// UserListPage.xaml 交互逻辑
/// </summary>
public partial class UserListPage : INavigableView<UserListViewModel>
{
    /// <summary>
    /// 视图模型对象
    /// </summary>
    public UserListViewModel ViewModel { get; }
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="viewModel">视图模型对象</param>
    public UserListPage(UserListViewModel viewModel)
    {
        ViewModel = viewModel;
        DataContext = this;
        InitializeComponent();
    }
}

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

其中INavigableView接口的定义如下。

public interface INavigableView<out T>
{
    /// <summary>
    /// 视图的 ViewModel
    /// </summary>
    T ViewModel { get; }
}

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

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

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

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

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

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

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

/// <summary>
    /// 触发查询处理命令
    /// </summary>
    /// <returns></returns>
    [RelayCommand]
    private async Task Search()
    {
        //切换第一页
        this.PagerInfo.CurrentPageIndex = 1;
        //转换下分页信息
        ConvertPagingInfo();
        //查询更新
        await GetData();
    }

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

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

/// <summary>
    /// 根据分页和查询条件查询,请求数据
    /// </summary>
    /// <returns></returns>
    public virtual async Task GetData()
    {
        var result = await service.GetListAsync(this.PageDto);
        if (result != null)
        {
            this.Items = result.Items?.ToList();
            this.PagerInfo.RecordCount = result.TotalCount;
        }
    }

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

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

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

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

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

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

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

/// <summary>
    /// 通用基础操作接口
    /// </summary>
    protected IMyCrudService<TEntity, TKey, TGetListInput> service { get; set; }
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="service">操作接口</param>
    /// <param name="navigationService">视图导航服务接口</param>
    public BaseListViewModel(IMyCrudService<TEntity, TKey, TGetListInput> service)
    {
        this.service = service;
    }

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

/// <summary>
    /// 触发的分页处理命令
    /// </summary>
    /// <param name="info"></param>
    /// <returns></returns>
    [RelayCommand]
    private async Task PageUpdated(FunctionEventArgs<int> info)
    {
        //根据分页页码展示
        this.PagerInfo.CurrentPageIndex = info.Info;
        //转换下分页信息
        ConvertPagingInfo();
        //查询更新
        await GetData();
    }

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

<hc:Pagination
    Margin="0,10,0,10"
    DataCountPerPage="{Binding ViewModel.PagerInfo.PageSize}"
    IsJumpEnabled="True"
    MaxPageCount="{Binding ViewModel.PagerInfo.MaxPageCount}"
    MaxPageInterval="5"
    PageIndex="{Binding ViewModel.PagerInfo.CurrentPageIndex}">
    <hc:Interaction.Triggers>
        <hc:EventTrigger EventName="PageUpdated">
            <hc:EventToCommand Command="{Binding ViewModel.PageUpdatedCommand}" PassEventArgsToCommand="True" />
        </hc:EventTrigger>
    </hc:Interaction.Triggers>
</hc:Pagination>

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

/// <summary>
    /// 触发查询处理命令
    /// </summary>
    /// <returns></returns>
    [RelayCommand]
    private async Task Search()
    {
        //切换第一页
        this.PagerInfo.CurrentPageIndex = 1;
        //转换下分页信息
        ConvertPagingInfo();
        //查询更新
        await GetData();
    }

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

 

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

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

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

namespace WHC.SugarProject.WpfUI.ViewModels;
/// <summary>
/// 用户列表-视图模型对象
/// </summary>
public partial class UserListViewModel : BaseListViewModel<UserInfo, int, UserPagedDto>
{
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="service">业务服务接口</param>
    public UserListViewModel(IUserService service) : base(service)
    {
    }
}

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

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

/// <summary>
/// 用户新增、编辑-视图模型
/// </summary>
public partial class UserEditViewModel : BaseEditViewModel<UserInfo, int, UserPagedDto>
{
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="service"></param>
    public UserEditViewModel(IUserService service) : base(service)
    {
        this.Title = "用户信息";
    }
}

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

DataGrid列表展示的处理代码,主要就是基类触发查询更新后,属性ViewModel.Items 的记录获得更新,这样DataGrid就可以顺利的显示对应的数据了,如下界面所示。

 

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

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

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

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

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

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

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

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

专注于代码生成工具、.Net/.NetCore 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架等框架产品。
 转载请注明出处:撰写人:伍华聪  http://www.iqidi.com

相关文章
|
3月前
|
C# 开发者 Windows
WPF 应用程序开发:一分钟入门
本文介绍 Windows Presentation Foundation (WPF),这是一种用于构建高质量、可缩放的 Windows 桌面应用程序的框架,支持 XAML 语言,方便 UI 设计与逻辑分离。文章涵盖 WPF 基础概念、代码示例,并深入探讨常见问题及解决方案,包括数据绑定、控件样式与模板、布局管理等方面,帮助开发者高效掌握 WPF 开发技巧。
166 65
|
4月前
|
C# UED 开发者
WPF与性能优化:掌握这些核心技巧,让你的应用从卡顿到丝滑,彻底告别延迟,实现响应速度质的飞跃——从布局到动画全面剖析与实例演示
【8月更文挑战第31天】本文通过对比优化前后的方法,详细探讨了提升WPF应用响应速度的策略。文章首先分析了常见的性能瓶颈,如复杂的XAML布局、耗时的事件处理、不当的数据绑定及繁重的动画效果。接着,通过具体示例展示了如何简化XAML结构、使用后台线程处理事件、调整数据绑定设置以及利用DirectX优化动画,从而有效提升应用性能。通过这些优化措施,WPF应用将更加流畅,用户体验也将得到显著改善。
252 1
|
4月前
|
安全 C# 数据安全/隐私保护
WPF安全加固全攻略:从数据绑定到网络通信,多维度防范让你的应用固若金汤,抵御各类攻击
【8月更文挑战第31天】安全性是WPF应用程序开发中不可或缺的一部分。本文从技术角度探讨了WPF应用面临的多种安全威胁及防护措施。通过严格验证绑定数据、限制资源加载来源、实施基于角色的权限管理和使用加密技术保障网络通信安全,可有效提升应用安全性,增强用户信任。例如,使用HTML编码防止XSS攻击、检查资源签名确保其可信度、定义安全策略限制文件访问权限,以及采用HTTPS和加密算法保护数据传输。这些措施有助于全面保障WPF应用的安全性。
54 0
|
4月前
|
C# 开发者 Windows
全面指南:WPF无障碍设计从入门到精通——让每一个用户都能无障碍地享受你的应用,从自动化属性到焦点导航的最佳实践
【8月更文挑战第31天】为了确保Windows Presentation Foundation (WPF) 应用程序对所有用户都具备无障碍性,开发者需关注无障碍设计原则。这不仅是法律要求,更是社会责任,旨在让技术更人性化,惠及包括视障、听障及行动受限等用户群体。
85 0
|
4月前
|
测试技术 C# 开发者
“代码守护者:详解WPF开发中的单元测试策略与实践——从选择测试框架到编写模拟对象,全方位保障你的应用程序质量”
【8月更文挑战第31天】单元测试是确保软件质量的关键实践,尤其在复杂的WPF应用中更为重要。通过为每个小模块编写独立测试用例,可以验证代码的功能正确性并在早期发现错误。本文将介绍如何在WPF项目中引入单元测试,并通过具体示例演示其实施过程。首先选择合适的测试框架如NUnit或xUnit.net,并利用Moq模拟框架隔离外部依赖。接着,通过一个简单的WPF应用程序示例,展示如何模拟`IUserRepository`接口并验证`MainViewModel`加载用户数据的正确性。这有助于确保代码质量和未来的重构与扩展。
95 0
|
4月前
|
前端开发 C# 设计模式
“深度剖析WPF开发中的设计模式应用:以MVVM为核心,手把手教你重构代码结构,实现软件工程的最佳实践与高效协作”
【8月更文挑战第31天】设计模式是在软件工程中解决常见问题的成熟方案。在WPF开发中,合理应用如MVC、MVVM及工厂模式等能显著提升代码质量和可维护性。本文通过具体案例,详细解析了这些模式的实际应用,特别是MVVM模式如何通过分离UI逻辑与业务逻辑,实现视图与模型的松耦合,从而优化代码结构并提高开发效率。通过示例代码展示了从模型定义、视图模型管理到视图展示的全过程,帮助读者更好地理解并应用这些模式。
102 0
|
4月前
|
容器 C# Docker
WPF与容器技术的碰撞:手把手教你Docker化WPF应用,实现跨环境一致性的开发与部署
【8月更文挑战第31天】容器技术简化了软件开发、测试和部署流程,尤其对Windows Presentation Foundation(WPF)应用程序而言,利用Docker能显著提升其可移植性和可维护性。本文通过具体示例代码,详细介绍了如何将WPF应用Docker化的过程,包括创建Dockerfile及构建和运行Docker镜像的步骤。借助容器技术,WPF应用能在任何支持Docker的环境下一致运行,极大地提升了开发效率和部署灵活性。
134 0
|
4月前
|
存储 C# 关系型数据库
“云端融合:WPF应用无缝对接Azure与AWS——从Blob存储到RDS数据库,全面解析跨平台云服务集成的最佳实践”
【8月更文挑战第31天】本文探讨了如何将Windows Presentation Foundation(WPF)应用与Microsoft Azure和Amazon Web Services(AWS)两大主流云平台无缝集成。通过具体示例代码展示了如何利用Azure Blob Storage存储非结构化数据、Azure Cosmos DB进行分布式数据库操作;同时介绍了如何借助Amazon S3实现大规模数据存储及通过Amazon RDS简化数据库管理。这不仅提升了WPF应用的可扩展性和可用性,还降低了基础设施成本。
89 0
|
4月前
|
vr&ar C# 图形学
WPF与AR/VR的激情碰撞:解锁Windows Presentation Foundation应用新维度,探索增强现实与虚拟现实技术在现代UI设计中的无限可能与实战应用详解
【8月更文挑战第31天】增强现实(AR)与虚拟现实(VR)技术正迅速改变生活和工作方式,在游戏、教育及工业等领域展现出广泛应用前景。本文探讨如何在Windows Presentation Foundation(WPF)环境中实现AR/VR功能,通过具体示例代码展示整合过程。尽管WPF本身不直接支持AR/VR,但借助第三方库如Unity、Vuforia或OpenVR,可实现沉浸式体验。例如,通过Unity和Vuforia在WPF中创建AR应用,或利用OpenVR在WPF中集成VR功能,从而提升用户体验并拓展应用功能边界。
73 0
|
4月前
|
区块链 C# 存储
链动未来:WPF与区块链的创新融合——从智能合约到去中心化应用,全方位解析开发安全可靠DApp的最佳路径
【8月更文挑战第31天】本文以问答形式详细介绍了区块链技术的特点及其在Windows Presentation Foundation(WPF)中的集成方法。通过示例代码展示了如何选择合适的区块链平台、创建智能合约,并在WPF应用中与其交互,实现安全可靠的消息存储和检索功能。希望这能为WPF开发者提供区块链技术应用的参考与灵感。
66 0