Maui学习笔记- SQLite简单使用案例02添加详情页

  • 我们继续上一个案例,实现一个可以修改当前用户信息功能。

    当用户点击某个信息时,跳转到信息详情页,然后可以点击编辑按钮导航到编辑页面。

创建项目

  • 我们首先在ViewModels目录下创建UserDetailViewModel。

  • 实现从详情信息页面导航到编辑页面。

  • 这里要使用一个字典来传输对象。

cs 复制代码
public partial class UserDetailViewModel:ObservableObject,IQueryAttributable
{
    [ObservableProperty] private User itemUser;

    public Func<User, Task> ParentRefreshAction { get;set; }

    [RelayCommand]
    async Task ShowEditFormAsync()
    {
        var context = new UserContext();
        var editedItem = context.Users.FirstOrDefault(u => u.Id == ItemUser.Id);

        await Shell.Current.GoToAsync(nameof(UserEditPage),
            parameters: new Dictionary<string, object>
            {
                { "ParentRefreshAction", (Func<User, Task>)ItemEditedAsync },
                { "Item", editedItem }
            });
    }
    
    async Task ItemEditedAsync(User user) {
        ItemUser = user;
        await ParentRefreshAction(user);
    }

    public virtual void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.TryGetValue("Item",out object currentItem))
        {
            ItemUser = (User)currentItem;
        }

        if (query.TryGetValue("ParentRefreshAction",out object parentRefreshAction))
        {
            ParentRefreshAction = (Func<User, Task>)parentRefreshAction;
        }
        query.Clear();
    }
}
  • 创建用户详情页面,用来显示用户的全部信息。

  • 在ToolbarItem中添加一个命令导航到编辑页面。

XML 复制代码
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:MauiApp3.ViewModels"
             Title="用户详情"
             x:Class="MauiApp3.Views.UserDetailPage">
    <ContentPage.BindingContext>
        <vm:UserDetailViewModel/>
    </ContentPage.BindingContext>
    <ContentPage.ToolbarItems>
        <ToolbarItem Text="编辑" Command="{Binding ShowEditFormCommand}"/>
    </ContentPage.ToolbarItems>
    <VerticalStackLayout>
        <Label Text="{Binding ItemUser.Id}" FontSize="Large"/>
        <Label Text="{Binding ItemUser.Name}" FontSize="Large"/>
        <Label Text="{Binding ItemUser.Phone}" FontSize="Large"/>
        <Label Text="{Binding ItemUser.Email}" FontSize="Large"/>
    </VerticalStackLayout>
</ContentPage>
  • 更新UserEditViewModel,我们让他直接继承自UserDetailViewModel.
cs 复制代码
public partial class UserEditViewModel:UserDetailViewModel
{
    [ObservableProperty] private bool isNewItem;
    

    [RelayCommand]
    private async Task SaveAsync()
    {
        await using var context = new UserContext();


        if (IsNewItem)
        {
            context.Users.Add(ItemUser);
        }
        else
        {
            context.Users.Attach(ItemUser);
            context.Entry(ItemUser).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
        }

        context.SaveChangesAsync();
        
        await ParentRefreshAction(ItemUser);
        
        await Shell.Current.GoToAsync("..");
    }
    
    public override void ApplyQueryAttributes(IDictionary<string, object> query) {
        if (query.TryGetValue("IsNewItem", out object isNew)) {
             IsNewItem = (bool)isNew;
        }
        base.ApplyQueryAttributes(query);
    }
}
  • 修改MainViewModel。
cs 复制代码
public partial class MainViewModel:ObservableObject
{
    [ObservableProperty]
    ObservableCollection<User> users;

    [ObservableProperty] private bool refreshing;

    [RelayCommand]
    private async Task LoadUsersAsync()
    {
        await Task.Run(() =>
        {
            using var context = new UserContext();

            Users = new ObservableCollection<User>(context.Users);
        });

        Refreshing = false;
    }

    [RelayCommand]
    private void Showing()
    {
        Refreshing = true;
    }

    [RelayCommand]
    private void DeleteUser(User user)
    {
        var context = new UserContext();

        context.Users.Remove(user);
        
        context.SaveChanges();
        
        Users.Remove(user);
    }

