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
双击安装,没法选安装位置,直接下一步到位。
安装完验证
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
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代码。
运行:
安装Semi.Avalonia
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
安装好后,可以如下查看:
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类。
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
。
点击接口名称那一行,会出现一个问号,可以点击问号,创建实现类。
或者按快捷键alt+enter
,再创建,效果一致。
using System.Threading.Tasks;
using AvaloniaSemiBase.Models;
namespace AvaloniaSemiBase.Services;
public interface IPoetryStorage
{
// 异步插入
Task InsertAsync(Poetry poetry);
}
public class PoetryStorage : IPoetryStorage
{
}
同样,按红色灯泡,或者快捷键,讲实现类移动到单独的class文件中去。
选择默认或者第一个就行。
同样的操作,可以实现缺少的成员。
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,嵌入式数据库,随应用一起安装,应用开启它开启,应用关闭它关闭。
在项目上通过图形化界面安装。
取消勾选“预发布”,搜索
sqlite-net-plc
:duckdb有4个包
- 包的功能区别
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。
修改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
代码调整如下:
本地文件存储路径帮助工具类
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();
}
}
运行:
以上实现了数据库到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>
......
设置断点,运行,先点击“初始化数据库”,再点击“插入数据”。
放行,插入多点击几下:
查询实现
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>
运行,点击“查询数据”: