1. 我为什么动了"抛弃 Electron"的念头——三个真实场景把我打醒
先交代一下背景:我做了七年桌面端开发,前三年半基本都在用 Electron 套各种壳。项目交付出去的时候,node_modules比业务代码还大是常态,用户抱怨启动慢、内存高,我一开始还能拿"跨平台开发就该这样"来安慰自己。直到三个真实场景连续发生,我才开始认真思考自研引擎这条路。
第一个场景发生在某个工业级数据看板项目上。业务方要求桌面端同时展示 16 块曲线图和实时报警列表,Electron 渲染进程的堆内存直接冲到 2.3GB。用户机器是一台 IPC 工控机,4GB 内存跑 Windows 10,原本还有两个上位机软件在跑。数据看板一开,工控机其他进程集体卡死,鼠标拖窗口都像是慢动作。我知道可以用虚拟列表、canvas 优化,但底层内存机制在那摆着——Chromium 的渲染进程、GPU 进程、网络服务进程各占一份内存,每个页面实例还会复制一大堆 V8 堆。你优化得再好,内存也回不到百兆级别。
第二个场景是启动速度。某个给客户现场演示用的设备参数配置工具,Electron 写完后冷启动要 4-7 秒不等。固态硬盘上还算能忍,可客户现场有一批老式电脑用的还是机械盘,冷启动直接 12 秒起,期间窗口一片白。做演示的时候场面一度很尴尬,后来我只能加了个"启动引导页"来遮丑,但这个引导页本身要等 Electron ready 之后才能渲染——等于白等。
第三个场景最直接,是关于分发体积的。打包出来 78MB 的安装包,实际安装完解压后 260MB。客户问了我一句:你们这功能不就是几个表格加几个按钮吗,为什么要装两百多兆的东西?我嘴上解释这是运行时,心里明白:如果我用原生 C# + Win32 写,整个程序可能不到 3MB。当时那句话像针一样扎在职业自尊心上。
也就是从那时候开始,我认真调研了自研 C# UI 引擎的可行性,最后花了接近四个月的时间写出了 XchyUI 的原型,再经过两个真实项目的打磨,内核稳定在约 200KB。这篇文章就把整个思路、设计、踩坑和实测数据完整摊开聊一聊。如果你也在 Electron 和原生方案之间反复横跳,希望这篇能帮你做出更有底气的决定。
2. XchyUI 的内核设计:200KB 是怎么从 Electron 的几百 MB 里剥出来的
很多朋友听到"自研 UI 引擎",第一反应是这东西一定很复杂、很庞大。事实恰恰相反——XchyUI 的完整内核只要约 200KB,这个体积不是通过什么压缩魔法实现的,而是从根上砍掉了两大重量级子系统:浏览器引擎和 JavaScript 运行时。
2.1 设计起点:不嵌入 WebView,也不碰 V8
Electron 的体积构成非常清晰:Chromium 渲染引擎大约 150MB 起步,Node.js 运行时再加 30MB 左右,还有一些 FFmpeg、沙箱模块等附属文件。这些都是"通用能力"的代价——它能跑任意网页,付出的就是承载任意网页的运行时成本。
桌面应用真正需要的 UI 能力并没有那么宽。你需要窗口、布局、文本、按钮、输入框、图表、滚动区域,需要响应鼠标键盘事件,需要重绘和动画。这些需求如果用浏览器那套 DOM/CSS/JavaScript 模型去做,等于开着航母过小河——大部分动力根本用不上,还得时刻担心油耗。
XchyUI 的起点就是放弃 WebView,直接基于 Win32 窗口和 GPU 绘制管线实现一套轻量渲染。整个引擎只做一件事:把 C# 层声明的 UI 结构转换为 GPU 绘制指令。没有 HTML、没有 CSS、没有 JavaScript,更没有 V8 堆内存。
2.2 按需裁剪:什么样的功能才配进内核
在设计内核的时候,我给自己定了一条铁律:每个进入内核的功能必须能回答"为什么要进内核"。凡是可选的功能一律走外置模块,动态加载。
最后内核只保留了以下模块:
| 模块 | 职责 | 体积预估 |
|---|---|---|
| 窗口管理 | 创建窗口、消息循环、HWND 绑定 | 约 20KB |
| 渲染指令集 | 将 UI 树转为 GPU 绘制指令 | 约 35KB |
| 布局计算 | 水平/垂直/绝对/网格布局 | 约 30KB |
| 文本处理 | 字体加载、文本测量、基础排版 | 约 40KB |
| 状态管理 | 控件状态、事件通知、重绘标记 | 约 25KB |
| 基础控件 | 按钮、标签、输入框、滚动区域 | 约 50KB |
合计 200KB 左右。注意这里面没有模块依赖,全部是 C# native 代码,不需要额外安装 .NET 之外的东西。
2.3 即时模式 UI 是体积控制的关键决策
这里要花点篇幅讲一个很重要的选型决策:XchyUI 采用即时模式(Immediate Mode)而非保留模式(Retained Mode)。
传统的桌面框架(WPF、Qt、WinForms)都是保留模式。你要在内存里维护一棵界面元素树,每个元素都有自己的生命周期、状态和布局属性。框架会持续追踪这棵树的状态变化,增量地更新界面。好处是写起来直观,坏处是框架本身非常重——属性系统、依赖属性通知、视觉树、逻辑树、模板引擎,光这些基础设施就要几十万行代码。
即时模式的做法完全不同:你每一帧都重新描述整个界面是什么样子。不存在持久的 UI 元素实例,代码每次执行到绘制区域时,都从头计算布局、生成绘制指令,然后立刻交给 GPU。这种模式天然省掉了大量的"追踪状态"代码。
用代码直观感受一下这两种模式的差异。保留模式下,你创建一个按钮需要实例化对象、设置属性、挂到容器上,后续框架自己去更新它:
// 保留模式:一切皆对象 var btn = new Button(); btn.Text = "提交"; btn.Width = 120; btn.Click += OnSubmitClicked; panel.Children.Add(btn);即时模式下,你不需要创建任何持久对象,只需要描述"这里有一个按钮,文字是提交,点击后执行某某方法",引擎在需要的时候现场绘制它:
// 即时模式:UI 是函数的输出 Draw.Button("提交", new Rect(10, 20, 120, 36), () => OnSubmitClicked());从代码量上你就能看出来,即时模式砍掉了彻底的一套对象生命周期管理,控件状态要么由调用方保存,要么由引擎内部以数据驱动方式管理。这让引擎核心代码大幅减少,也是内核能压到 200KB 的一大原因。
当然,即时模式不是没有代价。它要求每一帧都从零计算布局,如果界面复杂到一定程度,CPU 布局计算会成为瓶颈。但这个问题可以通过脏区域标记和缓存命中来缓解,我后面在实战章节会专门讲优化手段。
3. 渲染管线的核心原理:从 CPU 布局到 GPU 绘制的完整路径
做 UI 引擎,最核心的就是渲染链路。Electron 走的是 HTML/CSS 解析、样式计算、布局、绘制、合成的完整浏览器管线。XchyUI 把这套管线大幅简化,只保留了桌面控件需要的四个阶段。
3.1 布局:不用 Flexbox,用自研的简单盒布局
浏览器布局引擎是出了名的复杂,光是 Flexbox 规范就有几千页。桌面 UI 场景中,控件布局绝大多数可以拆成四类:
- 绝对定位:指定左上角坐标和宽高
- 水平排列:从左往右依次排
- 垂直排列:从上往下依次排
- 网格排列:按行和列排
XchyUI 的布局器实现了这四种模式,采用了一个非常朴素的策略:先测量子元素的最小尺寸,再根据父容器的可用空间分配剩余空间。布局一次完成后生成一个 Box 数组,每个 Box 包含位置和尺寸。
这里有个直接效能的对比:同样的布局,浏览器引擎要先构建 DOM 树、解析 CSS 选择器、计算继承属性、跑样式匹配,最后才进入布局阶段。XchyUI 直接从面片上声明布局,跳过了解析过程,这部分天然就快了一个数量级。当然不是绝对的快,但普通界面的布局计算从几毫秒降到了亚毫秒,确实是肉眼可见的差异。
3.2 绘制:指令化而不是在位绘制
布局完成后,UI 树会被转换为一个绘制指令列表。每个控件生成若干条指令,比如:
DrawRectangle(200, 150, 480, 36, #FF2D2D2D) DrawText("提交", (310, 158), "Microsoft YaHei 14", #FFFFFFFF)这些指令不是直接输出像素,而是先记录到一个指令缓冲里,等一帧的指令全部生成完毕,再统一提交给 GPU 执行。这种"先录后播"的机制有几个好处:
- 指令可以批量处理,减少渲染状态切换
- 指令可以排序,先尽数绘制背景,再绘制前景,减少覆盖区域的无效绘制
- 同一帧内如果检测出某区域被重复覆盖,可以直接裁剪掉部分指令
GPU 绘制用的是 D2D1 或者 Direct3D 11,视目标操作系统而定。早期原型先用 GDI+ 验证逻辑,后来性能瓶颈明显,才迁移到 Direct2D。迁移后,界面渲染的开销主要落到了 GPU 上,CPU 只负责生成指令列表。
3.3 文本渲染:最容易低估的子系统
Text rendering 是自研 UI 引擎里最容易被低估的模块。很多人觉得画字就是调 API,实际做起来才发现细节极其繁琐。
首当其冲的是文本测量。布局时你不知道一个按钮里文字到底占多宽,必须调用字体引擎测量每个尺寸。中英文混排时测量逻辑还要处理行高差异、全角半角宽度差异。XchyUI 接的是 DirectWrite,用它做字形测量和栅格化,但围绕测量结果还要自己做换行策略、对齐策略、裁剪省略号逻辑。
其次是用字体的问题。系统字体千奇百怪,不同 Windows 版本之间字体差异也很大,稍有不慎界面在 Windows 7 和 Windows 11 上显示效果完全不同。我在 XchyUI 的默认字体配置上最后选了 Microsoft YaHei 作为默认字体,这个字体在近 20 年的 Windows 上都有覆盖,兼容性最稳定。
最麻烦的是高分屏下的文本清晰度。如果 DPI 缩放系数是 150%,你把文本绘制在非整数像素坐标上就可能出现模糊边缘。DirectWrite 提供了像素对齐选项,但开启后你又要让引擎里的所有布局坐标也按同样规则对齐,否则文字会和控件边框产生半像素偏差。这个问题的完整解决方案我会放在后面的踩坑章节讲。
4. 实测数据说话:XchyUI 与 Electron 的对照测试
写这篇博文之前,我特意把 XchyUI 和一个同功能 Electron 应用放在同一台测试机上做了详细对比。测试场景是一个管理后台风格的界面:左侧导航栏、顶部状态栏、中间列表表格、右侧操作面板,含 600 行数据和实时刷新图表。
4.1 测试环境与控制变量
- CPU:Intel i5-8400
- 内存:16GB DDR4
- 系统:Windows 10 19044
- 磁盘:金士顿 KC600 SATA SSD
- Electron 版本:v30.3.0(Chromium 124)
- .NET 版本:.NET 8
两个应用功能一致,窗口尺寸一致,同样的数据源,人为设定同样的刷新频率。测试项目的 Electron 打包用的是 standard 模板,XchyUI 发布方式是单文件发布。
4.2 关键指标对比
| 指标 | XchyUI | Electron | 差距 |
|---|---|---|---|
| 冷启动至首帧时间 | 约 150ms | 约 280ms | 快 1.87 倍 |
| 常驻内存(空闲) | 约 42MB | 约 185MB | 省 77% |
| 常驻内存(加载 600 行数据) | 约 68MB | 约 410MB | 省 83% |
| 安装包体积 | 约 2.1MB | 约 82MB | 省 97.4% |
| 安装后目录体积 | 约 9MB | 约 265MB | 省 96.6% |
| 界面刷新帧率(含 600 行+图表) | 60FPS 稳 | 约 30FPS 上下波动 | 更稳 |
注意冷启动时间——考虑到 Electron 那套首帧前还要初始化 V8、加载所有 JS 模块,XchyUI 快 1.8 倍并不意外。真正让我满意的是内存数据,空载 42MB 对桌面应用来说已经可以接受,加载数据后也只到 68MB,相比 Electron 的 410MB 完全是两个量级。
4.3 为什么 Electron 天然就有这些开销
为了做到公平,我必须说明:Electron 的劣势是架构决定的,不是优化不好的问题。Chromium 当初是为浏览器设计的,每个渲染进程都带完整的 JS 引擎、HTML 解析器、CSS 引擎、布局引擎、GPU 合成器,这些东西在运行时就意味着内存和 CPU 占用。多进程架构又进一步放大占用,每个页面一个渲染进程,进程间通信也要消耗。
你没法"精简"Chromium,因为它不是一个可以拆零件的引擎。你只能接受它的完整形态,或者换一个像 XchyUI 这样的轻量方案。这就是为什么"桌面应用体积"这个指标,在 Electron 生态里无解。
5. 实战:从零到一接入 XchyUI 写一个完整界面
前面都是原理和数据,现在开始讲讲怎么真正上手。我用一个简单的登录界面作为例子,把 XchyUI 的开发方式完整过一遍。
5.1 项目初始化与最小窗口
XchyUI 以 NuGet 包形式分发,安装XchyUI包后,创建一个控制台应用,修改入口:
using XchyUI; using XchyUI.Windowing; class Program { static void Main() { var app = new XchyApplication(); app.Run(() => { var window = new XchyWindow("登录系统") { Size = new Size(400, 300), CenterOnScreen = true }; window.Show(); return window; }); } }XchyApplication.Run内部封装了 Win32 消息循环,传入的回调负责创建主窗口。窗口创建后,UI 内容在窗口的绘制回调中声明。
5.2 声明式绘制:布局和控件的实际写法
XchyUI 使用的是即时模式,所以窗口内容在OnPaint回调中每次重新描述。为了方便组织,我封装了一个登录界面的绘制方法:
using XchyUI.Controls; using XchyUI.Drawing; class LoginWindow : XchyWindow { public LoginWindow() : base("登录系统") { Size = new Size(400, 300); CenterOnScreen = true; } protected override void OnPaint(PaintContext ctx) { var bounds = ClientBounds; ctx.Clear(Color.FromRgb(245, 245, 245)); // 标题文本 ctx.DrawText("XchyUI 登录", new Rect(bounds.Width / 2 - 60, 30, 120, 30), "Microsoft YaHei 18", Color.FromRgb(33, 33, 33), TextAlign.Center); // 用户名输入框 _userBox = ctx.TextBox( new Rect(80, 90, 240, 32), _userBox == null ? "" : _userBox.Text, "请输入用户名"); // 密码输入框 _passBox = ctx.TextBox( new Rect(80, 135, 240, 32), _passBox == null ? "" : _passBox.Text, "请输入密码", PasswordMode: true); // 登录按钮 if (ctx.Button("登 录", new Rect(80, 190, 240, 36))) { TryLogin(); } // 状态提示 if (!string.IsNullOrEmpty(_message)) { ctx.DrawText(_message, new Rect(80, 245, 240, 20), "Microsoft YaHei 12", Color.FromRgb(200, 60, 60), TextAlign.Center); } } void TryLogin() { /* 业务逻辑 */ } }注意这里TextBox、Button的返回值和状态保持方式是即时模式的典型特征。ctx.TextBox(...)不是创建一个持久对象,而是绘制一个输入框并返回当前状态。如果你需要跨帧保留文本框内容,就得像示例里那样用字段保存。
5.3 事件模型:即时模式下的点击和键盘事件
即时模式下的事件处理逻辑比保留模式更直接。鼠标点击时,引擎会把点击坐标转换为命中测试,遍历当前帧的指令列表,找到命中的交互区域,然后触发对应的回调。因为 UI 每帧都会重建,事件绑定也每帧都重建,所以回调闭包捕获的东西必须是最新状态,这点和保留模式事件机制的思路完全不同。
实际开发中最常用的交互模式是"轮询式事件触发":
if (ctx.Button("导出数据", rect)) { ExportData(); // 每次检测到按钮区域被点击,都会执行 }只要当前帧内按钮处于按下状态,回调就会触发。不需要像 WPF 那样处理 route event、bubbling 这些复杂机制,代码直观得多。
5.4 样式系统:没有 CSS 的样式怎么做
不做 CSS 不代表不搞样式。XchyUI 的样式通过一个StyleRegistry实现,字典结构,键是控件类型,值是样式定义:
var styleRegistry = new StyleRegistry(); styleRegistry.Register<Button>(state => new ButtonStyle { Background = state.IsHovered ? Color.FromRgb(70, 140, 220) : Color.FromRgb(50, 120, 200), TextColor = Color.White, CornerRadius = 4, BorderWidth = 0, Padding = new Thickness(8, 4, 8, 4) });样式定义里的状态判断就是引擎自带的"鼠标悬停""按下""聚焦"三个状态。你可以为不同状态返回不同样式,引擎渲染时会去查。主题切换其实就是换一套样式注册表,不需要遍历节点改属性。
6. 自研引擎路上的坑:六个足够劝退人的问题,以及我怎么绕过去的
这一章节是我最想写的部分。原理、数据、代码别人都能写,但踩坑的血泪细节是真的要自己走一遍才知道。以下六个坑,都是我实际在 XchyUI 开发中遇到并解决的,按痛苦程度排序。
6.1 中文字体渲染模糊:高 DPI 下的半像素偏移
第一个坑是所有自研 UI 引擎绕不过去的。Windows 的 DPI 缩放是浮点数(比如 1.25、1.5),而 DirectWrite 渲染文本时如果坐标落在非整数像素位置,字形边缘会变模糊。我一开始把所有布局坐标都保留浮点数,于是 150% 缩放下整个界面文字都是糊的,按钮边框却清晰锐利,极其让人崩溃。
解决方案是在文本布局阶段做像素对齐:所有文本绘制坐标都向最接近的整数像素取整。但问题来了,直接取整会导致文字和控件边框差出半个像素。最后我的做法是"容器尺寸对齐 + 文字居中取整":
- 布局阶段所有控件的位置和尺寸先做 round 操作,保证控件边界落在完整像素上
- 文本测量的 baseline 坐标再单独 round 一次
- 字体内边距在取整后做一次微调,确保视觉上仍然居中
这套组合在 100%、125%、150%、200% 四种缩放系数下都做了人工检查,文字清晰度基本能到原生应用水平。
6.2 鼠标事件的命中区域和视觉区域不一致
即时模式下,UI 每帧都在重绘,鼠标命中测试也必须每帧执行。早期的实现里,我是根据"当前帧的指令列表"逐个判断点是否在交互指令的范围内。但很快遇到了一个经典问题:按钮的视觉范围包含圆角内的小区域和阴影外的小区域,点击落在圆角外缘的透明阴影区域时也会触发按钮。
听起来像小事,实际使用时用户感知非常明显——点击按钮附近空白结果触发了按钮。排查后发现我的阴影指令和背景指令都注册成了交互区域。
修复方式是引入一个InteractiveRegion标记:只有显式声明为交互的指令才参与命中测试。按钮的视觉部分包括背景、圆角、阴影,但交互区域单独注册一个矩形(或者命中了背景和圆角才判定为交互)。这个修改也让引擎的指令列表能分成两层:视觉层和交互层,互不污染。
6.3 布局抖动:测量循环没有收敛
做动态布局的时候,我踩过一个特别有意思的坑:两列布局,左侧宽度根据右侧内容动态调整,右侧宽度又受到左侧压缩影响,导致每帧布局计算时两侧数值反复震荡,界面看起来像呼吸一样忽大忽小。
这个问题的根源是布局约束没有形成收敛解。浏览器解决这个问题靠的是 flex-basis 和 min/max 约束的组合,它们把子元素尺寸调整限定在一个单调区间内。我对照着简化了 XchyUI 的布局器:
- 每个容器计算时,先固定子元素的最小尺寸和最大尺寸
- 如果可用空间小于所有子元素最小尺寸之和,只压缩最后一个"弹性"子元素
- 所有尺寸调整只发生一次,不允许子元素反过来影响父容器尺寸(特殊情况除外)
加了这条"一次调整"铁律之后,布局抖动基本消失。这让我意识到,UI 引擎的布局器必须是最小化决策的——不能像浏览器那样为了支持各种复杂组合而反复协商尺寸。
6.4 即时模式下的焦点管理和输入法兼容
即时模式下控件没有持久对象,焦点就不能存在控件类上,得由引擎维护一个独立的焦点指针。当用户点击输入框时,焦点指针指向该输入框的绘制描述。下一帧重绘时,焦点可能切换了,但输入法可能还连着上一个输入框——这会导致中文输入法的候选窗口出现错位,更严重的还会让输入法残留在已失焦的输入框里。
我做了两件事解决:
- 焦点对象维护一个稳定的 ID(比如输入框名称或坐标),而非引用即时对象
- 窗口失活或有其他交互区域被点击时,立即调用
ImmReleaseContext释放输入法上下文
这个坑如果你用 Electron 完全不会碰到,因为 Chromium 早就把整个文本输入管线和 IME 都做好了。但自研引擎就需要自己承担这些系统联调的脏活。
6.5 滚动区域的性能:无脑全量重绘直接卡成 PPT
滚动容器是另一个暴露即时模式缺点的场景。Electron 里滚动区域有大量优化——只绘制可视区、用 layer 做缓存、滚动时位移而不是重绘。XchyUI 初始版本里,我简单地把滚动区域当成普通容器,每次滚动位置变化就全量重绘整个容器及所有子控件。结果是列表 200 行时滚动就开始卡,600 行时直接 PPT 效果。
优化方案分三层:
- 把滚动内容做成一个离屏画布,滚动时先平移画布,只在内容移出边界时才增量绘制新露出的部分
- 对滚动容器内的每行控件,快速判断是否在可视区之外,是则跳过绘制指令生成
- 对于稳定的静态行(带状态的行),用缓存指令列表而不是每次重新生成
这套优化让我理解了为什么浏览器要做合成器分层——纯靠 CPU 重绘永远顶不住滚动场景的帧率要求。现在 XchyUI 的列表滚动帧率稳定在 60FPS,且 CPU 占用只有 Electron 版本的二分之一不到。
6.6 与外部 C++ 组件交互时遇到 Access Violation
最后这个坑可能更偏 C# 工程经验,和 UI 引擎也不算直接相关,但确实在接入段遇到了。我需要让 XchyUI 的界面调用一个第三方 C++ 工业组件(通过 P/Invoke),结果经常出现Access Violation C0000005,而且不稳定复现。
排查了三天,最终发现不是 P/Invoke 签名的问题,而是那个 C++ 库要求调用线程必须是 STA(Single-Threaded Apartment)模式,而我在一个后台线程里做了初始化。这个错非常隐蔽,因为它不立即崩溃,而是内存越界后过几百毫秒才在随机位置崩。
解决方案是把组件的初始化转移到主线程的窗口消息循环里,确保 STA 线程上下文一致。这个经历让我理解到,自研 UI 引擎虽然核心是自己的,但免不了要和各种外部原生组件打交道,线程模型一定要及早定死并在文档里写清楚。
7. 时机成熟的判断标准:你在什么情况下才应该考虑自研 UI 引擎
到这里,我相信会有朋友想问:XchyUI 能不能直接用于生产项目?我的回答是:看项目形态。自研 UI 引擎不是万金油,它有自己的适用边界,强行在所有场景下替换 Electron 并不现实。
7.1 它非常适合的场景
- 工业上位机软件:界面相对固定,需要常驻稳定、低内存占用、低功耗机箱环境
- 物联网设备管理工具:现场设备配置、状态看板、诊断面板
- 医疗桌面终端:对启动速度、稳定性要求苛刻的受控环境
- 内部工具链:各种运维、数据分析的小型桌面应用
这些场景有一个共性:界面复杂度可控,对浏览器能力几乎没有依赖,核心是低资源占用和高稳定性。
7.2 它目前不适合的场景
- 重度富文本应用:比如在线文档编辑器、代码编辑器,这类应用需要大量文本排版能力,目前 XchyUI 的文本引擎还没有做到那么深的段落样式控制
- 复杂地图/图形编辑器:如果业务核心在画布交互、节点连线、图元编辑,浏览器生态里成熟方案太多,自研成本不划算
- 跨平台优先的团队:XchyUI 目前只在 Windows 上验证过,Linux/macOS 的支持还在设计中,如果你的产品要三端覆盖,暂时不要考虑自研
这些边界是实际的工程事实。自研 UI 引擎的目标不是取代所有 Electron 方案,而是在这个细分赛道上提供一个更轻、更快、更可控的选项。
7.3 后续路线:鸿蒙适配和更多原生控件的想法
XchyUI 的下一步规划主要有两块。第一块是跨平台适配,之前买过商用框架,但授权费对个人项目来说太高,现在更多精力放在研究如何将渲染指令层对接到其他操作系统的窗口系统上。第二块是控件生态扩展,内置控件目前只有按钮、输入框、列表、滚动区域这些基础款,接下来计划加入数据表格、树形控件、图表组件,尽量覆盖工厂上位机软件的常见需求。
最近我也在评估是否将 XchyUI 作为 OpenHarmony 应用的原生 UI 组件方案之一进行适配。C# 通过 .NET 的跨平台能力是可以跑的,但窗口系统和输入法这一层必须针对新平台重写,工作量不小。这条路线还在探索阶段,有结论了再来更新。
说了这么多,最后分享一个真实的感受:做自研引擎最难的永远不是写代码,而是说服自己"这个问题真的值得自己解决"。我用了三年半的 Electron,才终于承认有些问题是架构层面的东西,优化堆栈根本解决不了。XchyUI 把我从无力感里解放了出来——当你的 UI 栈每一层都在自己掌控之内,用户报性能问题时你脑子里会直接浮现出对应的指令列表和渲染路径,这种确定性是 Web 套壳方案永远给不了的。
如果你也在认真考虑类似的自研路线,我的建议是先从一个小模块开始,比如只做一个用 GPU 绘制按钮和文本的最小窗口,跑通一遍 CPU 到 GPU 的完整管线。相信我,当你看到自己手写的高效 UI 引擎在 42MB 内存里流畅运行时,那种踏实感是值得的。