☰
Avalonia跨平台桌面开发:整合LiveChart2与SukiUI实战
2026/10/18 7:49:41 网站建设 项目流程

简介:这套Avalonia桌面程序示例整合LiveChart2与SukiUI,面向希望掌握跨平台UI开发的.NET开发者,重点解决Linux环境下的默认字体显示问题,已在Ubuntu 20.04验证通过。资源包共1031个文件,以734个C#源码和211个AXAML界面描述为主,配套16个TTF/7个OTF字体、项目配置及样式资源,完整覆盖从控件定义到主题定制的开发链路,压缩包大小约90.39MB。其中包含仪表盘、按钮、时间选择器、数据网格等多种典型视图与样式文件,便于对照学习MVVM数据绑定、LiveChart2图表接入及SukiUI主题扩展。已有1786人浏览学习,适合正在入门Avalonia或想借鉴成熟项目结构的开发者。通过研读源码,可快速掌握跨平台桌面应用的布局写法、第三方库集成和字体兼容处理思路,为跨平台桌面应用开发提供完整可参考的代码范本。

1. Avalonia 桌面程序:为什么是它,以及这套整合解决了什么

在 Linux 桌面端需要一个能快速出界面的 Avalonia 程序时,很多人第一反应是 WPF 或 Electron,但 Avalonia 跨平台 XAML 栈其实更接近前者,而且部署体积比 Electron 小一个数量级。某开发者在做某工业监控模拟项目时,用 Avalonia 搭了一个简约面板,顺手整合了 LiveChart2 画实时曲线、SukiUI 做暗色主题,期间踩得最深的坑是默认字体在 Linux 下发虚、中文显示成方块。这套资源把这几个问题一次解决,适合想避开重复调研、直接落地的 .NET 桌面开发者。

2. 搭建 Avalonia 项目:从模板到 Linux 上的可运行窗口

2.1 创建项目:模板选择与跨平台目录结构

Avalonia 的模板和 WPF 的思维方式很像,但目录结构更接近跨平台 UI 框架的标准味道。安装模板后,用 mvvm 模板创建项目,因为后续接 LiveChart2 和 SukiUI 都需要严格区分数据层和视图层,否则实时图表刷新时事件通知会绕得非常乱。

dotnet new install Avalonia.Templates dotnet new avalonia.mvvm -n MonitorPanel cd MonitorPanel dotnet build

模板会生成 App.axaml、MainWindow.axaml、ViewModels 目录和若干示例页面。这个结构的核心思路是 ViewModel 不直接持有控件引用,图表数据通过绑定流入控件,这样换主题、换皮肤时不需要改业务逻辑。生成的MonitorPanel项目默认引用Avalonia.Desktop,在 Linux 上需要确认 SDK 版本不低于 6.0,否则后续 NuGet 包版本会对不上。

sudo apt install libgl1 libgl1-mesa-dri libx11-dev libxrandr-dev libice-dev libsm-dev dotnet run

常见做法是在开发机上先跑通这个空窗口,再做界面。libgl1 和 mesa 是 Avalonia 软件渲染到 GPU 之间的桥梁,缺了会出现窗口白屏。装完依赖后,dotnet run弹出窗口,如果黑屏或闪退,优先检查环境变量DOTNET_SYSTEM_GLOBALIZATION_INVARIANT,后面避坑章节会展开。

2.2 ViewModel 骨架:绑定上下文与实时数据流

Avalonia 的绑定默认走DataContext,和 WPF 类似,但实时图表场景里有一个关键差异:控件在 Linux 上的消息循环优先级低于绘制线程,如果变化事件在非 UI 线程触发,界面不会立刻重绘。这里需要在 ViewModel 里把集合容器选对。

using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; namespace MonitorPanel.ViewModels; public partial class MainViewModel : ObservableObject { public ObservableCollection<double> TemperatureValues { get; } = new(); public MainViewModel() { InitData(); } private void InitData() { for (int i = 0; i < 30; i++) { TemperatureValues.Add(Math.Sin(i * 0.3) * 10 + 25); } } }

ObservableCollection自带INotifyCollectionChanged,LiveChart2 会订阅这个事件并触发坐标轴重排。不要用List<T>直接赋值,因为List不通知新增元素,界面不会动。这里加入 30 个初始点是为了让曲线在窗口加载时就有内容,避免空图表造成的布局跳动。

3. 接入 SukiUI:主题加载顺序与自定义样式

3.1 为什么选 SukiUI:一套完整的主题资源字典

