将Abp移植进.NET MAUI项目(三):构建UI层
很开心,终于到了创建页面的时候了!
我们需要两个页面
- MainPage 主页面
- MusicItemPage 条目编辑页面
编写主页面
新建一个MainPageViewModel.cs,作为MainPage的ViewModel层
public class MainPageViewModel : ViewModelBase
{
private readonly IRepository<Song, long> songRepository;
public MainPageViewModel(IRepository<Song, long> songRepository)
{
this.RefreshCommand=new Command(Refresh, (o) => true);
this.DeleteCommand=new Command(Delete, (o) => true);
this.songRepository=songRepository;
}
private void Delete(object obj)
{
songRepository.Delete(obj as Song);
}
private async void Refresh(object obj)
{
this.IsRefreshing=true;
var getSongs = this.songRepository.GetAllListAsync();
await getSongs.ContinueWith(r => IsRefreshing=false);
var songs = await getSongs;
this.Songs=new ObservableCollection<Song>(songs);
}
private ObservableCollection<Song> songs;
public ObservableCollection<Song> Songs
{
get { return songs; }
set
{
songs = value;
RaisePropertyChanged();
}
}
private Song currentSong;
public Song CurrentSong
{
get { return currentSong; }
set
{
currentSong = value;
RaisePropertyChanged();
}
}
private bool _isRefreshing;
public bool IsRefreshing
{
get { return _isRefreshing; }
set
{
_isRefreshing = value;
RaisePropertyChanged();
}
}
public Command RefreshCommand { get; set; }
public Command DeleteCommand { get; private set; }
}

新建一个MainPage页面

编写Xaml为:
注意这个页面将继承MauiBoilerplate.ContentPageBase
<?xml version="1.0" encoding="utf-8" ?>
<mato:ContentPageBase xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mato="clr-namespace:MauiBoilerplate;assembly=MauiBoilerplate.Core"
x:Class="MauiBoilerplate.MainPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="155"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Label Text="My Music" FontSize="65"></Label>
<ListView
Grid.Row="1"
ItemsSource="{Binding Songs,Mode=TwoWay}"
x:Name="MainListView"
RowHeight="74"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding IsRefreshing}"
RefreshCommand="{Binding RefreshCommand}"
SelectedItem="{Binding CurrentSong,Mode=TwoWay}">
<ListView.Header>
<Grid HeightRequest="96">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Clicked="AddButton_Clicked"
CornerRadius="100"
Text=""
HeightRequest="44"
WidthRequest="200"
FontFamily="FontAwesome"
></Button>
<StackLayout VerticalOptions="End"
Margin="0,0,0,8"
Grid.Row="1"
HorizontalOptions="Center"
Orientation="Horizontal">
<Label HorizontalTextAlignment="Center"
FontSize="Small"
Text="{Binding Songs.Count}"></Label>
<Label HorizontalTextAlignment="Center"
FontSize="Small"
Text="首歌"></Label>
</StackLayout>
</Grid>
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid x:Name="ModeControlLayout"
VerticalOptions="CenterAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackLayout Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<Label
Text="{Binding MusicTitle}"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="Center"
FontSize="Body"
/>
<Label
Text="{Binding Artist}"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="Center"
FontSize="Body"
/>
</StackLayout>
<Button
x:Name="MoreButton"
HeightRequest="44"
WidthRequest="44"
Margin="10"
Text=""
Clicked="SongMoreButton_OnClicked"
FontFamily="FontAwesome"
Grid.Column="1"
CornerRadius="100"
HorizontalOptions="Center" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</mato:ContentPageBase>

编写CodeBehind为:
注意将它继承ITransientDependency接口
这个页面之前提到过,已经通过IocManager.Resolve(typeof(MainPage))解析出实例并赋值给App.MainPage了。
public partial class MainPage : ContentPageBase, ITransientDependency
{
private readonly MainPageViewModel mainPageViewModel;
private readonly MusicItemPageViewModel musicItemPageViewModel;
private readonly MusicItemPage musicItemPage;
public MainPage(MainPageViewModel mainPageViewModel, MusicItemPageViewModel musicItemPageViewModel, MusicItemPage musicItemPage)
{
InitializeComponent();
this.mainPageViewModel=mainPageViewModel;
this.musicItemPageViewModel=musicItemPageViewModel;
this.musicItemPage=musicItemPage;
BindingContext=this.mainPageViewModel;
}
protected override void OnAppearing()
{
base.OnAppearing();
mainPageViewModel.RefreshCommand.Execute(null);
}
private async void SongMoreButton_OnClicked(object sender, EventArgs e)
{
var currentsong = (sender as BindableObject).BindingContext as Song;
string action = await DisplayActionSheet(currentsong.MusicTitle, "取消", null, "修改", "删除");
if (action=="修改")
{
musicItemPageViewModel.CurrentSong = currentsong;
await Navigation.PushModalAsync(musicItemPage);
}
else if (action=="删除")
{
mainPageViewModel.DeleteCommand.Execute(currentsong);
mainPageViewModel.RefreshCommand.Execute(null);
}
}
private async void AddButton_Clicked(object sender, EventArgs e)
{
musicItemPageViewModel.CurrentSong = new Song();
await Navigation.PushModalAsync(musicItemPage);
}
}

