基于Avalonia 11.0.0+ReactiveUI 的跨平台项目开发1-通用框架

Avalonia简介:

Avalonia是.NET的一个跨平台UI框架,提供了一个灵活的样式系统,支持广泛的操作系统,如Windows、Linux、macOS,并对Android、iOS和WebAssembly提供了实验性支持。

为什么使用Avalonia:

之前已经了解了基于Avalonia的项目在国产麒麟系统中运行的案例。正是Avalonia在跨平台的出色表现,学习和了解Avalonia这个UI框架显得十分有必要。本项目采用的是最新稳定版本11.0.0-rc1.1。希望通过该项目了解和学习Avalonia开发的朋友可以在我的github上拉取代码,同时希望大家多多点点star。

https://github.com/raokun/TerraMours.Chat.Ava

项目需求:

Web端的AI应用我已经开发了一个AI聊天平台了。希望开发一个跨平台的AI聊天和其他功能的客户端平台。有个面试邀请是要求avalonia应用经验要求。写这样一个项目来学习和了解Avalonia。同时麒麟系统的开放版本也发布了也想后面部署一个将这个项目部署在openKylin 1.0 的系统上。

开发环境:

.net 7

篇幅有限,创建项目的部分跳过,需要了解的可以看我之前的基础博客:https://www.raokun.top/archives/chuang-jian-avalonia-mo-ban-xiang-mu---ji-chu

下面我会直接以TerraMours.Chat.Ava项目为例子 来介绍项目开发过程和各技术的应用

1.nuget包引用

引用包介绍:

  • Avalonia 版本11.0.0-rc1.1,稳定版本,其他基于avalonia的包要选用支持11.0.0-rc1.1的版本

  • Avalonia.ReactiveUI MVVM 架构模式的工具库,创建avalonia项目时会提示选择。

  • DialogHost.Avalonia 它提供了一种简单的方式来显示带有信息的对话框或在需要信息时提示用户。

  • FluentAvaloniaUI UI库,并将更多WinUI控件引入Avalonia

  • System.Data.SQLite 本地数据库SQLite

  • CsvHelper Csv导入导出工具库

  • Markdown.Avalonia 用于显示markdown文本的工具,用于展示聊天结果的渲染

  • Betalgo.OpenAI 调用ChatGpt的扩展库

<PackageReference Include="Avalonia" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.0-rc1.1" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Xaml.Interactivity" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.0-rc1.1" />
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.0-rc1.1" />
<PackageReference Include="DialogHost.Avalonia" Version="0.7.4" />
<PackageReference Include="FluentAvaloniaUI" Version="2.0.0-rc1" />
<PackageReference Include="System.Data.SQLite" Version="1.0.117" />
<PackageReference Include="CsvHelper" Version="30.0.1" />
<PackageReference Include="Markdown.Avalonia" Version="11.0.0-d1" />
<PackageReference Include="Markdown.Avalonia.SyntaxHigh" Version="11.0.0-d1" />
<PackageReference Include="Markdown.Avalonia.Tight" Version="11.0.0-d1" />
<PackageReference Include="Betalgo.OpenAI" Version="7.1.2-beta" />

2.功能介绍

项目开发的功能分为如下:

1.通用框架:

  • VMLocator: ViewModel 定位器。方便地获取和管理 ViewModel 实例,从而实现界面和数据的解耦和模块化,提高代码的可维护性和可测试性。
  • 国际化: 使用 CultureInfo.CurrentCulture 来实现多语言支持和本地化
  • 本地化数据:通过SQLite实现数据本地化
  • CSV导入导出:实现数据的迁移和补充
  • 自定义快捷键: 自定义快捷键,方便操作。发挥客户端的按键优势。
  • 自定义字体
  • 全局样式

2.界面交互

  • LoadView.axaml 加载界面:系统打开时候的加载界面,用于首页替换的技术实践。可改造成登陆界面。
  • MainWindow.axaml 首页
  • MainView.axaml 主界面
  • DataGridView.axaml 会话列表
  • ChatView.axaml 聊天界面
  • ApiSettingsView.axaml API配置

