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

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

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

创建项目 

  • 我们首先在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下运行程序 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

arkui-x跨平台与android java联合开发

华为鸿蒙系统采用的是arkts&#xff0c;支持跨平台crossplatform 即前端为arkts&#xff0c;arkui-x框架&#xff0c;后端为其他的语言框架。 本篇示例后端采用的是java&#xff0c;android studio工程。 主要方式是前端鸿蒙完成界面元素、布局等效果&#xff0c;后面androi…

Unity敌人逻辑笔记

写ai逻辑基本上都需要状态机。因为懒得手搓状态机&#xff0c;所以选择直接用动画状态机当逻辑状态机用。 架构设计 因为敌人的根节点已经有一个animator控制动画&#xff0c;只能增加一个子节点AI&#xff0c;给它加一个animator指向逻辑“动画”状态机。还有一个脚本&#…

ts 基础核心

吴悠讲编程 : 20分钟学会TypeScript 无废话速成TS https://www.bilibili.com/video/BV1gX4y177Kf

BGP分解实验·11——路由聚合与条件性通告(3)

续接上&#xff08;2&#xff09;的实验。其拓扑如下&#xff1a; 路由聚合的负向也就是拆分&#xff0c;在有双出口的情况下&#xff0c;在多出口做流量分担是优选方法之一。 BGP可以根据指定来源而聚合路由&#xff0c;在产生该聚合路由的范围内的条目注入到本地BGP表后再向…

【leetcode】T1599

解题心得&#xff1a; 题目长且绕&#xff0c;直接看测试样例的解析有助于更快把握题目核心需求&#xff08;即关注样例的输入、运算逻辑、输出&#xff09; 题面 原题链接1599. 经营摩天轮的最大利润 - 力扣&#xff08;LeetCode&#xff09; AC代码 class Solution { pub…

Ansible自动化运维实战--通过role远程部署nginx并配置(8/8)

文章目录 1、准备工作2、创建角色结构3、编写任务4、准备配置文件&#xff08;金甲模板&#xff09;5、编写变量6、编写处理程序7、编写剧本8、执行剧本Playbook9、验证-游览器访问每台主机的nginx页面 在 Ansible 中&#xff0c;使用角色&#xff08;Role&#xff09;来远程部…

关于opencv环境搭建问题:由于找不到opencv_worldXXX.dll,无法执行代码,重新安装程序可能会解决此问题

方法一&#xff1a;利用复制黏贴方法 打开opencv文件夹目录找到\opencv\build\x64\vc15\bin 复制该目录下所有文件&#xff0c;找到C:\Windows\System32文件夹&#xff08;注意一定是C盘&#xff09;黏贴至该文件夹重新打开VS。 方法二&#xff1a;直接配置环境 打开opencv文…

Linux(19)——使用正则表达式匹配文本

新年快乐&#xff01; 目录 一、正则表达式&#xff1a; 二、通过 grep 匹配正则表达式&#xff1a; 三、查找匹配项&#xff1a; 一、正则表达式&#xff1a; 正则表达式使用模式匹配机制查找特定内容&#xff0c;vim、grep 和 less 命令都可以使用正则表达式&#xff0c;P…

蓝牙技术在物联网中的应用有哪些

蓝牙技术凭借低功耗、低成本和易于部署的特性&#xff0c;在物联网领域广泛应用&#xff0c;推动了智能家居、工业、医疗、农业等多领域发展。 智能家居&#xff1a;在智能家居系统里&#xff0c;蓝牙技术连接各类设备&#xff0c;像智能门锁、智能灯泡、智能插座、智能窗帘等。…

使用 Confluent Cloud 的 Elasticsearch Connector 部署 Elastic Agent

作者&#xff1a;来自 Elastic Nima Rezainia Confluent Cloud 用户现在可以使用更新后的 Elasticsearch Sink Connector 与 Elastic Agent 和 Elastic Integrations 来实现完全托管且高度可扩展的数据提取架构。 Elastic 和 Confluent 是关键的技术合作伙伴&#xff0c;我们很…

【数据结构】初识链表

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

deepseek R1的确不错,特别是深度思考模式

deepseek R1的确不错&#xff0c;特别是深度思考模式&#xff0c;每次都能自我反省改进。比如我让 它写文案&#xff1a; 【赛博朋克版程序员新春密码——2025我们来破局】 亲爱的代码骑士们&#xff1a; 当CtrlS的肌肉记忆遇上抢票插件&#xff0c;当Spring Boot的…

Python爬虫之——Cookie存储器

目录 专栏导读1、背景介绍2、库的安装3、核心代码4、完整代码总结 专栏导读 &#x1f338; 欢迎来到Python办公自动化专栏—Python处理办公问题&#xff0c;解放您的双手 &#x1f3f3;️‍&#x1f308; 博客主页&#xff1a;请点击——> 一晌小贪欢的博客主页求关注 &…

VMware 中Ubuntu无网络连接/无网络标识解决方法【已解决】

参考文档 Ubuntu无网络连接/无网络标识解决方法_ubuntu没网-CSDN博客 再我们正常使用VMware时&#xff0c;就以Ubuntu举例可能有时候出现无网络连接&#xff0c;甚至出现无网络标识的情况&#xff0c;那么废话不多说直接上教程 环境&#xff1a;无网络 解决方案&#…

CSS(快速入门)

欢迎大家来到我的博客~欢迎大家对我的博客提出指导&#xff0c;有错误的地方会改进的哦~点击这里了解更多内容 目录 一、什么是CSS?二、基本语法规范三、CSS选择器3.1 标签选择器3.2 id选择器3.3 class选择器3.4 通配符选择器3.5 复合选择器 四、常用CSS样式4.1 color4.2 font…

网络安全攻防实战:从基础防护到高级对抗

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 引言 在信息化时代&#xff0c;网络安全已经成为企业、政府和个人必须重视的问题。从数据泄露到勒索软件攻击&#xff0c;每一次…

PETSc源码分析: Optimization Solvers

本文结合PETSc源代码&#xff0c;分析PETSc中的优化求解器。 注1&#xff1a;限于研究水平&#xff0c;分析难免不当&#xff0c;欢迎批评指正。 注2&#xff1a;文章内容会不定期更新。 参考文献 Balay S. PETSc/TAO Users Manual, Revision 3.22. Argonne National Labora…

单细胞-第四节 多样本数据分析,下游画图

文件在单细胞\5_GC_py\1_single_cell\2_plots.Rmd 1.细胞数量条形图 rm(list ls()) library(Seurat) load("seu.obj.Rdata")dat as.data.frame(table(Idents(seu.obj))) dat$label paste(dat$Var1,dat$Freq,sep ":") head(dat) library(ggplot2) lib…

基于Arcsoft的人脸识别

目录 一、前言 二、使用方法 三、获取SDK 四、人脸检测/人脸识别 五、代码实现 一、前言 face++,百度ai,虹软,face_recognition,其中除了face_recognition是python免费的一个库安装好响应的库直接运行就好,另外三个需要填入相关申请的信息id和key。 分别对应着相应的人…

计算机网络之链路层

本文章目录结构出自于《王道计算机考研 计算机网络_哔哩哔哩_bilibili》 02 数据链路层 在网上看到其他人做了详细的笔记&#xff0c;就不再多余写了&#xff0c;直接参考着学习吧。 1 详解数据链路层-数据链路层的功能【王道计算机网络笔记】_wx63088f6683f8f的技术博客_51C…