← blog

October 8, 2026 · 7 min read

What landed since August: 26.9.0, four new backends, CSS theming, and the behaviour audit

The last post here was written against 26.0.30. Since 2026-08-17 the repository has taken 557 commits and the current release is 26.9.0. This is a roundup for existing users and evaluators: what changed, how mature each piece is, and the one thing you must do when you upgrade.

Four new backends

There were three hosts — Avalonia, Uno, Headless. There are now seven, all running the same control set; the difference is who creates the window and presents the Skia surface.

GTK 4 (Majorsilence.Forms.Gtk4) is a Linux-first host built on gir.core: a real Gtk.Window per form, GLib main loop, also usable on Windows and macOS with the GTK 4 runtime. Select it explicitly with Gtk4Application.Use (); before Application.Run. Embedding works both ways (ToGtkWidget(), ToGtkWindow()), NativeControlHost has no airspace problem because GTK 4 composites one render tree, and WebBrowser is backed by WebKitGTK 6.0. Verified on Wayland. Known gaps: no screen-position control (GTK 4 removed the API), SetIcon(byte[]) is a no-op, native file pickers return empty so fallback dialogs are used, integer scale factor only, not AOT-analysed.

Terminal (Majorsilence.Forms.Terminal, 2026-10-04) hosts a form inside a console as a single view — the form fills the terminal, no title bar, like a phone. It uses Kitty graphics or Sixel at real pixel resolution where available, otherwise Unicode block elements; detection is by querying the terminal, and MF_TERMINAL_GRAPHICS=halfblock|kitty|sixel pins a mode. Mouse and keyboard work; Ctrl+C always exits. Selected with TerminalApplication.Use (options);. Verified in xterm and WezTerm only. No native pickers, NativeControlHost or webview.

WinForms (Majorsilence.Forms.WinForms) is a Windows-only migration backend: real System.Windows.Forms windows on the Win32 pump, Skia presented through a GDI bitmap. It exists so you can embed Majorsilence.Forms controls in an existing WinForms app one control at a time — myMfControl.ToWinFormsControl(), myForm.ToWinFormsForm(), MajorsilenceFormsPresenter — then swap the host to Avalonia or Uno once everything is ported. No gestures, no IWebViewFactory. It is distinct from the older WindowsFormsInterop, which bridges whole forms on the Avalonia host.

WPF (Majorsilence.Forms.Wpf) has the same shape and purpose for WPF apps: a real WPF Window, WriteableBitmap presentation, ToWpfElement() and ToWpfWindow(). Select it with Platform.Backend = new Majorsilence.Forms.Wpf.WpfPlatformBackend ();.

Avalonia, WinForms and GTK 4 give genuine OS-level modal dialogs; Uno opens an independent window, so use Form.ShowDialog(parent) there.

.NET Framework 4.8 and netstandard2.0

The core packages — Majorsilence.Forms, .Drawing.Common, .Telerik — now multi-target net8.0, net10.0 and netstandard2.0, and the WinForms and WPF backends add net48. A .NET Framework 4.8 application can therefore host Majorsilence.Forms controls without first moving to modern .NET, which removes an ordering problem many migrations had.

The behaviour-gap audit

The two API-surface gap plans (WinForms and GDI+) are at zero: every member upstream has is declared. That was always the less interesting half. A member that exists but stores a value nobody reads, or raises an event nobody fires, compiles your migrated app and then quietly does nothing.

So on 2026-08-25 a twelve-area audit compared each area against the upstream implementation and recorded 483 findings where behaviour differed. Since then phases 0–4 and most per-control families have landed. Concrete items now real: the ProcessCmdKey pre-processing chain, a single focus/validation choke point, the title bar out of the client area, AutoScaleMode.Font actually scaling, live data binding (CurrencyManager, BindingNavigator), ListView.View = Details, form lifecycle in upstream order (Load → VisibleChanged → Activated, Shown posted), DataGridView column reordering, Ctrl+Z in text controls, NotifyIcon in the system tray, Application.AddMessageFilter, and disposing a form disposing its controls.

The hollow surface is also measured now: baseline files pin the known no-op methods, inert events and stored-only properties, so adding to them is a conscious act rather than an accident. The stub policy is unchanged — no-op or return default, never NotImplementedException.

Logical units: the one thing you must do