此页面将显示一个列表,并在列表条目下可以弹出一个菜单


编写条目编辑页面
新建一个MusicItemPageViewModel.cs,作为MusicItemPage的ViewModel层
public class MusicItemPageViewModel : ViewModelBase
{
private readonly IIocResolver iocResolver;
private readonly IRepository<Song, long> songRepository;
public event EventHandler OnFinished;
public MusicItemPageViewModel(
IIocResolver iocResolver,
IRepository<Song, long> songRepository)
{
this.CommitCommand=new Command(Commit, (o) => CurrentSong!=null);
this.iocResolver=iocResolver;
this.songRepository=songRepository;
this.PropertyChanged+=MusicItemPageViewModel_PropertyChanged;
}
private void MusicItemPageViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName==nameof(CurrentSong))
{
CommitCommand.ChangeCanExecute();
}
}
private void Commit(object obj)
{
songRepository.InsertOrUpdate(currentSong);
}
private Song currentSong;
public Song CurrentSong
{
get { return currentSong; }
set
{
currentSong = value;
RaisePropertyChanged();
}
}
}

新建一个MusicItemPage 页面
编写Xaml为:
注意这个页面将继承MauiBoilerplate.ContentPageBase
<?xml version="1.0" encoding="utf-8" ?>
<mato:ContentPageBase xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mato="clr-namespace:MauiBoilerplate;assembly=MauiBoilerplate.Core"
x:Class="MauiBoilerplate.MusicItemPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="155"></RowDefinition>
</Grid.RowDefinitions>
<TableView Intent="Form">
<TableRoot>
<TableSection Title="基础">
<EntryCell Label="标题" Text="{Binding CurrentSong.MusicTitle, Mode=TwoWay}"/>
<EntryCell Label="艺术家" Text="{Binding CurrentSong.Artist, Mode=TwoWay}"/>
<EntryCell Label="专辑" Text="{Binding CurrentSong.Album, Mode=TwoWay}"/>
</TableSection>
<TableSection Title="其他">
<EntryCell Label="时长" Text="{Binding CurrentSong.Duration}"/>
<EntryCell Label="发布日期" Text="{Binding CurrentSong.ReleaseDate}"/>
</TableSection>
</TableRoot>
</TableView>
<Button x:Name="CommitButton"
Grid.Row="1"
CornerRadius="100"
HeightRequest="44"
WidthRequest="200"
Text=""
Command="{Binding CommitCommand}"
FontFamily="FontAwesome"
HorizontalOptions="Center" />
</Grid>
</mato:ContentPageBase>

编写CodeBehind为:
注意将它继承ITransientDependency接口
public partial class MusicItemPage : ContentPageBase, ITransientDependency
{
private readonly MusicItemPageViewModel musicItemPageViewModel;
public MusicItemPage(MusicItemPageViewModel musicItemPageViewModel)
{
InitializeComponent();
this.musicItemPageViewModel=musicItemPageViewModel;
this.musicItemPageViewModel.OnValidateErrors+=MusicItemPageViewModel_OnValidateErrors;
this.musicItemPageViewModel.OnFinished+=MusicItemPageViewModel_OnFinished;
BindingContext=this.musicItemPageViewModel;
Unloaded+=MusicItemPage_Unloaded;
}
private async void MusicItemPageViewModel_OnFinished(object sender, EventArgs e)
{
await this.Navigation.PopModalAsync();
}
private void MusicItemPage_Unloaded(object sender, EventArgs e)
{
musicItemPageViewModel.CurrentSong = null;
}
private async void MusicItemPageViewModel_OnValidateErrors(object sender, List<System.ComponentModel.DataAnnotations.ValidationResult> e)
{
var content = string.Join(',', e);
await DisplayAlert("请注意", content, "好的");
}
}

这个页面提供歌曲条目新增和编辑的交互功能

[可选]使用Abp校验数据功能
这个部分使用Abp的ValidationConfiguration功能校验表单数据,以展示Abp功能的使用
首先在MusicItemPageViewModel 构造函数中添加对IValidationConfiguration对象的注入

