【Avalonia UI】1.0 入门和安装

Avalonia UI学习

我是Java工程师出身,以前端工程师为主,学过基本的语法的C#。
因此学习Avalonia UI会有一种熟悉的陌生人的感觉,看得懂,但写不明白,甚至不敢写,按照官网的路子,发现自己很容易写成一坨大便。

因此在其他平台学习Avalonia UI开发的精髓后,在这里文章记录如下。

Avalonia UI学习的曲线,有点和普通循序渐进的曲线相反,一开始就会很难而且官网讲不明白。

需要从MVVM入手,但MVVM本来就很抽象, Avalonia UI+.net更加抽象了一层,要求入门学习的时候,首先不得不触碰到这个技术栈核心的设计思想。

MVVM

View:负责展示数据,用户界面。
ViewModel:作为中介者,负责数据绑定、命令处理和状态管理。
Model:负责数据的存储和业务逻辑。

开发一个桌面级应用,就要解决3个问题,

  • 怎么数据存储和业务逻辑编写。
  • 怎么处理数据,连接展示和数据库层面。
  • 怎么展示。

官网或者第三方ui库资料,讲得最多的就是怎么一行代码就写完一个界面样式。然后一些绑定等操作,但实际上从一个工程化的角度来说,欠缺一种如臂使唤的感觉。

开发Avalonia UI思路

  • 安装.net SDK( 9.0 )
  • 安装Rider(你也可以用别的,只要你喜欢)
  • 执行dotnet new install Avalonia.Templates 安装 Avalonia 模板
  • 打开Rider创建Avalonia MVVM模板。
  • 安装UI
  • 理解并实现Model
  • 理解并实现ViewModel
  • 理解并实现View
  • 跨平台打包

到这里基本就可以利用Avalonia UI自由开发跨平台桌面级别应用了。

安装.NET SDK

https://dotnet.microsoft.com/zh-cn/download

image.png

双击安装,没法选安装位置,直接下一步到位。
image.png

image.png

安装完验证

dotnet -h
# .NET SDK 版本
dotnet --version
# .NET SDK 的列表
dotnet --list-sdks
# .NET 运行时的列表
dotnet --list-runtimes
# .NET 安装和计算机环境详细信息
dotnet --info

安装 Avalonia 依赖

# 安装 Avalonia 模板
dotnet new install Avalonia.Templates
# 列出已安装的模板
dotnet new list
image.png

dotnet 命令

https://learn.microsoft.com/zh-cn/dotnet/core/tools/dotnet

dotnet new
dotnet new -h

列出要使用 dotnet new 运行的可用模板

dotnet new list

在 NuGet.org 上搜索 dotnet new 支持的模板

dotnet new search console

安装模板包

dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates

卸载模板包

# 列出已安装的模板及其详细信息
dotnet new --uninstall
# 卸载 WebAssembly 模板包
dotnet new uninstall Microsoft.NET.Runtime.WebAssembly.Templates

更新已安装的模板包

dotnet new update -h
# 检查已安装模板包更新
dotnet new update --check-only
# 更新已安装模板包
dotnet new update
dotnet build

生成项目及其所有依赖项

dotnet build -h

dotnet run

无需任何显式编译或启动命令即可运行源代码

dotnet run -h
dotnet run --no-build

dotnet tool
dotnet tool -h
# 列出全局或本地安装的工具
dotnet tool list

创建 Avalonia 项目

通过Rider创建项目,解决方案名称和项目名称,建议保持大小写全英文写法,不要写下划线或者中划线等,其他写法指不定之后会存在什么问题。

  • Avalonia .NET App
    默认采用代码后台模式,将UI逻辑(如事件处理)直接写在视图(如.axaml.cs文件)中,视图与业务逻辑耦合度高,适合小型或快速验证型项目。 示例:按钮点击事件直接在窗口的代码后台文件中实现,未分离UI与逻辑。

  • Avalonia .NET MVVM App
    预置MVVM模式的结构,强制分离视图(View)、视图模型(ViewModel)和模型(Model)。通过数据绑定实现视图与逻辑的解耦,便于单元测试和复杂UI交互的管理。 示例:按钮的Command绑定到ViewModel中的命令,业务逻辑完全独立于UI代码。

image.png

运行:


image.png

