Revised from the original filing: renamed FormAppearanceMode -> FormRevealMode (Inherit/Classic/Deferred); removed the DeferLocationChange/DeferWindowPos batching sub-feature, now tracked separately at KlausLoeffelmann#12 pending an anchor-layout-engine integration fix; changed SuspendPaintingScope/SuspendRelocationScope from readonly ref struct to sealed class : IDisposable so the scopes can span an await; made ISupportSuspendPainting/ISupportSuspendRelocation explicit interface implementations with protected ...Core() hooks instead of public virtual methods on Control; added Application.DefaultFormRevealMode / SetDefaultFormRevealMode / IsFormRevealDeferred and the corresponding VB Application Framework wiring. Tracked alongside the rest of the .NET 11 API batch in #14694.
Rationale
WinForms developers commonly need to perform a burst of UI mutations: adding many items, changing layout-affecting properties, or showing a form whose initial background and child controls are not ready at the same time. Today, applications rely on a mix of control-specific BeginUpdate / EndUpdate, SuspendLayout / ResumeLayout, hand-written WM_SETREDRAW, and manual DWM/window tricks.
Those techniques work, but they are inconsistent, easy to misbalance, and often require application code to understand HWND-level details. This proposal adds a small set of composable WinForms APIs for the common cases:
- suspend painting for a control while a synchronous or asynchronous mutation runs;
- suspend relocation/layout work for a control while a mutation runs; and
- defer top-level form reveal to reduce the initial default-background flash, especially noticeable in dark-mode applications.
The goal is not to make the WinForms layout engine faster. The goal is to reduce intermediate visible states and collapse avoidable work during a UI-thread mutation.
API Proposal
namespace System.Windows.Forms;
public interface ISupportSuspendPainting
{
void BeginSuspendPainting();
void EndSuspendPainting();
}
public interface ISupportSuspendRelocation
{
void BeginSuspendRelocation();
void EndSuspendRelocation();
}
namespace System.Windows.Forms;
public sealed class SuspendPaintingScope : IDisposable
{
public SuspendPaintingScope(ISupportSuspendPainting? target);
public void Dispose();
}
public sealed class SuspendRelocationScope : IDisposable
{
public SuspendRelocationScope(ISupportSuspendRelocation? target);
public void Dispose();
}
public static class ControlMutationExtensions
{
public static SuspendPaintingScope SuspendPainting(this ISupportSuspendPainting? target);
public static SuspendRelocationScope SuspendRelocation(this ISupportSuspendRelocation? target);
}
namespace System.Windows.Forms;
public partial class Control : ISupportSuspendPainting, ISupportSuspendRelocation
{
void ISupportSuspendPainting.BeginSuspendPainting() => BeginSuspendPaintingCore();
void ISupportSuspendPainting.EndSuspendPainting() => EndSuspendPaintingCore();
void ISupportSuspendRelocation.BeginSuspendRelocation() => BeginSuspendRelocationCore();
void ISupportSuspendRelocation.EndSuspendRelocation() => EndSuspendRelocationCore();
protected virtual void BeginSuspendPaintingCore();
protected virtual void EndSuspendPaintingCore();
protected virtual void BeginSuspendRelocationCore();
protected virtual void EndSuspendRelocationCore();
}
namespace System.Windows.Forms;
public partial class ListView
{
protected override void BeginSuspendPaintingCore();
protected override void EndSuspendPaintingCore();
}
public partial class ListBox
{
protected override void BeginSuspendPaintingCore();
protected override void EndSuspendPaintingCore();
}
public partial class ComboBox
{
protected override void BeginSuspendPaintingCore();
protected override void EndSuspendPaintingCore();
}
public partial class TreeView
{
protected override void BeginSuspendPaintingCore();
protected override void EndSuspendPaintingCore();
}
public partial class RichTextBox
{
protected override void BeginSuspendPaintingCore();
protected override void EndSuspendPaintingCore();
}
namespace System.Windows.Forms;
public enum FormRevealMode
{
Inherit = -1,
Classic = 0,
Deferred = 1,
}
public partial class Form
{
public virtual FormRevealMode FormRevealMode { get; set; }
}
public partial class Application
{
public static FormRevealMode DefaultFormRevealMode { get; }
public static void SetDefaultFormRevealMode(FormRevealMode mode);
public static bool IsFormRevealDeferred { get; }
}
Namespace Microsoft.VisualBasic.ApplicationServices
Public Class ApplyApplicationDefaultsEventArgs
Inherits EventArgs
Public Property FormRevealMode As FormRevealMode
End Class
End Namespace
API Usage
using (buttonPanel.SuspendPainting())
using (buttonPanel.SuspendRelocation())
{
foreach (Button button in buttonPanel.Controls.OfType<Button>())
{
button.Text = GetUpdatedText(button);
button.Width += 20;
}
}
// Scopes are sealed classes, not ref structs, so they can span an await -
// e.g. suspend painting for the duration of an async reload.
private async void reloadButton_Click(object? sender, EventArgs e)
{
using SuspendPaintingScope scope = listView.SuspendPainting();
listView.Items.Clear();
List<ListViewItem> items = await LoadItemsAsync();
listView.Items.AddRange([.. items]);
}
// No call to SetDefaultFormRevealMode: FormRevealMode.Deferred is used automatically
// for all top-level forms because the application opted into dark mode.
Application.SetColorMode(SystemColorMode.Dark);
Application.Run(new MainForm());
// A splash screen wants an instant, non-deferred reveal even though the rest of the
// application defers by default in dark mode. FormRevealMode is a per-Form ambient
// property, so this does not require touching the process-wide default.
public partial class SplashScreenForm : Form
{
public SplashScreenForm()
{
InitializeComponent();
FormRevealMode = FormRevealMode.Classic;
}
}
// Force deferred reveal process-wide regardless of color mode.
Application.SetDefaultFormRevealMode(FormRevealMode.Deferred);
Application.Run(new MainForm());
Alternative Designs
readonly ref struct scope types
The first draft used readonly ref struct scopes for SuspendPaintingScope/SuspendRelocationScope, reasoning that "forgot the using" becomes a compile error and the scopes can never be stored, boxed, or escape a stack frame. That tradeoff blocks the async-repopulate usage shown above: a ref struct local cannot be alive across an await, because the compiler cannot hoist it into the generated async state machine (CS4007). Since suspending painting/relocation across an awaited fetch is a common, legitimate WinForms pattern - not a hazard comparable to holding a lock across an await - sealed class : IDisposable is proposed instead. The tradeoff is one allocation per scope, acceptable for a UI-mutation-burst control-flow helper.
Public virtual Begin*/End* methods directly on Control
An earlier draft made BeginSuspendPainting/EndSuspendPainting/etc. public virtual methods on Control itself. That makes the manual Begin/End pair the primary IntelliSense surface on every Control-derived type - noise for the overwhelming majority of consumers who should use the scope APIs. Explicit interface implementation, with protected ...Core() hooks for controls that need to customize behavior (ListView, etc.), keeps the designer/codegen contract available while keeping the ergonomic surface clean.
Default interface implementations
ISupportSuspendPainting could provide default implementations and keep Control untouched. That would still require per-instance refcount state. A ConditionalWeakTable or similar side table would be more indirect and more expensive than storing state on Control itself. It would also not help controls that need native BeginUpdate / EndUpdate behavior.
Only expose extension methods, not interfaces
Extension methods on Control would cover today's HWND controls, but would not give future custom controls or HWND-less visual implementations a clear contract to implement. The interfaces keep the contract independent of Control.
Use only SuspendLayout
SuspendLayout prevents layout computation but does not suppress painting. It is necessary but not sufficient for flicker-free mutations.
Why FormRevealMode.Inherit is necessary, not optional
An ambient property in WinForms needs a concrete enum member as its [AmbientValue] sentinel - there is no "absence of value" state the CodeDOM serializer and the ShouldSerialize/Reset designer pattern can key off of. Inherit = -1 (not the enum's zero value, which stays Classic for a conservative default(FormRevealMode)) follows the exact precedent already established by RightToLeft.Inherit and by the sibling VisualStylesMode.Inherit in #14587.
Why Application.SetDefaultFormRevealMode / DefaultFormRevealMode, not some other shape
This directly follows the naming convention already shipped for every other process-wide configuration switch on Application: SetColorMode/ColorMode, SetHighDpiMode/HighDpiMode, and the sibling SetDefaultVisualStylesMode/DefaultVisualStylesMode (#14587). It is not a new pattern requiring independent justification.
Why Inherit does not throw at the Application level (unlike VisualStylesMode.Inherit)
SetDefaultVisualStylesMode(Inherit) throws in #14587, because there is nothing above Application for visual-styles rendering to inherit from. FormRevealMode is different: Application-level Inherit has a well-defined resolution - Deferred when Application.IsDarkModeEnabled, Classic otherwise - because dark-mode state is itself a further, independent signal to fall back to. Application.IsFormRevealDeferred is the fully-resolved query (mirrors Application.IsDarkModeEnabled); Application.DefaultFormRevealMode may return the raw, unresolved Inherit (mirrors Application.ColorMode returning the unresolved System).
Why SetDefaultFormRevealMode is freely reassignable, unlike the write-once SetDefaultVisualStylesMode
FormRevealMode's Inherit resolution is derived from Application.ColorMode/IsDarkModeEnabled, which is deliberately kept freely reassignable to leave room for a future live system-theme-change event. Locking SetDefaultFormRevealMode after first use would make a form created after such a live change silently use a stale reveal behavior. VisualStylesMode has no equivalent live-changing input - it is a static, one-time rendering-version choice - so its write-once semantics remain correct there and are not inconsistent with this proposal.
Dark-mode-conditional default, not an unconditional one
An earlier draft made Deferred the unconditional runtime default whenever SetDefaultFormRevealMode is never called - a compatibility-relevant, opt-out behavior change for every existing application. Tying the default to Application.IsDarkModeEnabled instead means an application that never touches SystemColorMode/dark mode sees no behavior change, while the scenario the feature exists for (dark-mode startup flash) is fixed by default - piggybacking the fix on the same opt-in gesture (dark mode) that causes the problem in the first place.
Risks
- Ref-count imbalance remains possible when callers invoke
Begin* / End* directly through the interfaces. The scope APIs make the ergonomic path safer, but the interface methods must still tolerate nesting and unbalanced calls.
WM_SETREDRAW suppresses painting for HWND-backed controls but does not make arbitrary custom drawing atomic. Controls can still invalidate or repaint after the scope exits.
- Existing controls with public
BeginUpdate / EndUpdate methods must keep source and binary compatibility. Their suspension overrides should route through existing update behavior rather than changing those public APIs.
FormRevealMode.Deferred depends on DWM support. On unsupported systems or unsupported window kinds, it must be inert and preserve classic behavior.
- Deferred form reveal can reduce the default-background flash, but a deep tree of late-painting child controls can still update visibly after reveal.
- Uncloak timing is the hardest part of the form behavior. Uncloaking too early preserves the flash; uncloaking too late makes startup feel slower.
FormRevealMode only ever applies to top-level, non-MDI-child forms - DWMWA_CLOAK composes per top-level HWND, so a child (WS_CHILD) window has no independent DWM surface to cloak. This is a flat, Form-only property with no Control-parent-chain resolution (unlike VisualStylesMode, which genuinely needs one because rendering style cascades through nested controls).
- Special cases such as MDI children, owned/tool windows, splash screens, layered/opacity windows, per-monitor DPI changes during creation, and handle recreation need conservative handling.
- All APIs are UI-thread-oriented and do not make WinForms controls thread-safe.
Will this feature affect UI controls?
Yes. The painting and relocation APIs are implemented by Control and selected existing controls. They affect rendering and layout timing but do not change designer serialization, accessibility names/roles, localization, or existing layout results.
FormRevealMode.Deferred affects top-level form startup presentation. It should be documented as a visual startup behavior, not an accessibility feature. It should preserve accessibility/UIA object creation and not hide forms from accessibility clients longer than necessary. It has no localization requirements.
The new APIs should be available to designer-generated code because InitializeComponent lives in the user's assembly. FormRevealMode additionally needs [AmbientValue(FormRevealMode.Inherit)] and CodeDOM serialization support so an inherited-form designer can explicitly reset a derived form back to Inherit even though there is no multi-level parent chain to walk.
Status Checklist
Rationale
WinForms developers commonly need to perform a burst of UI mutations: adding many items, changing layout-affecting properties, or showing a form whose initial background and child controls are not ready at the same time. Today, applications rely on a mix of control-specific
BeginUpdate/EndUpdate,SuspendLayout/ResumeLayout, hand-writtenWM_SETREDRAW, and manual DWM/window tricks.Those techniques work, but they are inconsistent, easy to misbalance, and often require application code to understand HWND-level details. This proposal adds a small set of composable WinForms APIs for the common cases:
The goal is not to make the WinForms layout engine faster. The goal is to reduce intermediate visible states and collapse avoidable work during a UI-thread mutation.
API Proposal
API Usage
Alternative Designs
readonly ref structscope typesThe first draft used
readonly ref structscopes forSuspendPaintingScope/SuspendRelocationScope, reasoning that "forgot theusing" becomes a compile error and the scopes can never be stored, boxed, or escape a stack frame. That tradeoff blocks the async-repopulate usage shown above: aref structlocal cannot be alive across anawait, because the compiler cannot hoist it into the generated async state machine (CS4007). Since suspending painting/relocation across an awaited fetch is a common, legitimate WinForms pattern - not a hazard comparable to holding a lock across anawait-sealed class : IDisposableis proposed instead. The tradeoff is one allocation per scope, acceptable for a UI-mutation-burst control-flow helper.Public
virtualBegin*/End*methods directly onControlAn earlier draft made
BeginSuspendPainting/EndSuspendPainting/etc. public virtual methods onControlitself. That makes the manual Begin/End pair the primary IntelliSense surface on everyControl-derived type - noise for the overwhelming majority of consumers who should use the scope APIs. Explicit interface implementation, with protected...Core()hooks for controls that need to customize behavior (ListView, etc.), keeps the designer/codegen contract available while keeping the ergonomic surface clean.Default interface implementations
ISupportSuspendPaintingcould provide default implementations and keepControluntouched. That would still require per-instance refcount state. AConditionalWeakTableor similar side table would be more indirect and more expensive than storing state onControlitself. It would also not help controls that need nativeBeginUpdate/EndUpdatebehavior.Only expose extension methods, not interfaces
Extension methods on
Controlwould cover today's HWND controls, but would not give future custom controls or HWND-less visual implementations a clear contract to implement. The interfaces keep the contract independent ofControl.Use only
SuspendLayoutSuspendLayoutprevents layout computation but does not suppress painting. It is necessary but not sufficient for flicker-free mutations.Why
FormRevealMode.Inheritis necessary, not optionalAn ambient property in WinForms needs a concrete enum member as its
[AmbientValue]sentinel - there is no "absence of value" state the CodeDOM serializer and theShouldSerialize/Resetdesigner pattern can key off of.Inherit = -1(not the enum's zero value, which staysClassicfor a conservativedefault(FormRevealMode)) follows the exact precedent already established byRightToLeft.Inheritand by the siblingVisualStylesMode.Inheritin #14587.Why
Application.SetDefaultFormRevealMode/DefaultFormRevealMode, not some other shapeThis directly follows the naming convention already shipped for every other process-wide configuration switch on
Application:SetColorMode/ColorMode,SetHighDpiMode/HighDpiMode, and the siblingSetDefaultVisualStylesMode/DefaultVisualStylesMode(#14587). It is not a new pattern requiring independent justification.Why
Inheritdoes not throw at theApplicationlevel (unlikeVisualStylesMode.Inherit)SetDefaultVisualStylesMode(Inherit)throws in #14587, because there is nothing aboveApplicationfor visual-styles rendering to inherit from.FormRevealModeis different:Application-levelInherithas a well-defined resolution -DeferredwhenApplication.IsDarkModeEnabled,Classicotherwise - because dark-mode state is itself a further, independent signal to fall back to.Application.IsFormRevealDeferredis the fully-resolved query (mirrorsApplication.IsDarkModeEnabled);Application.DefaultFormRevealModemay return the raw, unresolvedInherit(mirrorsApplication.ColorModereturning the unresolvedSystem).Why
SetDefaultFormRevealModeis freely reassignable, unlike the write-onceSetDefaultVisualStylesModeFormRevealMode'sInheritresolution is derived fromApplication.ColorMode/IsDarkModeEnabled, which is deliberately kept freely reassignable to leave room for a future live system-theme-change event. LockingSetDefaultFormRevealModeafter first use would make a form created after such a live change silently use a stale reveal behavior.VisualStylesModehas no equivalent live-changing input - it is a static, one-time rendering-version choice - so its write-once semantics remain correct there and are not inconsistent with this proposal.Dark-mode-conditional default, not an unconditional one
An earlier draft made
Deferredthe unconditional runtime default wheneverSetDefaultFormRevealModeis never called - a compatibility-relevant, opt-out behavior change for every existing application. Tying the default toApplication.IsDarkModeEnabledinstead means an application that never touchesSystemColorMode/dark mode sees no behavior change, while the scenario the feature exists for (dark-mode startup flash) is fixed by default - piggybacking the fix on the same opt-in gesture (dark mode) that causes the problem in the first place.Risks
Begin*/End*directly through the interfaces. The scope APIs make the ergonomic path safer, but the interface methods must still tolerate nesting and unbalanced calls.WM_SETREDRAWsuppresses painting for HWND-backed controls but does not make arbitrary custom drawing atomic. Controls can still invalidate or repaint after the scope exits.BeginUpdate/EndUpdatemethods must keep source and binary compatibility. Their suspension overrides should route through existing update behavior rather than changing those public APIs.FormRevealMode.Deferreddepends on DWM support. On unsupported systems or unsupported window kinds, it must be inert and preserve classic behavior.FormRevealModeonly ever applies to top-level, non-MDI-child forms -DWMWA_CLOAKcomposes per top-level HWND, so a child (WS_CHILD) window has no independent DWM surface to cloak. This is a flat,Form-only property with noControl-parent-chain resolution (unlikeVisualStylesMode, which genuinely needs one because rendering style cascades through nested controls).Will this feature affect UI controls?
Yes. The painting and relocation APIs are implemented by
Controland selected existing controls. They affect rendering and layout timing but do not change designer serialization, accessibility names/roles, localization, or existing layout results.FormRevealMode.Deferredaffects top-level form startup presentation. It should be documented as a visual startup behavior, not an accessibility feature. It should preserve accessibility/UIA object creation and not hide forms from accessibility clients longer than necessary. It has no localization requirements.The new APIs should be available to designer-generated code because
InitializeComponentlives in the user's assembly.FormRevealModeadditionally needs[AmbientValue(FormRevealMode.Inherit)]and CodeDOM serialization support so an inherited-form designer can explicitly reset a derived form back toInheriteven though there is no multi-level parent chain to walk.Status Checklist
api-suggestionlabelapi-ready-for-reviewblockinglabel to expedite the review appointmentapi-approved