On 2026-10-01 ClientRectangle, ClientSize and the paint canvas (OnPaint, Paint, e.ClipRectangle, e.Canvas) became logical units, matching Width/Height/Bounds and MouseEventArgs. The framework scales the canvas to the display for you.

If a custom control called e.Graphics.ScaleTransform (e.Scaling, e.Scaling), remove it — the drawing is now scaled twice. Device pixels are still reachable through the Scaled* family (ScaledWidth, ScaledBounds, …), PaintEventArgs.Scaling and LogicalToDeviceUnits. The one exception is owner-draw events (DrawItem, DrawNode, CellPainting), which still hand you device-pixel bounds. Run your tests with MF_HEADLESS_SCALE=2 to catch anything that depends on the old behaviour.

Browser and mobile

On net10.0-browser, -android and -ios the Avalonia backend reports CanRunModalLoop = false, and the blocking calls — Form.ShowDialog, MessageBox.Show, the file pickers, TaskDialog.ShowDialog, VbInteraction.MsgBox/InputBox, RadMessageBox.Show — now throw PlatformNotSupportedException naming the async twin before anything is shown, rather than hanging. The async forms (ShowDialogAsync, MessageBox.ShowAsync, FileDialog.ShowDialogAsync, …) work on every host, so the idiom is an async void handler with await. A Roslyn analyzer in the core package finds these ahead of time — MFB001 blocking modal call, MFB002 synchronous wait on a Task, MFB003 Thread.Sleep, each with a code fix — active for browser TFMs or with majorsilence_forms.browser_target = true in .editorconfig.

The browser target also keeps an accessibility DOM beside the canvas: one transparent, click-through element per control with ARIA role, name, state and bounds, built from the framework’s own automation tree. Screen readers, find-in-page and DOM-based test tools can now see the UI.

On Android and iOS the on-screen keyboard is raised on TextBox focus, TextBoxBase.InputKind picks the keyboard type, safe-area insets apply through Form.SafeAreaPadding, and WindowBase.BackRequested handles the Android back button. Honest status: Android has had an initial real-device pass (boot, taps, render scaling, touch scroll confirmed on hardware); keyboard, safe-area and rotation are unit-tested only. iOS CI compiles the real head and launches it in a simulator smoke check, but nobody has run it interactively and the job is still continue-on-error.

Phone-style layout controls arrived alongside: StackPanel, Card, RichListBox (multi-line templated rows) and NavigationHost (a page stack with a back button).

CSS theming and Theme Studio

Themes can now be written as a strict, documented CSS subset: @theme "Ocean" extends Dark;, :root tokens one per Theme property, control-type rules like Button:hover { … }, parts via Type::part. The parser never fails silently — ThemeStyleSheet.Parse collects diagnostics. Load with Theme.LoadFromCssFile, or Theme.RegisterThemeCssFromFile + Theme.ApplyTheme ("Ocean"); export with Theme.ExportCss. Every control, including the Telerik compat layer, has a selector, and the older <Theme> XML still works.

Two companion packages apply the same sheet to other hosts: Majorsilence.Forms.Theming.WinForms restyles real System.Windows.Forms controls (Windows only) and Majorsilence.Forms.Theming.Avalonia restyles native Avalonia Fluent controls, so one CSS file can theme a mixed migration app. samples/ThemeStudio is a live editor with preview and diagnostics; prebuilt binaries are attached to GitHub Releases.

MVVM, Essentials, animation

Majorsilence.Forms.Mvvm is trim- and AOT-safe wiring over INotifyPropertyChanged and ICommand, no reflection, no toolkit dependency: viewModel.Observe (nameof (VM.Count), vm => vm.Count, …), two-way BindText/BindChecked/BindSelectedIndex/BindValue, BindCommand, and a BindingScope to dispose it all. It works alongside CommunityToolkit.Mvvm.

Majorsilence.Forms.Essentials keeps per-platform capabilities out of core: SecureStorage, Speech text-to-speech, Launcher.OpenAsync (http/https/mailto/tel/sms) and FileSystem.OpenAppPackageFileAsync. Everything degrades to a no-op; check IsSupported.

control.RequestAnimationFrame plus Majorsilence.Forms.Animation (Tween<T>, Easing, Animator) give display-aligned animation on Avalonia and a manual clock on Headless.

Tooling

Upgrade notes

Where to read more