3.功能开发

1.通用功能开发

1.VMLocator ViewModel 定位器

VMLocator 类在 Avalonia 项目中的作用是作为一个静态的 ViewModel 定位器,用于管理和提供各个 ViewModel 类的实例。ViewModel 是 MVVM 模式中用于处理界面逻辑和数据的类,而 ViewModel 定位器则负责管理 ViewModel 的实例,供界面进行数据绑定和交互。

优点:
  1. 提供了一种集中管理 ViewModel 的机制,使得整个应用程序可以方便地访问和使用各个 ViewModel 实例。
  2. 通过使用静态属性和延迟初始化的方式,避免了频繁创建 ViewModel 实例的开销,提高了程序的性能和效率。
  3. 可以在需要的时候动态获取 ViewModel 实例,而不需要手动实例化 ViewModel 类。
  4. 可以在不同的界面和组件之间共享同一个 ViewModel 实例,实现数据的共享和一致性
1.创建资源类VMLocator

2.注入资源

app.axaml.cs中OnFrameworkInitializationCompleted方法加入

var VMLocator = new VMLocator();
Resources.Add("VMLocator", VMLocator);

2.国际化

1.添加不同语言的资源文件
1.创建个语言的资源文件

2.文件内容

代码如下:

<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=System.Runtime">
<!-- Add Resources Here -->
<system:String x:Key="My.Strings.ConversationHitoryInfo">
"conversation history limit" is a client option, not an API parameter.
When the set value (tokens) is exceeded,
the conversation history will be automatically summarized.
</system:String>
</ResourceDictionary>

资源文件中定义了各个配置的说明文本的内容,通过使用 CultureInfo.CurrentCulture 来实现多语言支持和本地化

2.添加Application.Resources

App.axaml中添加资源文件配置

<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="/Assets/lang/zh-CN.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
3.添加AvaloniaXaml

在csproj中添加AvaloniaXaml

<AvaloniaXaml Update="Assets\lang\zh-CN.axaml">
<SubType>Designer</SubType>
</AvaloniaXaml>
什么是AvaloniaXaml:

AvaloniaXaml 是 Avalonia 框架中用于定义用户界面的 XAML 格式。它具有以下作用和特点:

作用:

  1. 声明式定义用户界面:AvaloniaXaml 允许开发人员使用 XAML 标记语言来声明用户界面的结构和外观。它提供了一种简洁、可读性强的方式来描述应用程序的用户界面。
  2. 分离视图与逻辑:通过使用 AvaloniaXaml,开发人员可以将用户界面的定义与应用程序的逻辑代码进行分离。这种分离可以提高代码的组织性、可维护性和可测试性。
  3. 支持样式和模板:AvaloniaXaml 提供了强大的样式和模板机制,使开发人员能够轻松地定制和重用用户界面的外观和行为。这样,可以实现更灵活和可扩展的界面设计。
4.自动判断语言

通过CultureInfo.CurrentCulture 实现系统随机器的语言系统自动切换显示语言实现国际化配置。

CultureInfo.CurrentCulture 是一个表示当前线程所使用的区域设置(Culture)的属性。它提供了关于当前文化信息(如语言、国家/地区、日期和时间格式等)的访问和管理。

在MainWindow的构造函数中添加

代码如下:

 var cultureInfo = CultureInfo.CurrentCulture;
if (cultureInfo.Name == "zh-CN") {
Translate("zh-CN");
}
Translate 方法

代码如下:

#region 国际化
public void Translate(string targetLanguage) {
var translations = App.Current.Resources.MergedDictionaries.OfType<ResourceInclude>().FirstOrDefault(x => x.Source?.OriginalString?.Contains("/Lang/") ?? false); if (translations != null)
App.Current.Resources.MergedDictionaries.Remove(translations); App.Current.Resources.MergedDictionaries.Add(
(ResourceDictionary)AvaloniaXamlLoader.Load(
new Uri($"avares://TerraMours.Chat.Ava/Assets/lang/{targetLanguage}.axaml")
)
);
}
#endregion