安装Semi.Avalonia

image.png

UI组件库我这里选型是Semi.Avalonia。
官网——安装使用文档
https://github.com/irihitech/Semi.Avalonia

Semi Avalonia 是完全独立的样式库,引用后你的程序不再需要使用FluentTheme或SimpleTheme。您可以放心地将它们移除。

ColorPicker, DataGrid 和 TreeDataGrid 由单独的nuget包分发,如果需要可按需安装。

dotnet add package Semi.Avalonia.ColorPicker
dotnet add package Semi.Avalonia.DataGrid
dotnet add package Semi.Avalonia.TreeDataGrid

安装好后,可以如下查看:


image.png

image.png

App.axaml调整如下:

<Application xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:semi="https://irihi.tech/semi" 
             x:Class="AvaloniaSemiBase.App"
             xmlns:local="using:AvaloniaSemiBase"
             RequestedThemeVariant="Default">
             <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->

    <Application.DataTemplates>
        <local:ViewLocator/>
    </Application.DataTemplates>
  
    <Application.Styles>
        <FluentTheme />
        <!-- 开启弹窗框动画 -->
        <semi:SemiTheme/>
        <semi:SemiPopupAnimations/>
        <StyleInclude Source="avares://Semi.Avalonia.ColorPicker/Index.axaml"/>
        <StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml"/>
        <StyleInclude Source="avares://Semi.Avalonia.TreeDataGrid/Index.axaml"/>
    </Application.Styles>
</Application>

xmlns="https://github.com/avaloniaui"- 这是 Avalonia UI 本身的 XAML 命名空间声明。这是必需的,没有它,文件将不会被识别为 Avalonia XAML 文档。

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"- 这是 XAML 语言命名空间的声明。

x:Class="AvaloniaApplication1.MainWindow"- 这是上述声明(用于 'x')的扩展,它告诉 XAML 编译器在何处查找此文件的关联类。该类在代码隐藏文件中定义,通常用 C# 编写。

Models

创建Class类。


image.png
namespace AvaloniaSemiBase.Models;

/**
 * 诗歌信息实体类
 */
public class Poetry
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

创建接口AvaloniaSemiBase/Services/IPoetryStorage.cs

namespace AvaloniaSemiBase.Services;

public interface IPoetryStorage
{
    
}

和Java规则不一样,Java是xxxService是接口,xxxServiceImpl接口实现类。
C#的约定方式是,Ixxx是接口,xxx 是接口实现类,为啥不一样,得搞点不一样才有优越感吧可能,其实没必要遵守,但遵守肯定更规范和维护性更高。

#
using System.Threading.Tasks;
using AvaloniaSemiBase.Models;

namespace AvaloniaSemiBase.Services;

public interface IPoetryStorage
{
    // 异步插入
    Task InsertAsync(Poetry poetry);
}

引用缺失快捷键alt+enter
点击接口名称那一行,会出现一个问号,可以点击问号,创建实现类。

image.png

或者按快捷键alt+enter,再创建,效果一致。

using System.Threading.Tasks;
using AvaloniaSemiBase.Models;

namespace AvaloniaSemiBase.Services;

public interface IPoetryStorage
{
    // 异步插入
    Task InsertAsync(Poetry poetry);
}

public class PoetryStorage : IPoetryStorage
{
}
image.png

同样,按红色灯泡,或者快捷键,讲实现类移动到单独的class文件中去。


image.png

image.png

选择默认或者第一个就行。


image.png

同样的操作,可以实现缺少的成员。


image.png
using System.Threading.Tasks;
using AvaloniaSemiBase.Models;

namespace AvaloniaSemiBase.Services;

public class PoetryStorage : IPoetryStorage
{
    public Task InsertAsync(Poetry poetry)
    {
        throw new System.NotImplementedException();
    }
}

SQLite

这里考虑2个嵌入式数据库SQLite和DuckDB,实际上DuckDB更适合我要做的应用需求,但这里先选用SQLite实现先。

SQLite,嵌入式数据库,随应用一起安装,应用开启它开启,应用关闭它关闭。

在项目上通过图形化界面安装。

image.png

取消勾选“预发布”,搜索 sqlite-net-plc:
image.png

