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

我们需要两个页面

  • 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. 从零开始配置vim(27)——代码片段

    我们之前介绍过缩写相关的内容,缩写是可以自动帮我们将缩写的单词展开成一段完整的话.但是代码本身是结构话的,仅仅使用缩写来配置是无法完成自动生成代码这个步骤的.好在我们大量的插件来进行配置.本篇我们将要 ...

  2. TienChin 新建业务菜单

    首先是移动菜单,参考下图将菜单移动到下图结构: 我这里将系统监控,系统工具都移动到了系统管理下面,并且排了个序,将多级菜单放在了一起,这样看起来更加的清晰. 修改一下系统管理(100)与TienChi ...

  3. 【SpringBoot】当AOP引发的异常与@RestControllerAdvice擦肩而过:异常处理的盲点揭秘

    各位上午/下午/晚上好呀! 今天在写bug的时候发现一个这样的问题: AOP抛出的异常竟然没有被@RestControllerAdvice注解修饰的异常统一处理类处理. 有一个需求,对某些加了自定义注 ...

  4. Rsync+Inotify 实现数据同步

    Rsync 是UNIX及类UNIX-Like平台下一款强大的数据镜像备份软件,它不像FTP或其他文件传输服务那样需要进行全备份,Rsync 可以根据数据的变化进行差异备份,从而减少数据流量,提高工作效 ...

  5. 《熬夜整理》保姆级系列教程-玩转Wireshark抓包神器教程(1)-初识Wireshark

    1.简介 前边已经介绍过两款抓包工具,应该是够用了,也能够处理在日常工作中遇到的问题了,但是还是有人留言让宏哥要讲解讲解Wireshark这一款抓包工具,说实话宏哥之前也没有用过这款工具,只能边研究边 ...

  6. php生成唯一订单号,高并发下不重复

    //生成唯一订单号 function create_trade_no($prefix='dd') { return $prefix . date('YmdHis', time()) . substr( ...

  7. 使用Dapr和.NET 6.0进行微服务实战系列

    大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的垫脚石,让我们一起精进. 本文是<使用Dapr和.NET 6.0进行微服务实战>的第1篇引言部分 ...

  8. 在PWM控制下的直流有刷电机性能优化

    结论 为了避免各位浪费时间, 先说结论: 选择合适的电机驱动模式和PWM频率, 能大幅提升直流电机的性能和可控性, 在常见的48:1减速电机上, 使用慢衰减模式和低于100Hz的PWM频率, 能达到最 ...

  9. dpt-shell 抽取壳实现原理分析(加壳逻辑)

    开源项目位置(为大佬开源精神点赞) https://github.com/luoyesiqiu/dpt-shell 抽取壳分为两个步骤 加壳逻辑: 一 对apk进行解析,将codeItem抽出到一个文 ...

  10. 【Android逆向】apk 反编译

    1. Kali搭建apktool环境 1. 访问apktool 官网https://ibotpeaches.github.io/Apktool/install/ 参考红圈里的步骤处理即可 2. 执行命 ...