3.本地化数据

本地化数据采用了SQLite本地数据库,直接使用SQLite,用sql查询,简单高效,但是需要开发人员对sql的了解程度高。当然,也可以其他ORM框架调用SQLite。

1.SQLite特点

SQLite 是一种嵌入式关系型数据库管理系统(RDBMS),有以下优点,这些优点也是为什么客户端广泛使用它的原因:

  1. 简单易用:SQLite 的设计目标之一是简化数据库系统的使用和管理。它有一个简洁的操作接口,易于学习和使用,无需繁琐的配置和管理步骤。因此,开发人员可以很快上手并快速开发出功能丰富的应用程序。
  2. 无服务器架构:与大多数数据库管理系统不同的是,SQLite 是一个无服务器的数据库引擎,数据库以一个文件的形式存储在磁盘上。这种架构使得 SQLite 的部署和维护变得非常简单,不需要安装和配置额外的服务器。
  3. 轻量级和高性能:由于 SQLite 的设计目标是资源消耗低、性能高效,它被认为是一个轻量级的数据库引擎。SQLite 数据库文件通常较小,并且可以在资源受限的环境中快速运行。同时,SQLite 使用了精简的 SQL 语法和优化技术,提供了快速的查询和高效的数据读写性能。
  4. 跨平台支持:SQLite 是一个跨平台数据库引擎,可以在多种操作系统上运行,包括 Windows、Linux、macOS 和嵌入式系统等。这种跨平台的特性使得开发人员可以方便地在不同的环境中使用 SQLite,并轻松共享和迁移数据库文件。
  5. 高度可移植:SQLite 数据库文件是独立于操作系统和硬件的二进制文件,可以在不同的平台之间自由传输和共享。这种可移植性使得 SQLite 成为一个理想的选择,特别是在需要在不同系统之间进行数据共享或迁移的情况下。
  6. ACID 事务支持:SQLite 支持事务处理和完整性约束,遵循 ACID(原子性、一致性、隔离性和持久性)原则。这意味着开发人员可以使用 SQLite 进行可靠的数据处理,并确保数据的正确性和一致性。
2.创建数据库操作类 DatabaseProcess
1.数据库连接
using var connection = new SQLiteConnection($"Data Source={AppSettings.Instance.DbPath}");
2.创建表和索引

代码如下:

public void CreateDatabase() {
using var connection = new SQLiteConnection($"Data Source={AppSettings.Instance.DbPath}");
string sql = "CREATE TABLE phrase (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL DEFAULT '', phrase TEXT NOT NULL DEFAULT '');"; using var command = new SQLiteCommand(sql, connection);
// phrase创建表格
connection.Open();
command.ExecuteNonQuery(); // phrase索引
sql = "CREATE INDEX idx_text ON phrase (phrase);";
command.CommandText = sql;
command.ExecuteNonQuery();
}
3.查询列表(例)

代码如下:

public async Task<List<string>> GetPhrasesAsync() {
List<string> phrases = new List<string>();
string sql = "SELECT name FROM phrase ORDER BY name COLLATE NOCASE"; using (var command = new SQLiteCommand(sql, memoryConnection)) {
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync()) {
phrases.Add(reader.GetString(0));
}
}
return phrases;
}
4.新增(例)

public async Task SavePhrasesAsync(string name, string phrasesText) {
using var connection = new SQLiteConnection($"Data Source={AppSettings.Instance.DbPath}");
await connection.OpenAsync(); using var transaction = connection.BeginTransaction();
try {
string sql = $"INSERT INTO phrase (name, phrase) VALUES (@name, @phrase)"; using var command = new SQLiteCommand(sql, connection);
command.Parameters.AddWithValue("@name", name);
command.Parameters.AddWithValue("@phrase", phrasesText); await command.ExecuteNonQueryAsync(); transaction.Commit();
}
catch (Exception) {
transaction.Rollback();
throw;
}
await memoryConnection.CloseAsync();
await DbLoadToMemoryAsync();
}

