← 博客

2026年10月8日 · 7 分钟阅读

八月以来的进展:26.9.0、四个新后端、CSS 主题,以及行为审计

本页为译文。如与英文版有出入,以英文版为准。 English

上一篇文章是基于 26.0.30 写的。自 2026-08-17 以来,仓库新增了 557 次提交,当前发布版本是 26.9.0。 这是一篇面向现有用户和评估者的汇总:有哪些变化,各部分的成熟度如何,以及升级时你必须做的那一件事。

四个新后端

之前有三个宿主——Avalonia、Uno、Headless。现在有七个,全都运行同一套控件;区别只在于由谁来创建窗口、 呈现 Skia 表面。

GTK 4(Majorsilence.Forms.Gtk4)是一个 Linux 优先的宿主,基于 gir.core 构建:每个窗体对应一个真正的 Gtk.Window,使用 GLib 主循环,在装有 GTK 4 运行时的 Windows 和 macOS 上同样可用。在 Application.Run 之前用 Gtk4Application.Use (); 显式选择它。嵌入可双向进行(ToGtkWidget()、ToGtkWindow()), NativeControlHost 没有空域(airspace)问题,因为 GTK 4 以单一渲染树进行合成;WebBrowser 由 WebKitGTK 6.0 支撑。已在 Wayland 上验证。已知缺口:无法控制屏幕位置(GTK 4 移除了该 API), SetIcon(byte[]) 是空操作,原生文件选择器返回空结果因此使用回退对话框,仅支持整数缩放因子, 尚未做 AOT 分析。

Terminal(Majorsilence.Forms.Terminal,2026-10-04)把一个窗体以单视图的形式宿主在控制台里—— 窗体填满终端,没有标题栏,就像手机一样。在可用的情况下,它以真实像素分辨率使用 Kitty graphics 或 Sixel, 否则使用 Unicode 块元素;检测方式是向终端查询,MF_TERMINAL_GRAPHICS=halfblock|kitty|sixel 可锁定某种 模式。鼠标和键盘可用;Ctrl+C 始终退出。用 TerminalApplication.Use (options); 选择它。仅在 xterm 和 WezTerm 中验证过。没有原生选择器、NativeControlHost 或 webview。

WinForms(Majorsilence.Forms.WinForms)是一个仅限 Windows 的迁移后端:在 Win32 消息泵上使用真正的 System.Windows.Forms 窗口,Skia 通过 GDI 位图呈现。它的存在是为了让你能把 Majorsilence.Forms 控件一个 一个地嵌入现有的 WinForms 应用——myMfControl.ToWinFormsControl()、myForm.ToWinFormsForm()、 MajorsilenceFormsPresenter——等全部移植完成后再把宿主切换到 Avalonia 或 Uno。不支持手势,没有 IWebViewFactory。它不同于更早的 WindowsFormsInterop,后者是在 Avalonia 宿主上桥接整个窗体。

WPF(Majorsilence.Forms.Wpf)对 WPF 应用而言有着相同的形态和用途:一个真正的 WPF Window、 WriteableBitmap 呈现、ToWpfElement() 和 ToWpfWindow()。用 Platform.Backend = new Majorsilence.Forms.Wpf.WpfPlatformBackend (); 选择它。

Avalonia、WinForms 和 GTK 4 提供真正的操作系统级模态对话框;Uno 则会打开一个独立窗口,因此在那里请使用 Form.ShowDialog(parent)。

.NET Framework 4.8 与 netstandard2.0

核心包——Majorsilence.Forms、.Drawing.Common、.Telerik——现在多目标于 net8.0、net10.0 以及 netstandard2.0,WinForms 和 WPF 后端还增加了 net48。因此,一个 .NET Framework 4.8 应用可以 在不先迁移到现代 .NET 的情况下宿主 Majorsilence.Forms 控件,这消除了许多迁移项目曾面临的先后顺序问题。

行为缺口审计

两份 API 表面缺口计划(WinForms 和 GDI+)已归零:上游拥有的每一个成员都已声明。但这一直是不太有意思的 那一半。一个存在却只是存储一个无人读取的值、或者引发一个无人触发的事件的成员,能让你迁移后的应用编译 通过,然后悄悄地什么也不做。

因此在 2026-08-25,一次覆盖十二个领域的审计把每个领域与上游实现逐一对比,记录了 483 项行为不一致的 发现。此后,阶段 0–4 和大部分按控件族划分的工作已经落地。现在已经真实可用的具体项目包括:ProcessCmdKey 预处理链、单一的焦点/验证汇聚点、标题栏移出客户区、AutoScaleMode.Font 真正进行缩放、实时数据绑定 (CurrencyManager、BindingNavigator)、ListView.View = Details、按上游顺序执行的窗体生命周期 (Load → VisibleChanged → Activated,Shown 以投递方式触发)、DataGridView 列重排、文本控件中的 Ctrl+Z、 系统托盘中的 NotifyIcon、Application.AddMessageFilter,以及释放窗体时同时释放其控件。

空心的表面现在也被度量了:基线文件锁定了已知的空操作方法、惰性事件和仅存储的属性,因此再往里添加 就成了有意识的行为,而不是意外。存根策略不变——空操作或返回默认值,绝不抛出 NotImplementedException。

