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

news2025/7/12 18:53:32
  • 我们继续上一个案例,实现一个可以修改当前用户信息功能。

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

创建项目 

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

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

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

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中添加一个命令导航到编辑页面。

<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.

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。

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;
    }
}
  • 注册路由

public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();
        Routing.RegisterRoute(nameof(UserEditPage),typeof(UserEditPage));
        Routing.RegisterRoute(nameof(UserDetailPage),typeof(UserDetailPage));
    }
}

 

  • 在主页添加GestureRecognizers,对详情页面的跳转

<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文件

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);
    
}
  • 实现接口

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;
    }
  • 创建工作单元,它的作用主要是作用于不同的数据表。

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

[RelayCommand]
private async Task LoadUsersAsync()
{
    using var uniOfWork = new DbUnitOfWork();
    Users = new ObservableCollection<User>(await uniOfWork.Items.GetAllAsync());
    Refreshing = false;
}
  • DeleteUserAsync

[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

[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

[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来捕获异常

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

[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

[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
  • 在编辑用户页面我们添加对用户名和邮箱的验证,并关联到保存命令上。

[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来进行验证和绑定命令。

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

  • <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下运行程序 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2286727.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Autogen_core 测试代码:test_cache_store.py

目录 原始代码测试代码代码中用到的typing注解 原始代码 from typing import Dict, Generic, Optional, Protocol, TypeVarT TypeVar("T")class CacheStore(Protocol, Generic[T]):"""This protocol defines the basic interface for store/cache o…

变压器的漏感

测量变压器漏感的时候需要将次级绕组短路&#xff1a; 测量变压器初级线圈的电感方法很简单&#xff0c;直接用LCR测量就可&#xff0c;无需像测量漏感那样将次级绕组短接&#xff1a;

从ChatGPT热潮看智算崛起

2025年1月7日&#xff0c;科智咨询发布《2025年IDC产业七大发展趋势》&#xff0c;其中提到“ChatGPT开启生成式AI热潮&#xff0c;智能算力需求暴涨&#xff0c;算力供给结构发生转变”。 【图片来源于网络&#xff0c;侵删】 为何会以ChatGPT发布为节点呢&#xff1f;咱们一起…

攻克 AI 幻觉难题

当下&#xff0c;AI 已经成为我们生活中不可或缺的一部分。无论是智能语音助手&#xff0c;还是对话式的AI模型&#xff0c;它们凭借强大的算法和海量的数据&#xff0c;为我们答疑解惑、出谋划策。 然而&#xff0c;小编今天向AI提问&#xff1a;上山打老虎。他却回答&#x…

格式化时间的插件

1.安装dayjs包 npm i dayjs 2.组件中的应用

自创《艺术人生》浅析

艺术是生活的馈赠&#xff0c;艺术是苦痛的呻吟。 笔记模板由python脚本于2025-01-29 00:01:11创建&#xff0c;本篇笔记适合喜欢写诗读诗诵诗的coder翻阅。 【学习的细节是欢悦的历程】 博客的核心价值&#xff1a;在于输出思考与经验&#xff0c;而不仅仅是知识的简单复述。 …

防御保护第一次实验:安全策略配置

一、实验拓扑 二、实验要求 三、需求分析 1.创建两个vlan 2.在ENSP中配置基于时间的ACL实现对于办公区PC访问OA Server的时间限制&#xff08;工作日早8到晚6&#xff09;。 3.通过配置基于MAC地址的ACL来实现对于生产区PC访问Web Server的限制&#xff08;除PC3外不能访问&am…

【Pytest】生成html报告中,中文乱码问题解决方案

链接上一篇文章:https://blog.csdn.net/u013080870/article/details/145369926?spm1001.2014.3001.5502 中文乱码问题&#xff0c;python3&#xff0c;Python3.7后&#xff0c;还一个文件就是result.py 因为中文可以在内容中&#xff0c;也可能在文件名&#xff0c;类名&…

【ollama通过命令行启动后如何在网页端查看运行】

ollama通过命令行启动后如何在网页端查看运行 http://localhost:11434/

Android createScaledBitmap与Canvas通过RectF drawBitmap生成马赛克/高斯模糊(毛玻璃)对比,Kotlin

Android createScaledBitmap与Canvas通过RectF drawBitmap生成马赛克/高斯模糊&#xff08;毛玻璃&#xff09;对比&#xff0c;Kotlin import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas import android.graphics.RectF …

Jetpack Compose 和 Compose Multiplatform 还有 KMP 的关系

今天刚好看到官方发布了一篇文章&#xff0c;用于讨论 Compose Multiplatform 和 Jetpack Compose 之间的区别&#xff0c;突然想起之前评论区经常看到说 “Flutter 和 CMP 对于 Google 来说项目重叠的问题”&#xff0c;刚好可以放一起聊一聊。 最近写的几篇内容写的太干&…

python生成图片和pdf,快速

1、下载安装 pip install imgkit pip install pdfkit2、wkhtmltopdf工具包&#xff0c;下载安装 下载地址&#xff1a;https://wkhtmltopdf.org/downloads.html 3、生成图片 import imgkit path_wkimg rD:\app\wkhtmltopdf\bin\wkhtmltoimage.exe # 工具路径&#xff0c;安…

解锁FPGA的故障免疫密码

我们身处“碳基智能”大步迈向“硅基智能”序曲中,前者更像是后者的引导程序,AI平民化时代,万物皆摩尔定律。 越快越好,几乎适用绝大多数场景。 在通往人工智能的征程中,算力无处不在,芯片作用无可替代。 十六年前,就已宣称自己是一家软件公司的英伟达,现已登顶全球…

【数据结构】初识链表

顺序表的优缺点 缺点&#xff1a; 中间/头部的插入删除&#xff0c;时间复杂度效率较低&#xff0c;为O(N) 空间不够的时候需要扩容。 如果是异地扩容&#xff0c;增容需要申请新空间&#xff0c;拷贝数据&#xff0c;释放旧空间&#xff0c;会有不小的消耗。 扩容可能会存在…

【hot100】刷题记录(6)-轮转数组

题目描述&#xff1a; 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转…

如何移植ftp服务器到arm板子?

很多厂家提供的sdk&#xff0c;一般都不自带ftp服务器功能&#xff0c; 需要要发人员自己移植ftp服务器程序。 本文手把手教大家如何移植ftp server到arm板子。 环境 sdk&#xff1a;复旦微 Buildroot 2018.02.31. 解压 $ mkdir ~/vsftpd $ cp vsftpd-3.0.2.tar.gz ~/vs…

深度学习 Pytorch 神经网络的损失函数

本节开始将以分类神经网络为例&#xff0c;展示神经网络的学习和训练过程。在介绍PyTorch的基本工具AutoGrad库时&#xff0c;我们系统地介绍过数学中的优化问题和优化思想&#xff0c;我们介绍了最小二乘法以及梯度下降法这两个入门级优化算法的具体操作&#xff0c;并使用Aut…

C++ 中用于控制输出格式的操纵符——setw 、setfill、setprecision、fixed

目录 四种操纵符简要介绍 setprecision基本用法 setfill的基本用法 fixed的基本用法 setw基本用法 以下是一些常见的用法和示例&#xff1a; 1. 设置字段宽度和填充字符 2. 设置字段宽度和对齐方式 3. 设置字段宽度和精度 4. 设置字段宽度和填充字符&#xff0c;结合…

996引擎 - NPC-添加NPC引擎自带形象

996引擎 - NPC-添加NPC引擎自带形象 截图参考添加NPC参考资料截图参考 添加NPC 编辑NPC表:Envir\DATA\cfg_npclist.xls 1.1. 需要临时隐藏NPC时可以在id前加 // 1.2. 如果NPC朝向不对,可以调整dir 列。(按8方向,上是0顺时针数。我这里给的4) 1.3. 形象代码:NPC代码、怪物…

深度研究新范式:通过Ollama和DeepSeek R1实现自动化研究

引言 在信息时代&#xff0c;海量数据的产生与传播速度前所未有地加快&#xff0c;这既为研究者提供了丰富的资源&#xff0c;也带来了信息筛选与处理的巨大挑战。 传统研究方法往往依赖于研究者的个人知识库、文献检索技能以及时间投入&#xff0c;但面对指数级增长的数据量…