在前面隨筆《循序漸進介紹基於CommunityToolkit.Mvvm 和HandyControl的WPF應用端開發(1)》中介紹了Mvvm 的開發,以及一些界面效果,本篇隨筆繼續深入探討基於CommunityToolkit.Mvvm 和HandyControl的WPF應用端開發,介紹如何整合Sql... ...
在前面隨筆《循序漸進介紹基於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