编辑
添加OnValidateErrors事件,并且在Page中订阅这个事件。此事件将在校验未通过时触发
MusicItemPageViewModel.cs中:
public event EventHandler<List<ValidationResult>> OnValidateErrors;

MusicItemPage.xaml.cs中:
this.musicItemPageViewModel.OnValidateErrors+=MusicItemPageViewModel_OnValidateErrors;

private async void MusicItemPageViewModel_OnValidateErrors(object sender, List<System.ComponentModel.DataAnnotations.ValidationResult> e)
{
var content = string.Join(',', e);
await DisplayAlert("请注意", content, "好的");
}

编写校验逻辑代码
MusicItemPageViewModel.cs中:
protected List<ValidationResult> GetValidationErrors(Song validatingObject)
{
List<ValidationResult> validationErrors = new List<ValidationResult>();
foreach (var validatorType in _configuration.Validators)
{
using (var validator = iocResolver.ResolveAsDisposable<IMethodParameterValidator>(validatorType))
{
var validationResults = validator.Object.Validate(validatingObject);
validationErrors.AddRange(validationResults);
}
}
return validationErrors;
}

Commit提交方法,改造如下:
当GetValidationErrors返回的校验错误列表中有内容时,将OnValidateErrors事件Invoke
private void Commit(object obj)
{
var validateErrors = GetValidationErrors(this.CurrentSong);
if (validateErrors.Count==0)
{
songRepository.InsertOrUpdate(currentSong);
this.OnFinished?.Invoke(this, EventArgs.Empty);
}
else
{
OnValidateErrors?.Invoke(this, validateErrors);
}
}

接下来在实体中定义校验规则,校验器将按照这些规则返回校验结果
public class Song : FullAuditedEntity<long>, IValidatableObject
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public override long Id { get; set; }
[Required]
[StringLength(6, ErrorMessage = "歌曲名称要在6个字以内")]
public string MusicTitle { get; set; }
[Required]
[StringLength(10, ErrorMessage = "歌曲名称要在10个字以内")]
public string Artist { get; set; }
[Required]
[StringLength(10, ErrorMessage = "歌曲名称要在10个字以内")]
public string Album { get; set; }
public TimeSpan Duration { get; set; }
public DateTime ReleaseDate { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (ReleaseDate != default && ReleaseDate>DateTime.Now)
{
yield return new ValidationResult("ReleaseDate不能大于当天",
new[] { nameof(ReleaseDate) });
}
}
}

运行,新建条目。当我们如下填写的时候,将会弹出提示框

iOS平台也测试通过