4.CSV导入导出

通过CsvHelper实现数据的导入导出

1.选择文件

弹出选择文件框,限制文件类型为CSV

代码如下:

private async Task ImportChatLogAsync() {
var dialog = new FilePickerOpenOptions {
AllowMultiple = false,
Title = "Select CSV file",
FileTypeFilter = new List<FilePickerFileType>
{new("CSV files (*.csv)") { Patterns = new[] { "*.csv" } },
new("All files (*.*)") { Patterns = new[] { "*" } }}
}; var result = await (Application.Current!.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)!.MainWindow!.StorageProvider.OpenFilePickerAsync(dialog); if (result.Count > 0) {
var selectedFilePath = result[0].Path.LocalPath;
try {
var msg = await _dbProcess.ImportCsvToTableAsync(selectedFilePath);
var cdialog = new ContentDialog() { Title = msg, PrimaryButtonText = "OK" };
await ContentDialogShowAsync(cdialog);
}
catch (Exception ex) {
var cdialog = new ContentDialog() { Title = "Failed to import log." + Environment.NewLine + "Error: " + ex.Message, PrimaryButtonText = "OK" };
await ContentDialogShowAsync(cdialog);
}
VMLocator.DataGridViewModel.ChatList = await _dbProcess.SearchChatDatabaseAsync();
}
}
2.导入到数据库

CSV从文件导入数据,跳过标题行,取得数据,将数据插入数据库

代码如下:

 // CSV从文件导入数据
using var reader = new StreamReader(fileName, System.Text.Encoding.UTF8);
var config = new CsvConfiguration(CultureInfo.InvariantCulture) {
HasHeaderRecord = true,
Delimiter = ","
};
using var csvReader = new CsvReader(reader, config);
csvReader.Read(); // 跳过标题行 using var con = new SQLiteConnection($"Data Source={AppSettings.Instance.DbPath}");
await con.OpenAsync();
using (var transaction = con.BeginTransaction()) {
try {
while (await csvReader.ReadAsync()) // 导入数据行
{ // 数据取得
var rowData = new List<string>();
for (int i = 1, loopTo = columnEnd; i <= loopTo; i++) // 从第二排到第八排
rowData.Add(csvReader.GetField(i));
// 创建插入语句
string values = string.Join(", ", Enumerable.Range(0, rowData.Count).Select(i => $"@value{i}")); string insertQuery = $"INSERT INTO {tableName} ({columnNames}) VALUES ({values});"; // 将数据插入数据库
using (var command = new SQLiteCommand(insertQuery, con)) {
for (int i = 0, loopTo1 = rowData.Count - 1; i <= loopTo1; i++)
command.Parameters.AddWithValue($"@value{i}", rowData[i]);
await command.ExecuteNonQueryAsync();
}
processedCount += 1;
} transaction.Commit();
msg = $"Successfully imported log. ({processedCount} Records)";
}
catch (Exception) {
transaction.Rollback();
throw;
}
}
await con.CloseAsync();

5.自定义快捷键

1.CustomNumericUpDown
  1. 提供了一种自定义的 NumericUpDown 控件,可以用于在用户界面中显示和编辑数字值。NumericUpDown 控件通常用于允许用户输入数值或选择数值的场景。
  2. 通过重写 OnApplyTemplate 方法,该类可以在控件的模板应用后,获取到控件内部的 TextBox 控件,并订阅其 KeyDown 事件。
  3. 在 TextBox_KeyDown 事件处理程序中,实现了自定义的键盘输入逻辑,只允许在 TextBox 中输入数字、小数点以及一些控制键。
  4. 在 DetachedFromVisualTree 事件中,清理了事件的订阅,以避免内存泄漏。
  5. 通过实现 IStyleable 接口,可以定义该控件的样式(StyleKey)。