    [RelayCommand]
    private async Task ShowNewFormAsync()
    {
        await Shell.Current.GoToAsync(nameof(UserEditPage),parameters:new Dictionary<string, object>
        {
            {"ParentRefreshAction",(Func<User,Task>)RefreshAddedAsync},
            {"Item",new User()},
            {"IsNewItem",true}
        });
    }

    Task RefreshAddedAsync(User addedUser)
    {
        Users.Add(addedUser);
        return Task.CompletedTask;
    }

    [RelayCommand]
    async Task ShowDetailFormAsync(User user)
    {
        await Shell.Current.GoToAsync(nameof(UserDetailPage),parameters:new Dictionary<string, object>
        {
            {"ParentRefreshAction",(Func<User,Task>)RefreshEditedAsync},
            {"Item",user},
        });
    }

    async Task RefreshEditedAsync(User updataUser)
    {
        int editedItemIndex = -1;
        await Task.Run(() =>
        {
            editedItemIndex = Users.Select(
                    (user, index) => new { user, index })
                .First(x => x.user.Id == updataUser.Id)
                .index;
        });

        if (editedItemIndex==-1)
        {
            return;
        }
        Users[editedItemIndex] = updataUser;
    }
}
  • 注册路由
cs 复制代码
public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();
        Routing.RegisterRoute(nameof(UserEditPage),typeof(UserEditPage));
        Routing.RegisterRoute(nameof(UserDetailPage),typeof(UserDetailPage));
    }
}
  • 在主页添加GestureRecognizers,对详情页面的跳转
XML 复制代码
<Grid RowDefinitions="40,40"
      ColumnDefinitions="*,*"
      Padding="10">
    <Grid.GestureRecognizers>
        <TapGestureRecognizer
            Command="{Binding Path=BindingContext.ShowDetailFormCommand,Source={RelativeSource Mode=FindAncestor,AncestorType={x:Type ContentPage}}}"
            CommandParameter="{Binding}"/>
    </Grid.GestureRecognizers>
IOS下运行程序

我们实现了查看用户详情,并且可以修改

优化代码

  • 我们要在数据库和视图模型之间提供一个抽象层,它能使项目有不同的模块区分,更明确的分离开。

  • 在项目中往往需要对多张表进行操作,我们创建一个泛型接口,来抽象对数据库中CURD。

  • 在Models中添加一个IRepository.cs文件

cs 复制代码
public interface IRepository<T> where T:class
{
     Task<T> GetByIdAsync(int id);
     
     Task<IEnumerable<T>> GetAllAsync();
     
     Task AddAsync(T item);
     
     Task UpdateAsync(T item);
     
     Task DeleteAsync(T item);
    
}
  • 实现接口
cs 复制代码
public class UserRepository: IRepository<User>
{
    private readonly DbSet<User> DbSet;

    private readonly UserContext Context;
    
    public UserRepository(UserContext context)
    {
        Context = context;
        DbSet = Context.Set<User>();
    }

    public async Task<User> GetByIdAsync(int id)
    {
        return await Task.Run(() => DbSet.Find(id));
    }

    public async Task<IEnumerable<User>> GetAllAsync()
    {
        return await Task.Run(() => DbSet.ToList());
    }

    public async Task AddAsync(User item)
    {
        DbSet.Add(item);
        await Task.CompletedTask;
    }

    public async Task UpdateAsync(User item)
    {
        DbSet.Attach(item);
        Context.Entry(item).State = EntityState.Modified;
        await Task.CompletedTask;
    }

    public async Task DeleteAsync(User item)
    {
        DbSet.Remove(item);
        await Task.CompletedTask;
    }
  • 创建工作单元,它的作用主要是作用于不同的数据表。
cs 复制代码
public class DbUnitOfWork:IDisposable,IUnitOfWork<User>
{
    readonly UserContext Context=new UserContext();

    private IRepository<User> userRepository;
    
    public IRepository<User> Items => userRepository ??= new UserRepository(Context);
    
    public void Dispose()
    {
        Context.Dispose();
    }