duckdb有4个包

  1. 包的功能区别
  • DuckDB.NET.Bindings
    提供DuckDB的底层原生绑定(Native Bindings),是其他高级包的基础依赖。此包不包含任何预编译的DuckDB本地库(如.dll、.so等),需开发者自行处理目标平台的二进制文件依赖。
    适用场景:需要完全控制本地库版本或跨平台部署时手动管理依赖。

  • DuckDB.NET.Bindings.Full
    在Bindings的基础上,内置了所有平台的本地库(Windows、Linux、macOS等),开箱即用,无需额外配置。
    适用场景:快速启动项目,避免手动管理本地库。

  • DuckDB.NET.Data
    基于Bindings包的高级封装,提供类似ADO.NET的接口(如DuckDBConnection、DuckDBCommand),适合通过SQL操作数据库。此包依赖DuckDB.NET.Bindings,因此仍需处理本地库依赖。
    适用场景:需要直接使用SQL或类似传统数据库操作方式。

  • DuckDB.NET.Data.Full
    Data包的增强版,同时包含高级接口和所有平台的本地库,即集成DuckDB.NET.Bindings.Full的功能。
    适用场景:希望直接使用SQL且无需手动管理本地库的开发者。


    image.png

推荐安装方案

  • 默认推荐:DuckDB.NET.Data.Full
    该包同时满足以下需求:
    提供完整的ADO.NET接口,简化数据库操作(如ExecuteQuery、ExecuteNonQuery)
    内置多平台本地库,避免手动处理二进制文件依赖,特别适合Avalonia这类跨平台应用
  • 特殊情况选择
    若需深度控制本地库版本(例如特定优化或安全要求),可组合安装DuckDB.NET.Bindings + DuckDB.NET.Data,但需自行编译或下载对应平台的DuckDB二进制文件。
    若仅需底层API(如通过P/Invoke调用),则单独使用DuckDB.NET.Bindings或DuckDB.NET.Bindings.Full。
image.png

修改AvaloniaSemiBase/Services/PoetryStorage.cs

using System.Threading.Tasks;
using AvaloniaSemiBase.Models;

namespace AvaloniaSemiBase.Services;

public class PoetryStorage : IPoetryStorage
{
    /// <summary>
    /// 静态常亮确定数据库的名称
    /// </summary>
    public const string DbName = "poetrydb.sqlite3";
    public async Task InsertAsync(Poetry poetry)
    {
        throw new System.NotImplementedException();
    }
}

数据库文件放哪

一般情况,我们习惯把自己的软件安装在文件D盘下面。
但涉及到不同的系统,甚至windows系统,人家没有D盘,这些数据库文件要怎么安装?
比如windows,我们系统必备的一个路径。

C:\Users\用户名\AppData

代码调整如下:


image.png

本地文件存储路径帮助工具类
AvaloniaSemiBase/Helpers/PathHelper.cs

using System;
using System.IO;

namespace AvaloniaSemiBase.Helpers;

/// <summary>
/// 提供文件名,告知文件名全路径在该系统下的位置。
/// </summary>
public class PathHelper
{
    private static string _localFolder = string.Empty;

    private static string LocalFolder
    {
        get
        {
            if (!string.IsNullOrEmpty(_localFolder))
            {
                return _localFolder;
            }

            // Environment.GetFolderPath(...) 相当于
            // C:Users\xxx\AppData\Local\AvaloniaSemiBase\...
            // 或者Linux系统的 /home/xxx/.local/share/AvaloniaSemiBase/...
            _localFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                nameof(AvaloniaSemiBase));

            if (!Directory.Exists(_localFolder))
            {
                Directory.CreateDirectory(_localFolder);
            }

            return _localFolder;
        }
    }

    public static string GetLocalFilePath(string fileName)
    {
        //Path.Combine 等同于  LocalFolder+"\"+fileName,不同操作系统分隔符不一样。
        return Path.Combine(LocalFolder, fileName);
    }
}

AvaloniaSemiBase/Services/IPoetryStorage.cs

using System.Threading.Tasks;
using AvaloniaSemiBase.Models;

namespace AvaloniaSemiBase.Services;

public interface IPoetryStorage
{
// 定义一个异步方法,用于初始化数据库表
    Task InitializeAsync();
    // 异步插入
// 异步插入一首诗歌
    Task InsertAsync(Poetry poetry);
}

