很开心,终于到了创建页面的时候了!

我们需要两个页面

  • 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层的更多相关文章

  1. 将Abp移植进.NET MAUI项目(一):搭建项目

    ​ 前言 去年12月份做了MAUI混合开发框架的调研,想起来文章里给自己挖了个坑,要教大家如何把Abp移植进Maui项目,由于篇幅限制,将分为三个章节. 将Abp移植进.NET MAUI项目(一):搭 ...

  2. [MAUI 项目实战] 手势控制音乐播放器(一): 概述与架构

    这是一篇系列博文.请关注我,学习更多.NET MAUI开发知识! [MAUI 项目实战] 手势控制音乐播放器(一): 概述与架构 [MAUI 项目实战] 手势控制音乐播放器(二): 手势交互 [MAU ...

  3. 【转】 linux内核移植和驱动添加(三)

    原文网址:http://blog.chinaunix.net/uid-29589379-id-4708909.html 原文地址:linux内核移植和驱动添加(三) 作者:genehang 四,LED ...

  4. 如何用ABP框架快速完成项目(5) - 用ABP一个人快速完成项目(1) - 使用代码生成器

    用ABP一个人快速完成项目有如下要点: 站在巨人的肩膀上 - 使用代码生成器 站在巨人的肩膀上 - 使用成熟控件框架, 一个框架不够就上两个, 两个不够就上三个 通过微服务模式而不是盖楼式来避免难度升 ...

  5. 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和商业许可证.           新版 ...

  6. 在Tomcat下部属项目三种方式:

    在Tomcat下部属项目三种方式:       1直接复制:       2. 通过配置虚拟路径的方式    直接修改配置文件 写到tomcat/conf/server.xml     找到<H ...

  7. 如何用ABP框架快速完成项目(6) - 用ABP一个人快速完成项目(2) - 使用多个成熟控件框架

    正如我在<office365的开发者训练营,免费,在微软广州举办>课程里面所讲的, 站在巨人的肩膀上的其中一项就是, 尽量使用别人成熟的框架. 其中也包括了控件框架   abp和52abp ...

  8. [译]ABP vNext微服务演示,项目状态和路线图

    译注: ABP的主要负责人hikalkan最近又发布了一篇博客, 说明了ABP vNext的微服务演示,项目状态和路线图.其中特意对ABP的中文社区进行了感谢! 本文翻译自该博客文章(https:// ...

  9. android 实践项目三

    android 实践项目三 本周我主要完成的任务是将代码进行整合,然后实现百度地图的定位与搜索功能.在这次实现的 图形界面如下: 在本周的工作中主要的实现出来定位与收索的功能,在地图中能实现了定位,显 ...

  10. crm 系统项目(三) 自动分页

    crm 系统项目(三) 自动分页 需求: 1. 做一个自动分页, 每15条数据1页 2. 让当前页数在中间显示 3. 上一页, 下一页 注意情况: 1.总页数 小于 规定显示的页数 2. 左右两边极值 ...

随机推荐

  1. go中channel源码剖析

    channel 前言 设计的原理 共享内存 csp channel channel的定义 源码剖析 环形队列 创建 写入数据 读取数据 channel的关闭 优雅的关闭 M个receivers,一个s ...

  2. idea破解《当脚本破解方式无效或不方便执行时可采用此方法》

    idea新版破解有时会各种不成功,很耽误事.所以,再次整理一个相对省事有效的办法.<此方式为修改idea启动脚本破解方式>内容如下: 一:下载此激活工具 二:按下图从下载的文件中找到箭头标 ...

  3. 小知识:RMAN基于某个具体时间点的恢复示例

    最近帮忙基于某个时间点恢复一个库,说是备份和归档是全的. 好多年没做过这类事情了,不过这算是最基本的DBA技能,下面给出RMAN基于某个具体时间点的恢复示例脚本: run{ allocate chan ...

  4. 《ASP.NET Core 微服务实战》-- 读书笔记(第5章)

    第 5 章 创建数据服务 选择一种数据存储 由于我坚持要尽可能的跨平台,所以我决定选用 Postgres,而不用 SQL Server 以照顾 Linux 或 Mac 电脑的读者 构建 Postgre ...

  5. C# 重绘图片.图片加字,加矩形,加圆,加线,根据XY坐标修改RGB

    using System; using System.Drawing; using System.Drawing.Drawing2D; using System.IO; using System.Ne ...

  6. 【译】发布 .NET Aspire 预览版 2(一)

    原文 | Damian Edwards 翻译 | 郑子铭 自上个月宣布并推出 .NET Aspire 以来,我们收到的反馈非常惊人!通过问题和拉取请求对回购协议的参与一直激励着团队.我们正在深入了解开 ...

  7. Skywalking-Aop Docker单机环境搭建

    1.OAP-SERVER和UI环境搭建 本次搭建是基于MySQL进行持久化,因此需要提前准备好一个MySQL容器 (MySQL容器部署略过).如有错误还请指正. 1.1 OAP服务搭建 拉取skywa ...

  8. 大数运算(BigInteger)与进制转换

    1 前言 Java 提供了 BigInteger(大整数)类和 BigDecimal(大浮点数)类用于大数运算,这两个类都继承自 Number 类(抽象类).由于 BigInteger 在大数运算中更 ...

  9. 《深入理解Java虚拟机》(七) volatile 变量

    目录 概述 一.内存模型 物理机内存模型 Java内存模型 Java内存模型中有如下的规定: 操作 二.Volatile变量 volatile修改变量后保证所有线程对其可见性 volatile禁止指令 ...

  10. 代码+案例,实战解析BeautifulSoup4

    本文分享自华为云社区<从HTML到实战:深入解析BeautifulSoup4的爬虫奇妙世界>,作者:柠檬味拥抱. 网络上的信息浩如烟海,而爬虫技术正是帮助我们从中获取有用信息的重要工具.在 ...