至此我们完成了所有的工作。
结束语
Abp是一个很好用的.Net开发框架,Abp库帮助我们抽象了整个项目以及更多的设计模式应用,其名称Asp Boilerplate,虽然有一个Asp在其中,但其功能不仅仅可以构建AspNet Core应用,
经过我们的探索用Abp构建了跨平台应用,同样它还可以用于Xamarin,Wpf甚至是WinForms这些基于桌面的应用。
欢迎参与讨论和转发。
项目地址
jevonsflash/maui-abp-sample (github.com)
将Abp移植进.NET MAUI项目(三):构建UI层的更多相关文章
- 将Abp移植进.NET MAUI项目(一):搭建项目
前言 去年12月份做了MAUI混合开发框架的调研,想起来文章里给自己挖了个坑,要教大家如何把Abp移植进Maui项目,由于篇幅限制,将分为三个章节. 将Abp移植进.NET MAUI项目(一):搭 ...
- [MAUI 项目实战] 手势控制音乐播放器(一): 概述与架构
这是一篇系列博文.请关注我,学习更多.NET MAUI开发知识! [MAUI 项目实战] 手势控制音乐播放器(一): 概述与架构 [MAUI 项目实战] 手势控制音乐播放器(二): 手势交互 [MAU ...
- 【转】 linux内核移植和驱动添加(三)
原文网址:http://blog.chinaunix.net/uid-29589379-id-4708909.html 原文地址:linux内核移植和驱动添加(三) 作者:genehang 四,LED ...
- 如何用ABP框架快速完成项目(5) - 用ABP一个人快速完成项目(1) - 使用代码生成器
用ABP一个人快速完成项目有如下要点: 站在巨人的肩膀上 - 使用代码生成器 站在巨人的肩膀上 - 使用成熟控件框架, 一个框架不够就上两个, 两个不够就上三个 通过微服务模式而不是盖楼式来避免难度升 ...
- Qt 4.5发布(最大的变动是换用LGPL许可证,移植进了苹果的Cocoa框架。之前的Qt只支持Carbon框架,现在的Qt 4.5两者都支持。单一源代码创建出支持32位或64位字节的Intel或PowerPC Mac二进制文件)
Nokia的开源Qt开发工具正式发布了4.5版.如前所述,Qt 4.5最大的变动是换用LGPL许可证,目前采用的三个许可证分别为LGPL/GPL和商业许可证. 新版 ...
- 在Tomcat下部属项目三种方式:
在Tomcat下部属项目三种方式: 1直接复制: 2. 通过配置虚拟路径的方式 直接修改配置文件 写到tomcat/conf/server.xml 找到<H ...
- 如何用ABP框架快速完成项目(6) - 用ABP一个人快速完成项目(2) - 使用多个成熟控件框架
正如我在<office365的开发者训练营,免费,在微软广州举办>课程里面所讲的, 站在巨人的肩膀上的其中一项就是, 尽量使用别人成熟的框架. 其中也包括了控件框架 abp和52abp ...
- [译]ABP vNext微服务演示,项目状态和路线图
译注: ABP的主要负责人hikalkan最近又发布了一篇博客, 说明了ABP vNext的微服务演示,项目状态和路线图.其中特意对ABP的中文社区进行了感谢! 本文翻译自该博客文章(https:// ...
- android 实践项目三
android 实践项目三 本周我主要完成的任务是将代码进行整合,然后实现百度地图的定位与搜索功能.在这次实现的 图形界面如下: 在本周的工作中主要的实现出来定位与收索的功能,在地图中能实现了定位,显 ...
- crm 系统项目(三) 自动分页
crm 系统项目(三) 自动分页 需求: 1. 做一个自动分页, 每15条数据1页 2. 让当前页数在中间显示 3. 上一页, 下一页 注意情况: 1.总页数 小于 规定显示的页数 2. 左右两边极值 ...
随机推荐
- 1.1 Windows驱动开发:配置驱动开发环境
在进行驱动开发之前,您需要先安装适当的开发环境和工具.首先,您需要安装Windows驱动开发工具包(WDK),这是一组驱动开发所需的工具.库.示例和文档.然后,您需要安装Visual Studio开发 ...
- 4.2 Inline Hook 挂钩技术
InlineHook 是一种计算机安全编程技术,其原理是在计算机程序执行期间进行拦截.修改.增强现有函数功能.它使用钩子函数(也可以称为回调函数)来截获程序执行的各种事件,并在事件发生前或后进行自定义 ...
- 如何在 macOS Sonoma 虚拟机中安装 VMware Tools
vmware-tools VMware Tools 简介 VMware Tools 中包含一系列服务和模块,可在 VMware 产品中实现多种功能,从而使用户能够更好地管理客户机操作系统,以及与客户机 ...
- 【C语言进阶】【小项目】实现一个通讯录【C语言知识点汇总项目】通过这个项目,掌握C语言重要知识点
[C语言进阶][小项目]实现一个通讯录[C语言知识点汇总项目]通过这个项目,掌握C语言重要知识点 欢迎来到#西城s的博客,今天,博主带着大家用C实现一个通讯录!干货满满不要错过噢! 作者: #西城s ...
- Nginx的反向代理做负载均衡
对于一个大型网站,随着网站的访问量快速增长,单台服务器很难再支撑起需要,所以我们会购置多个服务器来满足业务量的需求,然后再利用Nginx提供的反向代理功能,来实现多台服务器间的协作功能,提高网站的处理 ...
- 常用TS总结
自己常用的 TS 写法总结,应该会一直更新.可使用 TS在线编译 校验 TS 语法. 基本用法 普通 const num: number = 10 const isStop: boolean = fa ...
- ASP.NET Core分布式项目实战(运行Consent Page)--学习笔记
任务21:运行Consent Page 修改 Config.cs 中的 RequireConsent 为 true,这样登录的时候就会跳转到 Consent 页面 修改 ConsentControll ...
- 盘点Java集合(容器)概览,Collection和Map在开发中谁用的最多?
写在开头 在Java的世界里万物皆对象.但我认为是万物皆数据,世界由各种各样数据构建起来,我们通过程序去实现数据的增删改查.转入转出.加减乘除等等,不同语言的实现方式殊途同归.由此可见,数据对于程序语 ...
- NC15532 Happy Running
题目链接 题目 题目描述 Happy Running, an application for runners, is very popular in CHD. In order to lose wei ...
- SQL中为什么不要使用1=1?
最近看几个老项目的SQL条件中使用了1=1,想想自己也曾经这样写过,略有感触,特别拿出来说道说道. 编写SQL语句就像炒菜,每一种调料的使用都会影响菜品的最终味道,每一个SQL条件的加入也会影响查询的 ...