Changelog
All notable changes to Glass.Message are documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
How releases work: every tagged release on GitHub ships downloadable assets — the multi-framework NuGet package (
.nupkg+.snupkg), a raw DLL zip per target framework, and self-contained demo apps (x64 + x86) you can run without installing .NET. The notes below become the release description automatically. See the Releases page.
1.0.6 - 2026-10-07
A quality, performance, and concurrency hardening release. No new features and no public API changes — this release is exclusively internal correctness, memory/resource, threading, and rendering-quality fixes found during a full engineering audit of the codebase, verified with real stress, concurrency, and DPI-rendering tests rather than code review alone.
Thread Safety & Concurrency
- Unbounded UI-thread backlog from rapid
GlassProgressControllerupdates —SetValue/SetMessage/SetActivityeach previously queued a separateBeginInvokecall per invocation. A worker thread calling these at high frequency (or several worker threads calling them concurrently) could flood the dialog’s UI-thread message queue with thousands of queued delegates, growing memory and leaving the dialog applying a long backlog of stale values instead of the current one. Each update kind is now coalesced through a lock-free drain loop so at most one marshaled call per kind is ever outstanding, and only the latest value is ever applied — the worker thread is never blocked and the UI never falls behind. This is purely an internal dispatch change; the publicGlassProgressControllerAPI and its threading guarantees are unchanged. - Verified, with a live message pump and six concurrent worker threads, that
there is no deadlock, no cross-thread exception, and no hang under sustained
concurrent controller updates, including a 15-trial concurrent
update/
Dispose()race. - Verified that
GlassMessage’s async cancellation (CancellationToken) reaches a terminal task state — never hangs — when cancellation arrives before showing, immediately after showing, while visible/animating, concurrently with a button click, or while the owner is being disposed. - Verified that 25 simultaneously-stacked toasts and 50 rapid sequential toast show/dismiss cycles leave no stale entries in the internal active-toast list, and that firing DPI changes while a background thread hammers a progress controller and a toast is simultaneously open does not corrupt dialog/toast state or throw.
Performance
- Progress-controller coalescing (above) removes the unbounded-backlog growth path from high-frequency producers, keeping the UI thread’s queue bounded under load.
Memory / Resource Management
- Verified, via
GetGuiResources(the API backing Task Manager’s GDI/USER object columns), that GDI and USER handle counts remain bounded across 300 dialog and 200 toast create/show/paint/dispose cycles — no unbounded native handle growth. GlassDialogandGlassToast’sToastFormnow tolerate double-Dispose()safely with their additional cached GDI+ fields.
GDI/GDI+ Allocation Reduction
- Toast notifications allocated fresh GDI+ objects on every animation frame —
GlassToast’sToastForm.OnPaintbuilt a newGraphicsPath, a newLinearGradientBrush, a second newGraphicsPath, and two newPens on every repaint, including every tick of the fade-in/fade-out animation. These are now built once (lazily, since the final corner geometry is only known after the handle is created) and reused for the toast’s lifetime, matching the caching pattern already used byGlassDialog/GlassButton, and are disposed when the toast closes. GlassDialogclose-button and countdown-ring pens reallocated every paint — the close button’s “×” pen (which changes only on hover) and the auto-close countdown ring’s track/fill pens were allocated fresh on everyOnPaint, including every mouse-move-driven repaint and every countdown tick. They are now cached (keyed on hover state for the close button) and rebuilt only when a DPI change triggers a dialog rebuild, instead of on every frame.
Rendering / Visual Polish
- Verified, from actual rendered screenshots (not code review alone), that the close button, countdown ring, progress-bar fill, checkbox, and button borders render with sharp rounded corners, crisp single-pixel-consistent strokes, and no visible seams or artifacts, across idle, hover, and focused states.
DPI / Animation
- Verified, by driving
GlassDialog’s realWM_DPICHANGEDhandler and capturing actual composited window output, that the dialog rescales cleanly and proportionally at 100%, 125%, 150%, 175%, and 200% DPI, with no cropping or layout corruption at any scale.
Tests
- Added regression tests covering the three fixes above: concurrent/
high-frequency
GlassProgressControllerupdates from multiple threads complete without exceptions; the controller tolerates calls that arrive before the dialog’s window handle exists;GlassDialogandGlassToast’sToastFormtolerate double-Dispose()safely now that they own additional cached GDI+ resources; and a real handle-creation + paint pass on a toast exercises the new cached-resource code path end-to-end. - Added runtime stress/concurrency harnesses that create, show, paint, and
dispose real
Form-derived windows under an actual Win32 message pump (rather than only reviewing code): 300-cycle dialog and 200-cycle toast create/dispose loops asserting bounded GDI/USER handle growth; a 6-threadGlassProgressControllerhammering test running concurrently with message pumping; a dispose-race test; async-cancellation-lifecycle tests covering cancellation before/immediately-after/during-visible/concurrent-with-click/ owner-disposed scenarios; and toast concurrency tests for 25 simultaneously stacked toasts and 50 rapid sequential show/dismiss cycles, verified via the internal active-toast list for stale entries. - Added a DPI visual-capture harness that drives
GlassDialog’s realWM_DPICHANGEDhandler viaSendMessage(not reflection) and saves actualPrintWindow-composited screenshots at 100/125/150/175/200% for visual inspection, plus a mixed-DPI test that fires simulated DPI changes while a background thread hammersGlassProgressControllerand a toast is simultaneously open, asserting no exception and no corrupted dialog/toast state. - The test project now multi-targets all four supported frameworks
(
net481,net8.0-windows,net9.0-windows,net10.0-windows); the full suite was executed — not just built — on every one of them.
1.0.5 - 2026-06-16
A polish and code-quality release. The live progress dialog now animates more smoothly, a hairline rendering artifact on the bar is gone, and the source has been cleaned up to satisfy the Roslynator analyzer set. No public API changes.
Fixed
- Smoother live progress — calling
GlassProgressController.SetValue(...)now eases the determinate fill toward the new value instead of snapping to it, so a jump (e.g. 10 % → 60 %) glides across roughly half a second. The animation timer spins up only while the bar is catching up and retires itself once it settles, so an idle dialog still does no per-frame work. - Hairline “background edge” on the progress bar — the determinate fill was drawn through an aliased region clip whose hard edge exposed a 1 px rim of the lighter track behind the bar; the gradient also bled a thin seam at its edge. The fill is now painted as an antialiased rounded path with an inset gradient, so the bar reads as a clean, solid pill with no light fringe.
Changed
- Code-quality cleanup (internal only) — applied the Roslynator style/analyzer
fixes across the library and tests: conditional access (
?.) over null-check ternaries and chains, a read-only backing field, a static helper, concrete field types for owner-drawn controls, and a trimmed empty type body. Behaviour and the public API are unchanged.
1.0.4 - 2026-06-14
A feature release that makes the live progress dialog activity-aware — the bar can now animate to match the operation it represents (upload, download, sync, compress, …) — plus visual-polish and performance fixes for the progress bar.
Added
-
ProgressActivity(GlassProgressActivity)builder method and the newGlassProgressActivityenum (17 activities). The progress bar paints a distinct, eye-catching animation grouped into six visual families:Family Animation Activities Packets glowing dots glide along Upload·Download·StreamChevrons diagonal bands march FileTransfer·ImportSegments rounded blocks march Compress·Extract·ExportWave a sinusoidal ripple Backup·Restore·SyncPulse the fill breathes Encrypt·Decrypt·ConnectingComet a soft shine sweeps Install·Search·ProcessingStripes flow forward for outgoing work, backward for incoming, and ease both ways for a two-way sync. Works on both determinate and indeterminate bars; use
GlassProgressActivity.Nonefor a plain bar. -
GlassProgressController.SetActivity(GlassProgressActivity)— change the flow animation live as an operation moves between phases (e.g.CompressthenUpload).
Fixed
- Progress bar “box” artifact — the progress panel now paints the dialog’s themed gradient behind itself (like every other custom control) instead of clearing to a flat colour, so it blends into the window seamlessly rather than showing a dark rectangle around the track.
- Progress bar visibility — boosted the contrast of every activity animation so the styles read clearly at the standard bar height.
Performance
- No per-frame allocations in the animated progress panel — the glow-dot sprite, themed-background gradient, track border pen, flow brushes, and chevron geometry are all cached/reused, so an animating progress dialog no longer churns GDI+ objects at 30 fps.
1.0.3 - 2026-06-13
A maintenance release — documentation, packaging, and CI only. No library code or public API changes.
Changed
- Docs — removed an unsubstantiated adoption claim from the NuGet README
(
README.nuget.md). - Packaging — switched to a more reliable NuGet version badge and refreshed
the README badges (camo cache
cacheSecondslowered so the version badge updates promptly). - CI — bumped the release workflow actions to Node 24:
actions/checkout@v6,actions/upload-artifact@v7,actions/download-artifact@v8, andactions/setup-dotnet@v5; step names normalised to PascalCase.
1.0.2 - 2026-06-12
Added
showCapsLockHintparameter onInputPassword()— opt out of the Caps Lock badge per dialog with.InputPassword("placeholder", showCapsLockHint: false). The hint stays on by default; suppress it for PIN entry, kiosk flows, or anywhere uppercase input is intentional. Showcased by the new Password — Caps Lock Hint Off demo (Demo_PasswordNoCapsLock).
Fixed
Thread safety & concurrency
- Volatile static properties —
DefaultTheme,UseRoundedCorners, andPlaySystemSoundsinGlassMessagenow usevolatilebacking fields, ensuring writes from any thread are immediately visible on all architectures including ARM64. - CancellationToken callback crash (C1) — the
ct.Registercallback inShowModelessnow wrapsBeginInvokein atry/catchforInvalidOperationException/ObjectDisposedException, preventing an unhandled exception that could crash the process when the dialog’s handle is destroyed concurrently with cancellation. - Hanging async tasks on shutdown (C2, C7) —
GlassMessage.ShowModelessandGlassToast.ShowAsyncnow subscribe to the form’sDisposedevent and callTrySetResultas a safety net, guaranteeing that every awaited task completes even whenApplication.Exitdisposes forms without firingFormClosed. - Concurrent GDI+ bitmap access (C5) — system icons loaded from
SystemIcons/MessageBoxIconare now cloned per-dialog via a newResolveIconhelper. The_ownsIconBitmapflag ensures clones are disposed while shared bitmaps are left alone. - Toast options mutation —
GlassToast.Show/ShowAsyncresolved the theme into a local variable instead of writing back into the caller’sGlassToastOptions, so the same options object can be reused safely. - Toast race: form added to active list before handlers registered —
FormClosedandDisposedare now registered before_active.Add, eliminating the window where a fast OSWM_CLOSEcould leave a ghost entry in the stack. - Toast
OnClickexception suppressing dismiss — wrapped intry/finallyso a throwing callback never prevents the toast from closing.
Animation & DPI
- Scale animation close-button hit-test (#5) —
_closeBtnBoundsis now recalculated after every scale frame inApplyAnimationFrame, so the × button remains clickable throughout the opening/closing animation. - DPI change corrupting animation state (#7) —
Rebuild()now resets all animation flags (_scaleActive,_slideActive,_fadingOut) and disposes the fade timer before rebuilding, preventing leftover state from corrupting the next animation cycle. - Wrong initial DPI scale — the dialog now reads the DPI of the monitor under
the cursor at construction time via
GetDpiForMonitor+MonitorFromPointP/Invokes instead of using the primary monitor’s device context. - Width cap using primary monitor —
MeasureFormnow accepts atargetScreenparameter and caps width against the target screen’s working area, not the primary.
GDI+ resource management
- Per-frame pen allocation (#11) — the input border pen is now pre-allocated
once in the constructor as
_inputBorderPenand disposed inDispose(), avoiding GDI+ handle churn on everyOnPaintcall. - Preset theme double-instantiation (#18) —
GlassTheme.Darkis now the same object reference asGlassTheme.Default, halving font allocations for the default case and making equality checks (Dark == Default) correct.
Drag behaviour
- Dialog draggable off-screen (#3) —
OnMouseMovenow callsClampToScreenafter each drag update, keeping the title bar (and drag handle) always reachable.
API correctness
GlassResultnull implicit conversion (#19) — theimplicit operator DialogResultnow usesr?.Button ?? DialogResult.Noneinstead ofr.Button, preventing aNullReferenceExceptionwhen the result is assigned fromnull.InputDropdownnull argument crash (#1) — anullitemsenumerable no longer throws; an empty dropdown is created instead.EM_SETCUEBANNERwParam — thewParamfor theEM_SETCUEBANNERmessage inPlaceholderTextBoxis now1u(redraw even if focused), matching the MSDN spec and ensuring placeholder text renders reliably.GlassDialogConfigproperty access modifiers (#17) — all properties on the already-internalGlassDialogConfigclass are now explicitlyinternal, removing misleadingpublicmodifiers that had no external effect but implied wider accessibility than intended.
Docs & XML
Buttons(params string[])XML doc (#4) — updated to document the 3-label maximum and explain that labels beyond the third are silently ignored.
Tests
- Added
[CollectionDefinition("GlassStaticState")]and applied[Collection("GlassStaticState")]toGlassBuilderTestsandGlassMessageStaticTests, serialising the classes that mutate global static state so they cannot interfere with each other under xUnit parallel execution. - Added
CalcToastLocationTests— pure coordinate-math tests for all sixToastPositionvalues plus stack-offset assertions. - Added
V102RegressionTests— targeted regression tests for theGlassResultnull conversion,Dark == Defaultidentity, andInputDropdown(null)safety. - Updated xUnit packages:
xunitandxunit.runner.visualstudio→ 2.9.3,Microsoft.NET.Test.Sdk→ 17.12.0.
Progress controller
- Documented the intentional fire-and-forget design of
BeginInvokeinGlassProgressController.Marshal—SetValue/SetMessageare best-effort UI refreshes that must not block worker threads;Completionremains the correct await point.
Marquee timer
- Halved marquee progress timer pressure: interval 16 ms → 33 ms (≈ 30 fps), phase step scaled proportionally so visual speed is unchanged.
1.0.1 - 2026-06-06
First public release. 🎉
Added
- Drop-in
MessageBoxreplacement —GlassMessage.Show(...)overloads that are signature-compatible withSystem.Windows.Forms.MessageBox. - Fluent builder —
GlassMessage.Create(...)for composing dialogs. - Async dialogs — non-blocking, awaitable, and cancellable via
CancellationToken:GlassMessage.ShowAsync(...)for the basic message-box shape.GlassBuilder.ShowAsync()/ShowExAsync()so rich dialogs (input, checkbox, dropdown) can be awaited too. Cancellation yieldsDialogResult.Cancel.
- Rich result —
ShowEx()/ShowExAsync()return aGlassResult(button + checkbox state + typed input); implicitly converts toDialogResult. - Live progress —
GlassBuilder.ShowProgress()returns a thread-safeGlassProgressControllerto update the bar (SetValue) and caption (SetMessage) while work runs,Complete()it, awaitCompletion, and detect user dismissal viaWasCanceledByUser. - Theming —
Dark,Light,Mica,HighContrast, andWindowsClassicpresets, plus fully customisableGlassThemeinstances. - Auto theme detection —
GlassTheme.AutoDetect()/IsSystemDark()follow the Windows light / dark / high-contrast preference. - Modern chrome — Windows 11 rounded corners (DWM), Mica backdrop, and an Acrylic blur fallback on Windows 10.
- Inline inputs — single-line text, password (reveal eye + Caps Lock hint), multi-line, and drop-down.
- Checkbox, expandable detail panel, determinate / indeterminate progress bars, countdown auto-close, and custom bitmap icons.
- System sounds —
GlassBuilder.Sound()and the globalGlassMessage.PlaySystemSoundsplay the Windows sound matching the icon when a dialog opens, like the classicMessageBox. - Animations —
Fade(default),SlideDown,Scale, andNone. - Keyboard —
Ctrl+Ccopies title + message;Enter/Escmap to sensible results. - Right-to-left mirrored layout.
- Toast notifications —
GlassToastwith six anchor positions, auto-stacking, click actions, and an async variant. Toasts are multi-monitor aware: they auto-target the active window’s screen (then the cursor’s, then primary) and stack per-screen; override withGlassToastOptions.Screen. - Cross-target build — .NET Framework 4.8.1 and .NET 8 / 9 / 10 (Windows); AnyCPU, so the library runs in both x86 and x64 processes.
- Release pipeline — pushing a
v*tag builds every target framework, runs the tests, and publishes a GitHub Release with notes and downloadable assets.
Documentation links
Read all Glass.Message guides online · Project overview · All developer projects