6.自定义字体

1.添加 AvaloniaResource

在csproj中添加

<AvaloniaResource Include="Assets\Lato-Regular.ttf" />
<AvaloniaResource Include="Assets\migu-1m-regular.ttf" />
2.设置控件对应的字体

<Style Selector="TextBlock">
<Setter Property="FontFamily" Value="avares://TmCGPTD/Assets/Lato-Regular.ttf#Lato" />
<Setter Property="Foreground" Value="rgb(220, 220, 220)" />
</Style>

7.全局样式

1.添加Styles.axaml

Styles.axaml的作用是全局定义样式,控制整个系统的样式风格

2.Application.Styles

app.axaml中添加

代码如下:

<Application.Styles>
<StyleInclude Source="avares://TerraMours.Chat.Ava/Assets/Styles.axaml" />
</Application.Styles>
3.添加AvaloniaResource

csproj添加AvaloniaResource

  <ItemGroup>
<AvaloniaResource Include="Assets\Styles.axaml" />
</ItemGroup>

项目截图:

篇幅有限,功能开发部分请跳转下篇

基于Avalonia 11.0.0+ReactiveUI 的跨平台项目开发2-功能开发

阅读如遇样式问题,请前往个人博客浏览: https://www.raokun.top
拥抱ChatGPT:https://ai.terramours.site
开源项目地址:https://github.com/raokun/TerraMours.Chat.Ava

基于Avalonia 11.0.0+ReactiveUI 的跨平台项目开发1-通用框架的更多相关文章

  1. 基于Vue、web3的以太坊项目开发及交易内幕初探 错误解决总结

    基于Vue.web3的以太坊项目开发及交易内幕初探 本文通过宏观和微观两个层面窥探以太坊底层执行逻辑. 宏观层面描述创建并运行一个小型带钱包的发币APP的过程,微观层面是顺藤摸瓜从http api深入 ...

  2. DotNetBar for Windows Forms 11.8.0.8冰河之刃重打包版

    关于 DotNetBar for Windows Forms 11.8.0.8_冰河之刃重打包版 基于 官方原版的安装包 + http://www.cnblogs.com/tracky 提供的补丁DL ...

  3. 基于Hadoop 2.2.0的高可用性集群搭建步骤(64位)

    内容概要: CentSO_64bit集群搭建, hadoop2.2(64位)编译,安装,配置以及测试步骤 新版亮点: 基于yarn计算框架和高可用性DFS的第一个稳定版本. 注1:官网只提供32位re ...

  4. Oracle Linux 6.3下安装Oracle 11g R2(11.2.0.3)

    本文主要描写叙述了在Oracle Linux 6.3下安装Oracle 11gR2(11.2.0.3).从Oracle 11g開始,Oracle官方站点不再提供其Patch的下载链接,须要使用Meat ...

  5. 【实战】静默安装-oracle 11.2.0.3 on centos 5.10

    发现网上静默安装的文章非常多,乱七八糟,五花八门!来个扫盲的!   centos 5.10 下安装oracle 11g_r2 ************************************* ...

  6. 【转】:Oracle Linux6.9下安装Oracle 11.2.0.4.0及psu补丁升级

    为方便截图,本文操作都在vmware虚拟机上完成. 目录: 1.操作系统安装 2.数据库安装 3.PSU补丁升级卸载   part1 操作系统安装 Oracle (Enterprise) Linux ...

  7. 基于restful注解(spring4.0.2整合flex+blazeds+spring-mvc)<一>

    摘自: http://www.blogjava.net/liuguly/archive/2014/03/10/410824.html 参考官网:1.http://livedocs.adobe.com/ ...

  8. 基于数据库的代码自动生成工具,生成JavaBean、生成数据库文档、生成前后端代码等(v6.0.0版)

    TableGo v6.0.0 版震撼发布,此次版本更新如下: 1.UI界面大改版,组件大调整,提升界面功能的可扩展性. 2.新增BeautyEye主题,界面更加清新美观,也可以通过配置切换到原生Jav ...

  9. 1级搭建类102-Oracle 11g 单实例 FS(11.2.0.4+RHEL 7)公开

    项目文档引子系列是根据项目原型,制作的测试实验文档,目的是为了提升项目过程中的实际动手能力,打造精品文档AskScuti. 项目文档引子系列目前不对外发布,仅作为博客记录.如学员在实际工作过程中需提前 ...

  10. 基于.Net Core 5.0 Worker Service 的 Quart 服务

    前言 看过我之前博客的人应该都知道,我负责了相当久的部门数据同步相关的工作.其中的艰辛不赘述了. 随着需求的越来越复杂,最近windows的计划任务已经越发的不能满足我了,而且计划任务毕竟太弱智,总是 ...