AvaloniaSemiBase/Services/PoetryStorage.cs

using System.Threading.Tasks;
using AvaloniaSemiBase.Helpers;
using AvaloniaSemiBase.Models;
using SQLite;

namespace AvaloniaSemiBase.Services;

public class PoetryStorage : IPoetryStorage
{
    /// <summary>
    /// 静态常亮确定数据库的名称
    /// </summary>
    private const string DbName = "poetrydb.sqlite3";

    // 数据库文件存储全路径
    private static readonly string PoetryDbPath =
        PathHelper.GetLocalFilePath(DbName);

    private SQLiteAsyncConnection _connection;

    private SQLiteAsyncConnection Connection =>
        _connection ??= new SQLiteAsyncConnection(PoetryDbPath);
    // 等价于
    // if(_connection==null) _connection = new SQLiteAsyncConnection(PoetryDbPath);
    // return _connection;

    public async Task InitializeAsync()
    {
        // 如果表不存在,会自动创建表
        await Connection.CreateTableAsync<Poetry>();
    }

    public async Task InsertAsync(Poetry poetry)
    {
        await Connection.InsertAsync(poetry);
    }
}

修改vm类
AvaloniaSemiBase/ViewModels/MainWindowViewModel.cs

using System.Windows.Input;
using AvaloniaSemiBase.Services;
using CommunityToolkit.Mvvm.Input;

namespace AvaloniaSemiBase.ViewModels;

public partial class MainWindowViewModel : ViewModelBase
{
    // public string Greeting { get; } = "Welcome to Avalonia!";
    private readonly IPoetryStorage _poetryStorage;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="poetryStorage">接口单例实现类</param>
    public MainWindowViewModel(IPoetryStorage poetryStorage)
    {
        _poetryStorage = poetryStorage;
        // 在 C# 的 MVVM(Model-View-ViewModel)模式中,
        // RelayCommand 是一种实现 ICommand 接口的类,
        // 用于将 UI 元素(如按钮)的操作逻辑解耦到 ViewModel 中
        SayHelloCommand = new RelayCommand(SayHello);
    }

    private string _message;

    public string Message
    {
        get => _message;
        // ref 指针引用
        set => SetProperty(field: ref _message, value);
    }

    public ICommand SayHelloCommand { get; }

    private void SayHello()
    {
        Message = "Hello, World!";
    }
}

AvaloniaSemiBase/Views/MainWindow.axaml

<!-- Window:就是view -->
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:AvaloniaSemiBase.ViewModels"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="AvaloniaSemiBase.Views.MainWindow"
        x:DataType="vm:MainWindowViewModel"
        Icon="/Assets/avalonia-logo.ico"
        Title="Avalonia基线平台"
        DataContext="{Binding MainWindowViewModel ,Source={StaticResource  ServiceLocator}}">
    <!-- DataContext="{Binding MainWindowViewModel ,Source={StaticResource  ServiceLocator}}" -->
    <!-- DataContext="{ReflectionBinding Source={StaticResource ServiceLocator},Path=MainWindowViewModel}" -->
    <!-- DataContext:从名为ServiceLocator的静态类中去找实体MainWindowViewModel -->

    <!-- view关联viewModel -->
    <!-- <Design.DataContext> -->
    <!-- vm:MainWindowViewModel 等价于 new MainWindowViewModel()-->
    <!-- <vm:MainWindowViewModel />只能用于没有构造参数的构造函数 -->
    <!-- <vm:MainWindowViewModel /> -->
    <!-- </Design.DataContext> -->

    <StackPanel>
        <TextBlock Text="{Binding Message}" HorizontalAlignment="Center" VerticalAlignment="Center" />
        <Button Content="爸爸打我" Command="{Binding SayHelloCommand}"></Button>
    </StackPanel>

</Window>

AvaloniaSemiBase/App.axaml