逻辑单位:你必须做的那一件事

在 2026-10-01,ClientRectangle、ClientSize 和绘制画布(OnPaint、Paint、e.ClipRectangle、 e.Canvas)改为使用逻辑单位,与 Width/Height/Bounds 和 MouseEventArgs 保持一致。框架会替你 把画布缩放到显示器。

如果某个自定义控件调用了 e.Graphics.ScaleTransform (e.Scaling, e.Scaling),请把它删掉——否则绘制 现在会被缩放两次。设备像素仍可通过 Scaled* 系列(ScaledWidth、ScaledBounds……)、 PaintEventArgs.Scaling 和 LogicalToDeviceUnits 获得。唯一的例外是所有者绘制事件(DrawItem、 DrawNode、CellPainting),它们交给你的仍然是设备像素边界。用 MF_HEADLESS_SCALE=2 运行你的测试, 以捕获任何依赖旧行为的地方。

浏览器与移动端

在 net10.0-browser、-android 和 -ios 上,Avalonia 后端报告 CanRunModalLoop = false,而那些阻塞调用—— Form.ShowDialog、MessageBox.Show、文件选择器、TaskDialog.ShowDialog、VbInteraction.MsgBox/InputBox、 RadMessageBox.Show——现在会在显示任何内容之前抛出 PlatformNotSupportedException 并指出对应的异步 版本,而不是挂起。异步形式(ShowDialogAsync、MessageBox.ShowAsync、FileDialog.ShowDialogAsync……) 在每个宿主上都可用,因此惯用写法是带 await 的 async void 处理程序。核心包中的一个 Roslyn 分析器可以 提前找出这些问题——MFB001 阻塞式模态调用、MFB002 对 Task 的同步等待、MFB003 Thread.Sleep, 每一项都附带代码修复——对浏览器 TFM 自动启用,或在 .editorconfig 中设置 majorsilence_forms.browser_target = true 启用。

浏览器目标还在画布旁边维护一棵无障碍 DOM:每个控件对应一个透明、可点击穿透的元素,带有 ARIA 角色、 名称、状态和边界,由框架自身的自动化树构建而成。屏幕阅读器、页内查找以及基于 DOM 的测试工具现在都能 看到这个 UI 了。

在 Android 和 iOS 上,TextBox 获得焦点时会弹出屏幕键盘,TextBoxBase.InputKind 决定键盘类型,安全区 内边距通过 Form.SafeAreaPadding 生效,WindowBase.BackRequested 处理 Android 的返回键。坦白说明现状: Android 已完成一轮初步的真机测试(在硬件上确认了启动、点击、渲染缩放、触摸滚动);键盘、安全区和旋转 仅有单元测试。iOS CI 会编译真正的应用头并在模拟器中做启动冒烟检查,但还没有人以交互方式运行过它, 该任务仍然是 continue-on-error。

手机风格的布局控件也随之到来:StackPanel、Card、RichListBox(多行模板化行)和 NavigationHost (带返回按钮的页面栈)。

CSS 主题与 Theme Studio

主题现在可以用一个严格、有文档的 CSS 子集来编写:@theme "Ocean" extends Dark;、每个 Theme 属性对应 一个 :root 令牌、像 Button:hover { … } 这样的控件类型规则、通过 Type::part 指定部件。解析器绝不会 悄悄失败——ThemeStyleSheet.Parse 会收集诊断信息。用 Theme.LoadFromCssFile 加载,或用 Theme.RegisterThemeCssFromFile + Theme.ApplyTheme ("Ocean");用 Theme.ExportCss 导出。每个控件, 包括 Telerik 兼容层,都有选择器,而更早的 <Theme> XML 仍然可用。

两个配套包把同一份样式表应用到其他宿主上:Majorsilence.Forms.Theming.WinForms 重新设置真正的 System.Windows.Forms 控件的样式(仅限 Windows),Majorsilence.Forms.Theming.Avalonia 重新设置原生 Avalonia Fluent 控件的样式,因此一个 CSS 文件就能为混合的迁移应用设置主题。samples/ThemeStudio 是一个 带预览和诊断的实时编辑器;预构建的二进制文件附在 GitHub Releases 上。

MVVM、Essentials、动画

Majorsilence.Forms.Mvvm 是建立在 INotifyPropertyChanged 和 ICommand 之上、对裁剪和 AOT 安全的接线 层,不用反射,不依赖任何工具包:viewModel.Observe (nameof (VM.Count), vm => vm.Count, …)、双向的 BindText/BindChecked/BindSelectedIndex/BindValue、BindCommand,以及一个用来统一释放的 BindingScope。它可以与 CommunityToolkit.Mvvm 一起使用。

Majorsilence.Forms.Essentials 把各平台特有的能力放在核心之外:SecureStorage、Speech 文本转语音、 Launcher.OpenAsync(http/https/mailto/tel/sms)和 FileSystem.OpenAppPackageFileAsync。一切都会降级为 空操作;请检查 IsSupported。

control.RequestAnimationFrame 加上 Majorsilence.Forms.Animation(Tween<T>、Easing、Animator) 在 Avalonia 上提供与显示器对齐的动画,在 Headless 上提供手动时钟。

工具

升级说明

延伸阅读