随机推荐

  1. 基于sanic和爬虫创建的代理ip池

    搭建免费的代理ip池 需要解决的问题: 使用什么方式存储ip 文件存储 缺点: 打开文件修改文件操作较麻烦 mysql 缺点: 查询速度较慢 mongodb 缺点: 查询速度较慢. 没有查重功能 re ...

  2. JavaScript 发布-订阅设计模式实现 React EventBus(相当于vue的$Bus)非父子之间通信

    提前声明: 我没有对传入的参数进行及时判断而规避错误,仅仅对核心方法进行了实现: 解决了react的非父子间的通信: 参考文档:https://github1s.com/browserify/even ...

  3. ping功能实现(ICMP)

    简单记录下项目中ping功能实现 笔记:ping功能实现 void Handler::handlePingDepot(const char *ip) { int mSize=50*1024; bzer ...

  4. 笔记二:进程间的通信(fork、孤儿进程,僵死进程等)

          以下是以前学习<unix环境高级编程>时的一些笔记和测试代码,好久没看过了,没有再次验证,存在错误的话,希望见谅,分享下主要是!!! ps     查看系统中的进程   ps– ...

  5. 【Python基础】推导式(列表、字典、集合)

    推导式是一种简洁.高效的语法,用于从一个可迭代对象中生成新的可迭代(iterable)对象. 通常情况下,在以下情况可以考虑使用推导式: 只需要简单的表达式来计算新的可迭代对象的元素. 可迭代对象不是 ...

  6. STL------sort三种比较算子定义

    sort的三种比较算子的定义方式 例:一道细碎的细节模拟题: http://newoj.acmclub.cn/contests/1258/problem/3 1932: 2018蓝桥杯培训-STL应用 ...

  7. 2022-05-19:给定一个数组arr,给定一个正数M, 如果arr[i] + arr[j]可以被M整除,并且i < j,那么(i,j)叫做一个M整除对。 返回arr中M整除对的总数量。 来自微软。

    2022-05-19:给定一个数组arr,给定一个正数M, 如果arr[i] + arr[j]可以被M整除,并且i < j,那么(i,j)叫做一个M整除对. 返回arr中M整除对的总数量. 来自 ...

  8. Play to Earn Games

    什么是P2E游戏 P2E 游戏(Play to Earn Games)指的是在区块链游戏中,玩家可以通过完成任务.收获资源.挖矿或游戏中的其他活动以获得成就来赚取游戏内的资产(NFT)或代币(Toke ...

  9. 中文环境下使用 huggingface 模型替换 OpenAI的Embedding 接口

    OpenAI的文本嵌入衡量文本字符串的相关性.嵌入通常用于: 搜索(其中结果按与查询字符串的相关性排名) 聚类(其中文本字符串按相似性分组) 推荐(推荐具有相关文本字符串的项目) 异常检测(识别出相关 ...

  10. 如何获取 C#程序 内核态线程栈

    一:背景 1. 讲故事 在这么多的案例分析中,往往会发现一些案例是卡死在线程的内核态栈上,但拿过来的dump都是用户态模式下,所以无法看到内核态栈,这就比较麻烦,需要让朋友通过其他方式生成一个蓝屏的d ...