<Application xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:semi="https://irihi.tech/semi"
             x:Class="AvaloniaSemiBase.App"
             xmlns:local="using:AvaloniaSemiBase"
             RequestedThemeVariant="Light">
    <!-- “Default”主题变体遵循系统主题变体。还有“Dark”或“Light”可供选择。-->

    <!-- 全局的位置注册资源 -->
    <Application.Resources>
        <ResourceDictionary>
            <!-- 相当于 var ServiceLocator = new ServiceLocator(),,全局可用 -->
            <local:ServiceLocator x:Key="ServiceLocator"></local:ServiceLocator>
        </ResourceDictionary>
    </Application.Resources>
    
    <Application.DataTemplates>
        <local:ViewLocator />
    </Application.DataTemplates>

    <Application.Styles>
        <FluentTheme />
        <!-- 开启弹窗框动画 -->
        <semi:SemiTheme />
        <semi:SemiPopupAnimations />
        <StyleInclude Source="avares://Semi.Avalonia.ColorPicker/Index.axaml" />
        <StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" />
        <StyleInclude Source="avares://Semi.Avalonia.TreeDataGrid/Index.axaml" />
    </Application.Styles>
</Application>

AvaloniaSemiBase/App.axaml.cs

using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using System.Linq;
using Avalonia.Markup.Xaml;
using AvaloniaSemiBase.ViewModels;
using AvaloniaSemiBase.Views;

namespace AvaloniaSemiBase;

public partial class App : Application
{
    public override void Initialize()
    {
        AvaloniaXamlLoader.Load(this);
    }

    public override void OnFrameworkInitializationCompleted()
    {
        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
        {
            // Avoid duplicate validations from both Avalonia and the CommunityToolkit. 
            // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
            DisableAvaloniaDataAnnotationValidation();
            desktop.MainWindow = new MainWindow
            {
                // DataContext = new MainWindowViewModel(),
            };
        }

        base.OnFrameworkInitializationCompleted();
    }

    private void DisableAvaloniaDataAnnotationValidation()
    {
        // Get an array of plugins to remove
        var dataValidationPluginsToRemove =
            BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();

        // remove each entry found
        foreach (var plugin in dataValidationPluginsToRemove)
        {
            BindingPlugins.DataValidators.Remove(plugin);
        }
    }
}

AvaloniaSemiBase/ServiceLocator.cs

using System;
using AvaloniaSemiBase.Services;
using AvaloniaSemiBase.ViewModels;
using Microsoft.Extensions.DependencyInjection;

namespace AvaloniaSemiBase;

/// <summary>
/// 服务定位器类
/// </summary>
public class ServiceLocator
{
    private readonly IServiceProvider _serviceProvider;
    // 等价于
    // public MainWindowViewModel MainWindowViewModel
    // {
    //     get => _serviceProvider.GetRequiredService<MainWindowViewModel>();
    // }
    public MainWindowViewModel MainWindowViewModel =>
        _serviceProvider.GetRequiredService<MainWindowViewModel>();

   
    public ServiceLocator()
    {
        // 这里需要引入Microsoft.Extensions.DependencyInjection包,
        // 通过依赖关系注入,另一个类负责在运行时将依赖项注入到对象中。
        var serviceCollection = new ServiceCollection();

        // 注册需要使用的类型,存对象
        serviceCollection.AddSingleton<MainWindowViewModel>();
        // 接口和接口实现类
        serviceCollection.AddSingleton<IPoetryStorage, PoetryStorage>();

        // 建造者模式,ServiceProvider,就是取对象的东西
        _serviceProvider = serviceCollection.BuildServiceProvider();
    }
}

运行:

image.png

以上实现了数据库到service的过程,view到viewModel的过程,但还需要实现viewModel到service的过程。

优化实体类
AvaloniaSemiBase/Models/Poetry.cs

using SQLite;

namespace AvaloniaSemiBase.Models;

/**
 * 诗歌信息实体类
 */
public class Poetry
{
    // 主键,自增
    [PrimaryKey,AutoIncrement]
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

调整VM
AvaloniaSemiBase/ViewModels/MainWindowViewModel.cs

public partial class MainWindowViewModel : ViewModelBase
{
 ......

    public MainWindowViewModel(IPoetryStorage poetryStorage)
    {
   ......
        InsertCommand = new AsyncRelayCommand(InsertAsync);
    }

