Build cross-platform .NET applications with Uno Platform targeting WebAssembly, iOS, Android, macOS, Linux, and Windows from a single XAML/C# codebase.
WinForms Expert
Create, modify, debug, or review Windows Forms applications only when the request contains a concrete Windows Forms marker. USE FOR: WinForms, Windows Forms, System.Windows.Forms, WinForms Form or UserControl designer files (*.Designer.cs or *.Designer.vb), Visual Studio WinForms Designer, TableLayoutPanel, BindingSource, DataGridView, Control.InvokeAsync, component-tray ownership, or custom control serialization. DO NOT USE FOR: WPF or WPF XAML, including Window.InputBindings, KeyBinding, commands, or DataContext; .NET MAUI; Avalonia; or any request where none of the listed Windows Forms markers is present.
Workflow
1. Establish the project constraints
- Inspect the solution/project, target framework, language, nullable setting, package management,
application startup, and existing build commands.
- Identify whether the app targets modern .NET or .NET Framework. Preserve its current family
unless migration is requested.
- Read the complete partial-file set and
.resxfor every affected Form/UserControl before
editing. Check base classes and nearby controls for repository conventions.
- If the defect is designer-specific, record the exact load/serialization error and determine
whether the problem occurs before making broad changes.
2. Plan the serialization boundary
- Classify each change as serialized UI state or runtime behavior.
- Put only designer-representable state in
InitializeComponent. - Put conditional setup, dynamic content, data loading, conversions, validation, and service
interaction in the main partial class or another runtime type.
- Prefer the smallest designer diff. Do not reorder or reformat the whole generated file.
- If editing generated code is avoidable, use the Designer or a runtime initialization method
instead. If direct editing is necessary, follow the existing generated shape exactly.
3. Implement the UI structure
For new or substantially revised layouts:
- Prefer
TableLayoutPanel,FlowLayoutPanel,SplitContainer, and nested UserControls over
brittle absolute positioning.
- Divide complex screens into small layout regions. Avoid a single oversized grid.
- Prefer
AutoSizefor caption/single-line rows and columns,Percentfor expandable content,
and Absolute only for genuinely fixed-size content.
- Preserve the existing form's
AutoScaleMode. For new forms, choose scaling deliberately based
on the project conventions rather than copying coordinates from another DPI.
- Ensure an autosized child container participates in the full sizing chain; a fixed-height
Panel or GroupBox inside an autosized row can still clip its contents.
- Set meaningful minimum sizes where a resizable or localized dialog could otherwise become
unusable.
- Keep localized UI text in the project's resources and allow labels/buttons to grow.
For dialogs:
- Set
AcceptButton,CancelButton, and appropriateDialogResultvalues. - Validate the form as a whole when submitting. Do not trap users in a field by canceling every
focus change.
- Dispose modal forms according to the project's ownership pattern.
4. Add behavior outside generated code
- Follow the project's language and style conventions; modern syntax belongs only in regular code.
- Keep UI-thread affinity explicit. Marshal control access from background work.
- A normal WinForms
async voidevent handler resumes on its captured UI synchronization context
after await unless that context was deliberately bypassed. Direct control access there is valid. Use and await InvokeAsync when code can continue off-context, or when the task/repository contract explicitly requires a marshaled operation; do not rely on an unawaited post.
- For modern .NET versions that support it, select the
Control.InvokeAsyncoverload matching
whether the delegate is synchronous/asynchronous and whether it returns a value. Await the returned operation; do not create fire-and-forget UI work.
async voidis acceptable for WinForms event handlers and overrides that must returnvoid,
but catch exceptions around awaited work, handle cancellation intentionally, and surface unexpected failures through the application's established error path.
- Do not use application-level exception hooks as a substitute for handling expected failures.
Application.ThreadException covers UI-thread exceptions; AppDomain.UnhandledException is primarily a last-chance logging path and does not make continued execution safe.
- Preserve property allocation semantics. Do not change a cached property into
=> new ... or vice versa without confirming lifetime and disposal behavior.
For application-wide startup:
- Preserve the current startup model. C# projects may configure application defaults in
Program.cs; VB projects normally use the VB Application Framework and ApplicationEvents.vb, not a newly invented Program.vb.
- Use the APIs available to the actual TFM. Apply color mode or DPI defaults only when requested
or consistent with the app's existing policy.
- Prefer
SystemAwarewhen maintaining the normal .NET WinForms DPI behavior. Use
PerMonitorV2 only when the application is designed and tested for per-monitor scaling.
5. Implement binding and serialization deliberately
For classic binding:
- Use
INotifyPropertyChangedfor mutable bound objects andBindingList<T>or an established
adapter for list change notifications.
- Use
BindingSourceas the designer/runtime mediator when that matches the existing design. - Put conversion logic in
Binding.FormatandBinding.Parsehandlers rather than embedding
executable logic in designer code.
- Treat one-way-to-source requests carefully: classic WinForms
Bindingdoes not provide a
direct WPF-style mode. Document and test any workaround instead of implying native support.
For modern WinForms MVVM APIs, first confirm the TFM contains the requested API:
- Use
Control.DataContextfor ambient view-model context when available. - Use
ButtonBase.Command,ToolStripItem.Command, andCommandParameterwhen available. - Keep view models in a UI-independent project when the solution already uses that separation or
the requested change benefits from it; do not add a new architecture for a small fix.
- If design-time object data sources are required, preserve or add the project's established
.datasource and BindingSource pattern and verify it in the Designer.
For custom Component/Control properties, choose one intentional CodeDOM serialization policy:
| Policy | Mechanism | Use | |---|---|---| | Serialize only when non-default | [DefaultValue(...)] or matching ShouldSerializeX/ResetX | Stable values with a meaningful default | | Never serialize | [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] | Runtime-only, calculated, or unsupported state | | Serialize content | DesignerSerializationVisibility.Content plus designer-compatible mutable content | Owned nested objects/collections intentionally edited in the property grid |
Import System.ComponentModel (or fully qualify its attributes). Keep default metadata and runtime initialization directly aligned: for [DefaultValue(typeof(Color), "Yellow")], initialize the property or its backing field directly with Color.Yellow; do not hide the actual default behind a second constant or factory. Do not combine contradictory policies. Test property-grid editing and save/reload behavior for custom serialization changes.
6. Check usability and accessibility
- Set logical
TabIndexorder and verify keyboard-only traversal. - Add unambiguous mnemonics where the product uses them.
- Set meaningful accessible names/descriptions for actionable controls when labels or context do
not already provide them.
- Verify default focus, accept/cancel behavior, resizing, clipping, and localized text growth.
- Check at the DPI/theme combinations relevant to the change. System colors adapt to theme;
hard-coded colors and owner-drawn controls do not.
- For owner drawing,
DataGridView, icons, and custom painting, verify contrast in every supported
theme rather than assuming dark-mode adaptation.
7. Validate in increasing-cost order
- Re-read the designer diff and confirm every statement is serialization-safe and every referenced
field/handler exists in the correct partial class.
- Discover and run the repository's narrowest task-specific validation script or test when one is
present. A generic build does not replace a designer/layout/binding validator.
- Run the repository's smallest applicable restore/build/analyzer command. Use Visual Studio
MSBuild when the project type or .NET Framework dependencies require it.
- Run any remaining relevant automated tests.
- Launch the affected UI when possible and exercise creation, load, resize, keyboard navigation,
binding, async/error paths, and close/disposal behavior.
- When Visual Studio with a compatible WinForms Designer is available:
1. Open every changed Form/UserControl in the Designer. 2. Confirm the design surface, toolbox integration, property grid, component tray, and inherited controls load without errors. 3. Make a harmless reversible property change, save, close, reopen, and confirm the designer can serialize and reload the component. 4. Revert only the harmless validation change, not the requested implementation. 5. Build again after the serialization round trip.
Related skills
Build and modernize WPF applications on .NET with correct XAML, data binding, commands, threading, styling, and Windows desktop migration decisions.
Build, maintain, or modernize Windows Forms applications with practical guidance on designer-driven UI, event handling, data binding, MVP separation, and migration to modern .NET.
Building AI agents on .NET?
Managed Code builds production AI agents in C# and .NET.