    public async Task SaveAsync()
    {
        await Task.Run(() => Context.SaveChangesAsync());
    }
}

public interface IUnitOfWork<T> where T : class {
    IRepository<T> Items { get; } 
    Task SaveAsync(); 
}
修改MainViewModel
  • LoadUserAsync
cs 复制代码
[RelayCommand]
private async Task LoadUsersAsync()
{
    using var uniOfWork = new DbUnitOfWork();
    Users = new ObservableCollection<User>(await uniOfWork.Items.GetAllAsync());
    Refreshing = false;
}
  • DeleteUserAsync
cs 复制代码
[RelayCommand]
private async Task DeleteUser(User user)
{
    using var uniOfWork = new DbUnitOfWork();
    await uniOfWork.Items.DeleteAsync(user);
    await uniOfWork.SaveAsync();
    
    Users.Remove(user);
}
修改CustomerEditViewModel
  • SaveAsync
cs 复制代码
[RelayCommand]
private async Task SaveAsync()
{
    using var unitOfWork = new DbUnitOfWork();


    if (IsNewItem)
        await unitOfWork.Items.AddAsync(ItemUser);
    else
        await unitOfWork.Items.UpdateAsync(ItemUser);
    

    await unitOfWork.SaveAsync();
    
    await ParentRefreshAction(ItemUser);
    
    await Shell.Current.GoToAsync("..");
}
修改UserDetailViewModel
  • ShowEditFormAsync
cs 复制代码
[RelayCommand]
async Task ShowEditFormAsync()
{
    using var unitOfWork = new DbUnitOfWork();
    
    var editedItem = await unitOfWork.Items.GetByIdAsync(ItemUser.Id);

    await Shell.Current.GoToAsync(nameof(UserEditPage),
        parameters: new Dictionary<string, object>
        {
            { "ParentRefreshAction", (Func<User, Task>)ItemEditedAsync },
            { "Item", editedItem }
        });
}

数据库验证错误

  • 很多时候我们需要对用户输入的数据进行验证,有很多方法和形式,我们来看看在数据库层面如何做错误验证处理。并反馈在页面给用户。

  • 我们在UserContext中进行简单的数据约束

  • 使用try catch来捕获异常

cs 复制代码
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //用户邮箱唯一
    modelBuilder.Entity<User>().HasIndex(u => u.Email).IsUnique();
    //用户名不能为空
    modelBuilder.Entity<User>().Property(u => u.Name).IsRequired();
    //初始化数据
    modelBuilder.Entity<User>().HasData(new User
    {
        Id = 1,
        Name = "张三",
        Email = "张三@163.com",
        Phone = "123456789"
    });
    
    base.OnModelCreating(modelBuilder);
}
修改MainViewModel
  • DeleteCustomerAsync
cs 复制代码
[RelayCommand]
private async Task DeleteUserAsync(User user)
{
    using var uniOfWork = new DbUnitOfWork();
    try
    {
        await uniOfWork.Items.DeleteAsync(user);
        await uniOfWork.SaveAsync();
    }
    catch (Exception ex)
    {
        await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
        return;
    }

    Users.Remove(user);
}
修改UserEditViewModel
  • SaveAsync
cs 复制代码
[RelayCommand]
private async Task SaveAsync()
{
    using var unitOfWork = new DbUnitOfWork();

    try
    {
        if (IsNewItem)
            await unitOfWork.Items.AddAsync(ItemUser);
        else
            await unitOfWork.Items.UpdateAsync(ItemUser);
    

        await unitOfWork.SaveAsync();

    }
    catch (Exception ex)
    {
        await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
        return;
    }
    await ParentRefreshAction(ItemUser);
    
    await Shell.Current.GoToAsync("..");
}
IOS下运行程序
  • 这里如果用户没有添加用户名,程序会提出错误信息。

在UI中验证数据

  • 程序中,在将数据提交到数据库之前可以在UI层执行某些验证规则,可以在用户保存某些修改时告知用户,从而改善用户体验。
修改UserEditViewModel
  • 在编辑用户页面我们添加对用户名和邮箱的验证,并关联到保存命令上。
cs 复制代码
[NotifyCanExecuteChangedFor(nameof(SaveCommand))] [ObservableProperty]
private bool isEmailValid;

[NotifyCanExecuteChangedFor(nameof(SaveCommand))] [ObservableProperty]
private bool isNameValid;

bool CanSave() => IsEmailValid&& IsNameValid;