  ......
    // 等价于
    // public async Task InsertAsync()
    // {
    // 等价于
    // Poetry poetry = new Poetry();
    // poetry.Name = "Name" + new Random().Next();
    // await _poetryStorage.InsertAsync(poetry);
    // 等价于
    // Poetry poetry = new() { Name = "Name" + new Random().Next() };
    // await _poetryStorage.InsertAsync(poetry);
    // 等价于
    // await _poetryStorage.InsertAsync(new Poetry { Name = "Name" + new Random().Next() });
    // }
    // 定义一个异步方法InsertAsync,用于向_poetryStorage中插入一条新的Poetry记录
    // 调用_poetryStorage的InsertAsync方法,插入一条新的Poetry记录,其Name属性值为"Name"加上一个随机数
    public async Task InsertAsync() =>
        await _poetryStorage.InsertAsync(new Poetry { Name = "Name" + new Random().Next() });
    
    public ICommand InsertCommand { get; } 
}

修改AvaloniaSemiBase/Views/MainWindow.axaml

......
    <StackPanel>
        <TextBlock Text="{Binding Message}" HorizontalAlignment="Center" VerticalAlignment="Center" />
        <Button Content="爸爸打我" Command="{Binding SayHelloCommand}"></Button>
        <Button Content="初始化数据库" Command="{Binding InitializeCommand}"></Button>
        <Button Content="插入数据" Command="{Binding InsertCommand}"></Button>
    </StackPanel>
......

设置断点,运行,先点击“初始化数据库”,再点击“插入数据”。


image.png

image.png
image.png

放行,插入多点击几下:


image.png

查询实现

AvaloniaSemiBase/Services/IPoetryStorage.cs

using System.Collections.Generic;
using System.Threading.Tasks;
using AvaloniaSemiBase.Models;

namespace AvaloniaSemiBase.Services;

public interface IPoetryStorage
{
// 定义一个异步方法,用于初始化数据库表
    Task InitializeAsync();

    // 异步插入
// 异步插入一首诗歌
    Task InsertAsync(Poetry poetry);

     // 查询诗歌列表数据
     Task<IList<Poetry>> ListAsync();
     
     // 根据关键字查询诗歌
     Task<IList<Poetry>> QueryAsync(string keyword);
}

AvaloniaSemiBase/Services/PoetryStorage.cs

using System.Collections.Generic;
using System.Threading.Tasks;
using AvaloniaSemiBase.Helpers;
using AvaloniaSemiBase.Models;
using SQLite;

namespace AvaloniaSemiBase.Services;

public class PoetryStorage : IPoetryStorage
{
    /// <summary>
    /// 静态常亮确定数据库的名称
    /// </summary>
    private const string DbName = "poetrydb.sqlite3";

    // 数据库文件存储全路径
    private static readonly string PoetryDbPath =
        PathHelper.GetLocalFilePath(DbName);

    private SQLiteAsyncConnection _connection;

    private SQLiteAsyncConnection Connection =>
        _connection ??= new SQLiteAsyncConnection(PoetryDbPath);
    // 等价于
    // if(_connection==null) _connection = new SQLiteAsyncConnection(PoetryDbPath);
    // return _connection;

    public async Task InitializeAsync()
    {
        // 如果表不存在,会自动创建表
        await Connection.CreateTableAsync<Poetry>();
    }

    public async Task InsertAsync(Poetry poetry)
    {
        await Connection.InsertAsync(poetry);
    }

    public async Task<IList<Poetry>> ListAsync() => await Connection.Table<Poetry>().ToListAsync();

    // 根据name关键字查询某条数据
    // System.StringComparison.CurrentCultureIgnoreCase:使用当前文化环境以及忽略所比较字符串的大小写来比较字符串。
    public async Task<IList<Poetry>> QueryAsync(string keyword) =>
        await Connection.Table<Poetry>().Where(p => p.Name.Contains(keyword, System.StringComparison.CurrentCultureIgnoreCase)).ToListAsync();
}

AvaloniaSemiBase/ViewModels/MainWindowViewModel.cs

using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows.Input;
using AvaloniaSemiBase.Models;
using AvaloniaSemiBase.Services;
using CommunityToolkit.Mvvm.Input;

namespace AvaloniaSemiBase.ViewModels;

public partial class MainWindowViewModel : ViewModelBase
{
    // public string Greeting { get; } = "Welcome to Avalonia!";
    private readonly IPoetryStorage _poetryStorage;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="poetryStorage">接口单例实现类</param>
    public MainWindowViewModel(IPoetryStorage poetryStorage)
    {
        _poetryStorage = poetryStorage;
        // 在 C# 的 MVVM(Model-View-ViewModel)模式中,
        // RelayCommand 是一种实现 ICommand 接口的类,
        // 用于将 UI 元素(如按钮)的操作逻辑解耦到 ViewModel 中
        SayHelloCommand = new RelayCommand(SayHello);
        // MVVM结构,异步函数传染
        InitializeCommand = new AsyncRelayCommand(InitializeAsync);

        InsertCommand = new AsyncRelayCommand(InsertAsync);
        
        ListAsyncCommand = new AsyncRelayCommand(ListAsync);
    }

