# 基於Avalonia 11.0.0+ReactiveUI 的跨平臺項目開發1-通用框架 ### Avalonia簡介: Avalonia是.NET的一個跨平臺UI框架,提供了一個靈活的樣式系統,支持廣泛的操作系統,如Windows、Linux、macOS,並對Android、iOS和WebAss ...
基於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 的實例,供界面進行數據綁定和交互。
優點:
- 提供了一種集中管理 ViewModel 的機制,使得整個應用程式可以方便地訪問和使用各個 ViewModel 實例。
- 通過使用靜態屬性和延遲初始化的方式,避免了頻繁創建 ViewModel 實例的開銷,提高了程式的性能和效率。
- 可以在需要的時候動態獲取 ViewModel 實例,而不需要手動實例化 ViewModel 類。
- 可以在不同的界面和組件之間共用同一個 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 格式。它具有以下作用和特點:
作用:
- 聲明式定義用戶界面:AvaloniaXaml 允許開發人員使用 XAML 標記語言來聲明用戶界面的結構和外觀。它提供了一種簡潔、可讀性強的方式來描述應用程式的用戶界面。
- 分離視圖與邏輯:通過使用 AvaloniaXaml,開發人員可以將用戶界面的定義與應用程式的邏輯代碼進行分離。這種分離可以提高代碼的組織性、可維護性和可測試性。
- 支持樣式和模板: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),有以下優點,這些優點也是為什麼客戶端廣泛使用它的原因:
- 簡單易用:SQLite 的設計目標之一是簡化資料庫系統的使用和管理。它有一個簡潔的操作介面,易於學習和使用,無需繁瑣的配置和管理步驟。因此,開發人員可以很快上手並快速開發出功能豐富的應用程式。
- 無伺服器架構:與大多數資料庫管理系統不同的是,SQLite 是一個無伺服器的資料庫引擎,資料庫以一個文件的形式存儲在磁碟上。這種架構使得 SQLite 的部署和維護變得非常簡單,不需要安裝和配置額外的伺服器。
- 輕量級和高性能:由於 SQLite 的設計目標是資源消耗低、性能高效,它被認為是一個輕量級的資料庫引擎。SQLite 資料庫文件通常較小,並且可以在資源受限的環境中快速運行。同時,SQLite 使用了精簡的 SQL 語法和優化技術,提供了快速的查詢和高效的數據讀寫性能。
- 跨平臺支持:SQLite 是一個跨平臺資料庫引擎,可以在多種操作系統上運行,包括 Windows、Linux、macOS 和嵌入式系統等。這種跨平臺的特性使得開發人員可以方便地在不同的環境中使用 SQLite,並輕鬆共用和遷移資料庫文件。
- 高度可移植:SQLite 資料庫文件是獨立於操作系統和硬體的二進位文件,可以在不同的平臺之間自由傳輸和共用。這種可移植性使得 SQLite 成為一個理想的選擇,特別是在需要在不同系統之間進行數據共用或遷移的情況下。
- 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
- 提供了一種自定義的 NumericUpDown 控制項,可以用於在用戶界面中顯示和編輯數字值。NumericUpDown 控制項通常用於允許用戶輸入數值或選擇數值的場景。
- 通過重寫 OnApplyTemplate 方法,該類可以在控制項的模板應用後,獲取到控制項內部的 TextBox 控制項,並訂閱其 KeyDown 事件。
- 在 TextBox_KeyDown 事件處理程式中,實現了自定義的鍵盤輸入邏輯,只允許在 TextBox 中輸入數字、小數點以及一些控制鍵。
- 在 DetachedFromVisualTree 事件中,清理了事件的訂閱,以避免記憶體泄漏。
- 通過實現 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-功能開發