[RelayCommand(CanExecute = nameof(CanSave))]
private async Task SaveAsync()
{
    using var unitOfWork = new DbUnitOfWork();

    try
    {
        if (IsNewItem)
            await unitOfWork.Items.AddAsync(ItemUser);
        else
            await unitOfWork.Items.UpdateAsync(ItemUser);
    

        await unitOfWork.SaveAsync();

    }
    catch (Exception ex)
    {
        await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
        return;
    }
    await ParentRefreshAction(ItemUser);
    
    await Shell.Current.GoToAsync("..");
}
  • 在UserEditPage文件中,我们添加一个样式来反馈给用户,并使用工具包中的ValidationBehavior来进行验证和绑定命令。

  • 这个验证很简单,当用户输入的内容不满足条件时,会使用我们设置的样式颜色来显示,保存按钮无法点击,当满足条件时颜色变为正常并可以保存内容。

XML 复制代码
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:MauiApp3.ViewModels;assembly=MauiApp3"
             xmlns:toolkit = "http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             x:Class="MauiApp3.Views.UserEditPage"
             Title="新用户">
    <ContentPage.BindingContext>
        <vm:UserEditViewModel/>
    </ContentPage.BindingContext>
    <ContentPage.Resources>
        <Style TargetType="Entry" x:Key="invalidEntryStyle">
            <Setter Property="TextColor" Value="Red"></Setter>
        </Style>
    </ContentPage.Resources>
    
    <Grid>
        <VerticalStackLayout VerticalOptions="Start">
            <Entry Placeholder="用户名"
                   Text="{Binding ItemUser.Name}">
                <Entry.Behaviors>
                    <toolkit:TextValidationBehavior 
                        InvalidStyle="{StaticResource invalidEntryStyle}"
                        IsValid="{Binding IsNameValid}"
                        Flags="ValidateOnValueChanged,ValidateOnAttaching"
                        //内容长度不能小于5
                        MinimumLength="5"/>
                </Entry.Behaviors>
            </Entry>
            <Entry Placeholder="电话"
                   Text="{Binding ItemUser.Phone}"/>
            <Entry Placeholder="Email"
                   Text="{Binding ItemUser.Email}"
                   ReturnCommand="{Binding SaveCommand}">
                <Entry.Behaviors>
                  //验证是否时正常的邮箱格式
                    <toolkit:EmailValidationBehavior
                        InvalidStyle="{StaticResource invalidEntryStyle}"
                        IsValid="{Binding IsEmailValid}"
                        Flags="ValidateOnValueChanged,ValidateOnAttaching"/>
                </Entry.Behaviors>
            </Entry>
            
           
            <Button Text="保存" 
                    Command="{Binding SaveCommand}"/>
        </VerticalStackLayout>
        <ActivityIndicator 
            VerticalOptions="Center"
            HorizontalOptions="Center"
            IsRunning="{Binding SaveCommand.IsRunning}"/>
    </Grid>
</ContentPage>
IOS下运行程序
相关推荐
Erik_LinX4 分钟前
day1-->day7| 机器学习(吴恩达)学习笔记
笔记·学习·机器学习
魔理沙偷走了BUG5 分钟前
【Linux笔记】Day5
linux·笔记
索然无味io19 分钟前
组件框架漏洞
前端·笔记·学习·安全·web安全·网络安全·前端框架
珊瑚里的鱼34 分钟前
【单链表算法实战】解锁数据结构核心谜题——环形链表
数据结构·学习·程序人生·算法·leetcode·链表·visual studio
林涧泣35 分钟前
图的矩阵表示
学习·线性代数·矩阵
chimchim6642 分钟前
【starrocks学习】之catalog
学习
Jackilina_Stone2 小时前
【论文阅读笔记】“万字”关于深度学习的图像和视频阴影检测、去除和生成的综述笔记 | 2024.9.3
论文阅读·人工智能·笔记·深度学习·ai
梦云澜2 小时前
论文阅读(二):理解概率图模型的两个要点:关于推理和学习的知识
论文阅读·深度学习·学习
Ronin-Lotus2 小时前
上位机知识篇---CMake
c语言·c++·笔记·学习·跨平台·编译·cmake
lxl13072 小时前
学习数据结构(2)空间复杂度+顺序表
数据结构·学习