SukiUI 不是简单的颜色变量集合,它内部把 Avalonia 的ThemeVariant和一套默认控件样式打包成一个资源字典。选它而不是自己写样式,是因为监控面板需要暗色背景和高亮数据线,手写每个控件的模板会花掉大量时间,而且不同系统下圆角、边框、阴影表现不一致,调起来是玄学。

接入前需要理解 Avalonia 的主题加载顺序:Application.Styles里的样式按顺序叠加,后声明的覆盖先声明的。SukiUI 必须作为第一个样式放入,否则它内部对控件模板的默认设置会被后续自定义样式部分覆盖,导致按钮颜色正确但圆角丢失。

dotnet add package SukiUI

版本号一般选 5.x,对应 Avalonia 11。如果工程用的是 Avalonia 0.10,需要换更老的包,否则启动时直接抛 XAML 解析异常。

3.2 在 App.axaml 中注册主题

<Application ... xmlns:suki="clr-namespace:SukiUI;assembly=SukiUI"> <Application.Styles> <suki:SukiStyles /> </Application.Styles> </Application>

这段 XAML 把 SukiUI 的样式集合挂进应用级样式表。注意命名空间映射的assembly=SukiUI要和 NuGet 包名完全一致。SukiStyles内部会加载浅色和暗色两套资源,默认走暗色。Windows 上不改也可能正常,但 Linux 下建议显式指定主题,否则控件的背景色和文字色可能跟系统 GTK 主题冲突。

3.3 切换主题与自定义强调色

主题切换代码写在某个按钮的点击事件里,或者启动时调用。

using SukiUI; public partial class MainWindow : Window { private void OnSwitchThemeClick(object? sender, RoutedEventArgs e) { SukiTheme.GetTheme().SwitchTheme(); } }

SwitchTheme()会在浅色和暗色之间切换,不需要重建窗口。如果你要固定品牌色,可以用ChangePrimaryColor。这里的选择逻辑是:界面里图表区域需要持续高亮,所以用暗色主题加蓝色强调色,曲线数据行在暗色背景上对比度最高,长时间盯屏不疲劳。

4. 整合 LiveChart2:数据绑定与实时刷新

4.1 LiveChart2 与 SkiaSharp 的关系

LiveChart2 在 Avalonia 上依赖 SkiaSharp 做离屏渲染,这意味着图表不是用普通控件绘制,而是先渲染成 Skia 画布再贴到控件区域。好处是缩放和旋转的绘制效果一致,坏处是每次数据变化都要重绘画布,如果数据点过多或者刷新频率太高,会吃掉不少 CPU。理解这一点,就会明白为什么实时图表要控制数据点数量,而不是无限增长。

引入包时注意平台:Linux 上 Skia 需要 native 依赖,发布阶段需要对应的 runtimes 目录,否则运行时报找不到 SkiaSharp 内部函数。

dotnet add package LiveChartsCore.SkiaSharpView.Avalonia

装完包后,CartesianChart控件在 XAML 中可用。

4.2 在 View 中声明图表控件并绑定 Series

<UserControl ... xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.Avalonia;assembly=LiveChartsCore.SkiaSharpView.Avalonia"> <Grid> <lvc:CartesianChart Series="{Binding Series}" /> </Grid> </UserControl>

Series属性绑定到 ViewModel,等号左边不是自动属性,而是ISeries[]数组。因为 LiveChart2 需要读取整个系列集合来构建坐标轴和绘制图层,数组在首次赋值后不再变化,后续内部点通过Values集合的事件驱动重绘。

4.3 在 ViewModel 中创建可更新的 Series

using LiveChartsCore; using LiveChartsCore.SkiaSharpView; using LiveChartsCore.SkiaSharpView.Avalonia; public ISeries[] Series { get; set; } public MainViewModel() { TemperatureValues.CollectionChanged += (_, _) => { OnPropertyChanged(nameof(TemperatureValues)); }; Series = new ISeries[] { new LineSeries<double> { Values = TemperatureValues, Name = "温度", StrokeThickness = 2, Fill = null } }; }

通过CollectionChanged触发OnPropertyChanged是数据刷新的关键,因为Series对象本身没有变,变的是它内部的Values集合。如果你只想在按钮点击后一次性更新数据,用ISeries[]加集合事件就够了;但如果数据是后台线程高频产生,建议把TemperatureValues替换成线程安全的队列,并限制最多保留 120 个点,防止曲线越来越密导致 Skia 渲染超时。实时刷新频率高到每 100 毫秒一次时,还需要在添加点的时候做节流,否则 UI 线程容易积压无效绘制。

5. 常见问题与排查:字体、布局与图表的五个坑