    private string _message;

    public string Message
    {
        get => _message;
        // ref 指针引用
        set => SetProperty(field: ref _message, value);
    }

    public ICommand SayHelloCommand { get; }

    private void SayHello()
    {
        Message = "Hello, World!";
    }


    public ICommand InitializeCommand { get; }

    // 异步初始化方法
    public async Task InitializeAsync()
    {
        // 异步初始化_poetryStorage
        await _poetryStorage.InitializeAsync();
    }


    // 等价于
    // public async Task InsertAsync()
    // {
    // 等价于
    // Poetry poetry = new Poetry();
    // poetry.Name = "Name" + new Random().Next();
    // await _poetryStorage.InsertAsync(poetry);
    // 等价于
    // Poetry poetry = new() { Name = "Name" + new Random().Next() };
    // await _poetryStorage.InsertAsync(poetry);
    // 等价于
    // await _poetryStorage.InsertAsync(new Poetry { Name = "Name" + new Random().Next() });
    // }
    // 定义一个异步方法InsertAsync,用于向_poetryStorage中插入一条新的Poetry记录
    // 调用_poetryStorage的InsertAsync方法,插入一条新的Poetry记录,其Name属性值为"Name"加上一个随机数
    public async Task InsertAsync() =>
        await _poetryStorage.InsertAsync(new Poetry { Name = "Name" + new Random().Next() });

    public ICommand InsertCommand { get; }

    // 定义一个ObservableCollection类型集合的属性Poetries,用于存储Poetry对象
    public ObservableCollection<Poetry> Poetries { get; set; } = new();
    
    public async Task ListAsync()
    {
        var poetries = await _poetryStorage.ListAsync();
        Poetries.Clear();
        foreach (var poetry in poetries)
        {
            Poetries.Add(poetry);
        }
        
    }
    
    public ICommand ListAsyncCommand { get; }
}

AvaloniaSemiBase/Views/MainWindow.axaml

<!-- Window:就是view -->
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:AvaloniaSemiBase.ViewModels"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="AvaloniaSemiBase.Views.MainWindow"
        x:DataType="vm:MainWindowViewModel"
        Icon="/Assets/avalonia-logo.ico"
        Title="Avalonia基线平台"
        DataContext="{Binding MainWindowViewModel ,Source={StaticResource  ServiceLocator}}">
    <!-- DataContext="{Binding MainWindowViewModel ,Source={StaticResource  ServiceLocator}}" -->
    <!-- DataContext="{ReflectionBinding Source={StaticResource ServiceLocator},Path=MainWindowViewModel}" -->
    <!-- DataContext:从名为ServiceLocator的静态类中去找实体MainWindowViewModel -->

    <!-- view关联viewModel -->
    <!-- <Design.DataContext> -->
    <!-- vm:MainWindowViewModel 等价于 new MainWindowViewModel()-->
    <!-- <vm:MainWindowViewModel />只能用于没有构造参数的构造函数 -->
    <!-- <vm:MainWindowViewModel /> -->
    <!-- </Design.DataContext> -->

    <StackPanel>
        <TextBlock Text="{Binding Message}" HorizontalAlignment="Center" VerticalAlignment="Center" />
        <Button Content="爸爸打我" Command="{Binding SayHelloCommand}"></Button>
        <Button Content="初始化数据库" Command="{Binding InitializeCommand}"></Button>
        <Button Content="插入数据" Command="{Binding InsertCommand}"></Button>
        <Button Content="查询数据" Command="{Binding ListAsyncCommand}"></Button>
        <ItemsControl ItemsSource="{Binding Poetries}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"></TextBlock>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>

</Window>

运行,点击“查询数据”:


image.png
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容