5.1 字体不生效不是代码问题:Linux 字体配置优先

现象:界面图表数字和按钮文字发虚,部分中文变成方块或空心方框。原因:Avalonia 默认字体走系统字体匹配,Linux 桌面环境如果没有安装中文字体,字库回调找不到对应字形。解决:在程序入口注册自定义字体文件,并指定 fallback 顺序。

using Avalonia.Media; FontManager.Current.DefaultFontFamilyName = "Microsoft YaHei UI, WenQuanYi Micro Hei, Noto Sans CJK SC";

更保险的做法是直接把字体文件放进 Assets 目录,用 embedded font。FontFamily设置为avares://MonitorPanel/Assets/xxx.ttf#字体名,这样 Windows 和 Linux 都能显示一致。资源里已解决默认字体问题,复制 Assets 文件到你的项目下,再将上述代码放到 App 启动前。

5.2 布局在开发机和目标机上不一样

现象:同一套窗口宽度,在开发机上完美居中的按钮跑到 Linux 上偏移。原因:不同系统 DPI 缩放系数不同,Avalonia 默认开启系统缩放,但部分 Linux 环境没有正确报告缩放值。解决:窗口启用UsePlatformInsights辅助调试,或者把所有尺寸用相对比例设定。

this.Width = 1200; this.Height = 720; this.WindowStartupLocation = WindowStartupLocation.CenterScreen;

固定宽度只能保证开发环境效果,目标机器分辨率差异大时,建议用Grid的*单位做行高列宽分配,避免在 Margin 里写死偏移量。

5.3 LiveChart2 图表数据不刷新

现象:数值在 ViewModel 里变了,界面曲线不动。原因:Series数组没有变化,只要内部Values集合是普通List,LiveChart2 就不会收到通知。解决:用ObservableCollection<T>,并在集合变更事件里再次通知Series属性。

TemperatureValues.CollectionChanged += (_, _) => { OnPropertyChanged(nameof(TemperatureValues)); };

如果用了集合通知还不刷新,检查是否在后台线程直接添加项,需要把操作切回 UI 线程。Avalonia 里可以用Dispatcher.UIThread.Post,直接Add会导致跨线程写入被异常吞掉,界面静默不更新。

5.4 SukiUI 主题设置后部分控件无变化

现象:按钮颜色变了,但DataGrid表头和边框还是默认样式。原因:SukiUI 的资源字典只覆盖基础控件,第三方表格组件要单独引入配套样式。解决:在App.axaml中显式加入表格样式资源,并确保它在SukiStyles之后。

<Application.Styles> <suki:SukiStyles /> <suki:SukiUIDataGrid /> </Application.Styles>

样式叠加顺序插错了,后面的资源字典会盖掉主题,所以不要把它放在SukiStyles前面。

5.5 发布后运行报 Skia 原生依赖缺失

现象:开发机上正常,拷到干净 Linux 环境后启动即崩,日志里出现未找到libSkiaSharp。原因:Skia 的 native 库没有随程序输出。解决:发布时指定 RID,或者把 Skia native 文件一起拷贝。

dotnet publish -c Release -r linux-x64 --self-contained true

如果嫌包太大,用--self-contained false但要把runtimes/linux-x64/native目录整个带上。检查日志时看有没有libSkiaSharp.so丢失,这一步是跨平台发布的黑匣子,踩一次后就会记住。

6. 发布与验证:Linux 打包与运行检查

6.1 单文件发布与依赖核对

dotnet publish -c Release -r linux-x64 -p:PublishSingleFile=true

单文件模式会把托管代码和部分 native 库合并,但 Skia 的libSkiaSharp.so仍可能外置,运行后验证程序目录下是否有libSkiaSharp.so。如果缺失,把runtimes/linux-x64/native里的文件复制到输出目录,这是最直接的兜底办法。发布前检查字体文件是否被复制到输出目录,EmbeddedResource不会自动进单文件包,需要额外在 csproj 里声明。

6.2 运行清单验证

检查项验证方法预期结果
字体显示打开中文面板截图无方块,无发虚
图表刷新观察曲线频率每秒更新,无卡顿感
主题切换触发切换按钮深浅色即时切换
Skia 依赖ldd检查输出目录动态库存在
窗口居中在不同分辨率下启动首次显示居中

这套资源里已经包含一个自动化脚本,可以按顺序执行这些检查项。某开发者后来每次发布前都强制走一遍清单,确认字体文件、Skia 依赖、主题字典三件事没问题才敢交付到目标机。从那以后我没再遇到过现场翻车,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询