Technical Documentation: dotnet/winforms
βΉοΈ Provenance: Multi-Tier Ingestion Β· CodeWiki Reference
Overview (`README.md`)
Windows Forms
Windows Forms (WinForms) is a UI framework for building Windows desktop applications. It is a .NET wrapper over Windows user interface libraries, such as User32 and GDI+. It also offers controls and other functionality that is unique to Windows Forms.
Windows Forms also provides one of the most productive ways to create desktop applications based on the visual designer provided in Visual Studio. It enables drag-and-drop of visual controls and other similar functionality that make it easy to build desktop applications.
Windows Forms Out-Of-Process Designer
For information about the WinForms Designer supporting the .NET runtime and the changes between the .NET Framework Designer (supporting .NET Framework up to version 4.8.1) vs. the .NET Designer (supporting .NET 6, 7, 8, 9+), please see Windows Forms Designer Documentation.
Important: As a Third Party Control Vendor, when you migrate controls from .NET Framework to .NET, your control libraries at runtime are expected to work as before in the context of the respective new TFM (special modernization or security changes in the TFM kept aside, but those are rare breaking changes). Depending on the richness of your control's design-time support, the migration of control designers from .NET Framework to .NET might need to take a series of areas with breaking changes into account. The provided link points out additional resources which help in that migration process.
Relationship to .NET Framework
This codebase is a fork of the Windows Forms code in the .NET Framework 4.8.
We started the migration process by targeting .NET Core 3.0, when we've strived to bring the two runtimes to a parity. Since then, we've done a number of changes, including breaking changes, which diverged the two. For more information about breaking changes, see the Porting guide.
The bar for innovation and new features
WinForms is a technology which was originally introduced as a part of .NET Framework 1.0 on February 13th, 2002. It's primary focus was and is to be a Rapid Application Tool for Windows based Apps, and that principal sentiment has not changed over the years. WinForms at the time addressed developer's requests for
- A framework for stable, monolithic Line of Business Apps, even with extremely complicated and complex domain-specific workflows
- The ability to easily provide rich user interfaces
- A safe and - over the first 3 versions of .NET Framework - increasingly performant way to communicate across process boundaries via various Windows Communication Services, or access on-site databases via ADO.NET providers.
- A very easy to use, visual what-you-see-is-what-you-get designer, which requires little ramp-up time, and was primarily focused to support 96 DPI resolution-based, pixel-coordinated drag & drop design strategies.
- A flexible, .NET reflection-based Designer extensibility model, utilizing the .NET Component Model.
- Visual Controls and Components, which provide their own design-time functionality through Control Designers
Over time, and with a growing need to address working scenarios with multi-monitor, high resolution monitors, significantly more powerful hardware, and much more, WinForms has continued to be modernized.
And then there is the evolution of Windows: When new versions of Windows introduce new or change existing APIs or technologies - WinForms needs to keep up and adjust their APIs accordingly.
And that is still the primary motivation for once to modernize and innovate, but also the bar to reach for potential innovation areas we either need or want to consider:
- Areas, where for example for security concerns, the Windows team needed to take an depending area out-of-proc, and we see and extreme performance hit in WinForms Apps running under a new Service Pack or a new Windows Version
- New features to comply with updated industry standards for accessibility.
- HighDPI and per Monitor V2-Scenarios.
- Picking up changed or extended Win32 Control functionality, to keep controls in WinForms working the way the Windows team wants them to be used.
- Addressing Performance and Security issues
- Introducing ways to support asynchronous calls interatively, to enable apps to pick up migration paths via Windows APIs projection/Windows Desktop Bridge, enable scenarios for async WebAPI, SignalR, Azure Function, etc. calls, so WinForms backends can modernized and even migrated to the cloud.
What would not make the bar:
- New functionality which modern Desktop UIs like WPF or WinUI clearly have already
- Functionality, which would "stretch" a Windows Desktop App to be a mobile, Multi-Media or IoT app.
- Domain-specific custom controls, which are already provided by the vast variety of third party control vendors
A note about Visual Basic: Visual Basic .NET developers make up about 20% of WinForms developers. We welcome changes that are specific to VB if they address a bug in a customer-facing scenario. Issues and PRs should describe the customer-facing scenario and, if possible, include images showing the problem before and after the proposed changes. Due to limited bandwidth, we cannot prioritize VB-specific changes that are solely for correctness or code cleanliness. However, VB remains important to us, and we aim to fix any critical issues that arise.
Please note
:warning: This repository contains only implementations for Windows Forms for .NET platform.
It does not contain either:
- The .NET Framework variant of Windows Forms. Issues with .NET Framework, including Windows Forms, should be filed on the Developer Community or Product Support websites. They should not be filed on this repository.
- The Windows Forms Designer implementations. Issues with the Designer can be filed via VS Feedback tool (top right-hand side icon in Visual Studio) or be filed in this repo using the Windows Forms out-of-process designer issue template.
How can I contribute?
We welcome contributions! Many people all over the world have helped make this project better.
- Contributing explains what kinds of changes we welcome
- Developer Guide explains how to build and test
- Get Up and Running with Windows Forms .NET explains how to get started building Windows Forms applications.
How to Engage, Contribute, and Provide Feedback
Some of the best ways to contribute are to try things out, file bugs, join in design conversations, and fix issues.
- The contributing guidelines and the more general .NET contributing guide define contributing rules.
- The Developer Guide defines the setup and workflow for working on this repository.
- If you have a question or have found a bug, file an issue.
- Use daily builds if you want to contribute and stay up to date with the team.
Reporting security issues
Security issues and bugs should be reported privately via email to the Microsoft Security Response Center (MSRC) [email protected]. You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Further information, including the MSRC PGP key, can be found in the Security TechCenter. Also see info about related Microsoft .NET Core and ASP.NET Core Bug Bounty Program.
Code of Conduct
This project uses the .NET Foundation Code of Conduct to define expected conduct in our community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at [email protected].
License
.NET (including the Windows Forms repository) is licensed under the MIT license.
.NET Foundation
.NET Windows Forms is a .NET Foundation project.
See the .NET home repositoryΒ to find other .NET-related projects.
Overview (`docs/designer/readme.md`)
Windows Forms Out-Of-Process Designer
The Windows Forms out-of-process designer is currently available as a preview Visual Studio feature in Visual Studio 2019 or later. It can be enabled in Tools -> Options -> Environment -> Preview Features dialog by checking the Use the preview Windows Forms out-of-process designer for .NET apps checkbox.
Releases of .NET Windows Forms Designer are tied to Visual Studio Preview releases, so update to the latest Visual Studio Preview version to get the newest designer features.
:heavy_exclamation_mark: Visual Studio 2022 v17.12 Preview 3 announces a change in the OOP designer support. Upgrade Your Windows Forms .NET Projects to the Latest .NET Version for Enhanced Security
For the news on the designer please refer to the series of blogs:
- Modernization of code-behind generation
- Custom Controls for WinForms' Out-Of-Process Designer
- Visual Basic WinForms Apps in .NET 5 and Visual Studio 16.8
- Databinding with the OOP Windows Forms Designer
- State of the Windows Forms Designer for .NET Applications as of 01/13/2022
WinForms out of process designer SDK
For more information about creating custom control libraries, including sample code and templates refer to the WinForms Designer Extensibility Repo. Details about how to use the WinForms Designer Extensibility SDK and structure a control library nuget package can be found there as well. Download the Extensibility SDK from NuGet.
Reporting bugs in the WinForms Designer
To make bug reports visible to the large number of users and let them comment and vote on bugs, please use the Visual Studio bug reporting feature. This tooling provides secure dump and screenshot upload options.
Table of Content
- How to use .NET Framework designer for .NET applications
- How to troubleshoot the out-of-process designer
Overview (`eng/ApiCompatibility/README.md`)
Mark Shipped Tool
========
This tool should be run after every supported release that has API changes. It will
merge the collection of PublicApi.Shipped.txt files with the PublicApi.Unshipped.txt
versions. This will take into account *REMOVED* elements when updating the files.
Usage:
mark-shipped.cmdOverview (`eng/common/README.md`)
Don't touch this folder
uuuuuuuuuuuuuuuuuuuu
u" uuuuuuuuuuuuuuuuuu "u
u" u$$$$$$$$$$$$$$$$$$$$u "u
u" u$$$$$$$$$$$$$$$$$$$$$$$$u "u
u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u
u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u
u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u
$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $
$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $
$ $$$" ... "$... ...$" ... "$$$ ... "$$$ $
$ $$$u `"$$$$$$$ $$$ $$$$$ $$ $$$ $$$ $
$ $$$$$$uu "$$$$ $$$ $$$$$ $$ """ u$$$ $
$ $$$""$$$ $$$$ $$$u "$$$" u$$ $$$$$$$$ $
$ $$$$....,$$$$$..$$$$$....,$$$$..$$$$$$$$ $
$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $
"u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u"
"u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u"
"u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u"
"u "$$$$$$$$$$$$$$$$$$$$$$$$" u"
"u "$$$$$$$$$$$$$$$$$$$$" u"
"u """""""""""""""""" u"
""""""""""""""""""""!!! Changes made in this directory are subject to being overwritten by automation !!!
The files in this directory are shared by all Arcade repos and managed by automation. If you need to make changes to these files, open an issue or submit a pull request to https://github.com/dotnet/arcade first.
Overview (`pkg/Microsoft.Dotnet.WinForms.ProjectTemplates/readme.md`)
Windows Forms Projects Templates
We provide the following templates:
- Windows Forms app (C#)
- Windows Forms app (VB)
- Windows Forms class library (C#)
- Windows Forms class library (VB)
- Windows Forms control library (C#)
- Windows Forms control library (VB)
Item templates are located in the VSProject repo
Testing Templates
Before submitting a change to any of the templates please make sure to test them locally.
Install the required template with
dotnettool (e.g.WinFormsApplication-CSharp).
In the command prompt of your choice run the following command:text> dotnet new -i <path to the repo>\winforms\pkg\Microsoft.Dotnet.WinForms.ProjectTemplates\content\WinFormsApplication-CSharpTo confirm that the template is correctly installed run the following command and look for your template:
text> dotnet new -uCreate an app from your template:
text> dotnet new winforms -n testapp1Verify the app behaves as expected.
If necessary, tweak the template and create new apps.Once you are happy with the template, you can uninstall it to revert to the original by running the command given in
dotnet new -uoutput; e.g.text> dotnet new -u <path to the repo>\winforms\pkg\Microsoft.Dotnet.WinForms.ProjectTemplates\content\WinFormsApplication-CSharp
Overview (`pkg/Microsoft.Private.Windows.Core/readme.md`)
Microsoft Private Windows Core Package
It is an internal only package. Public consumption is not supported.
Overview (`pkg/Microsoft.Private.Winforms/README.md`)
Overview
This is a transport package consumed by WPF and WindowsDesktop. Some packaging configurations are defined in this project, and others are defined in <repo root>\eng\packageContent.targets.
`sdk\dotnet-windowsdesktop` folder
This folder contains props and targets used to ingest our assemblies into the Windows Desktop SDK, bundling the correct set of assemblies into either the Microsoft.WindowsDesktop.App.Ref pack or Microsoft.WindowsDesktop.App.Runtime pack.
System.Windows.Forms.FileClassification.propscontains a manifest for the "WindowsForms" SDK¹, i.e. a list of our assemblies that form it.
The file is imported by Microsoft.WindowsDesktop.App.Ref project.
The manifest will need to be rebuilt if there are changes in the solution with respect to the SDK assemblies, e.g. a new assembly is added or an existing assembly is removed.
How to update System.Windows.Forms.FileClassification.props
The existing System.Windows.Forms.FileClassification.props is be compared against the list of assemblies in Microsoft.Private.Winforms.[version].nuspec² generated as part of the pack command.
If the content of these files differ - the build will fail.
:warning: The process is purposefully made manual to ensure changes in the manifest are made consciously.
To update the manifest run the following command and check in the updated files manifest:
.\build.cmd -pack /p:_GenerateManifest=trueTo debug the script run the following command:
dotnet build .\pkg\Microsoft.Private.Winforms\Microsoft.Private.Winforms.csproj /t:UpdateTransportPackage /p:_GenerateManifest=true /v:m /bl /p:CommonLibrary_NativeInstallDir=$env:UserProfile\.netcoreeng\native\`sdk\dotnet-wpf` folder
This folder contains props and targets that are part of Windows Desktop SDK (which is hosted and assembled in dotnet/wpf).
These files are referenced the Microsoft.NET.Sdk.WindowsDesktop project's props and targets located here. When this project is being built, it copies the files from our transport NuGet package to a Microsoft.NET.Sdk.WindowsDesktop bundle.
Microsoft.NET.Sdk.WindowsDesktop.WindowsForms.propscontains various Windows Forms specific configurations, such as our defaultusingimports.Microsoft.NET.Sdk.WindowsDesktop.WindowsForms.targetscontains various Windows Forms specific targets.System.Windows.Forms.Analyzers.propscontains a list of properties required by our source generators.
1. Except for Accessibility.dll which is noted explicitly.
2. Generated in .\winforms\artifacts\obj\Microsoft.Private.Winforms\[Debug|Release].
Overview (`src/Accessibility/readme.md`)
Accessibility
This directory contains source files used to build Accessibility.dll, the managed wrapper for COM accessibility interface.
See https://docs.microsoft.com/dotnet/api/accessibility
Source in this directory is generated by a tool and should not be modified directly.
To produce the IL source the following process is used:
- Use https://aka.ms/tlbimp to generate an assembly for oleacc.dll
tlbimp c:\windows\system32\oleacc.dll /out:Accessibility.dll- Use ildasm to disassemble that assembly.
ildasm Accessibility.dll /out=Accessibility.ilReplace the desktop assembly references with their corresponding .NETCore assemblies (since .NETCore doesn't provide a version of tlbimp.exe yet)
- replace
[mscorlib]System.Runtime.InteropServiceswith[INTEROP_ASSEMBLY]System.Runtime.InteropServices - replace
[mscorlib]with[CORE_ASSEMBLY] - review the diff
- replace
Add assembly level attributes to the IL
Overview (`src/System.Drawing.Common/README.md`)
System.Drawing.Common
This assembly provides access to GDI+ basic graphics functionality via types such as Bitmap and Font.
Note that System.Drawing.Common is only supported on Windows: https://learn.microsoft.com/dotnet/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only.
Documentation can be found at https://learn.microsoft.com/dotnet/api/system.drawing.
Contribution Bar
- We consider new features, new APIs and performance changes
- We consider PRs that target this library for new source code analyzers
- We don't accept refactoring changes due to new language features
See the Help Wanted issues.
Deployment
System.Drawing.Common is provided as a NuGet package and part of the Microsoft.WindowsDesktop.App shared framework.
Overview (`src/System.Windows.Forms.PrivateSourceGenerators/README.md`)
This project contains source generators that are used during the build of the Windows Forms SDK.
For example, to emit code that validates enum arguments.
Overview (`src/System.Windows.Forms.Analyzers/tests/README.md`)
Extending WinForms analyzers
Analyzer tests use Roslyn's native C# and Visual Basic test types through the thin, language-neutralAnalyzerTestCase and AnalyzerTestFactory helpers in UnitTests. Keep analyzer behavior in the
language project and reusable descriptors, resources, and semantic facts in the shared project.
Adding a diagnostic
- Reserve a WFO ID in
src\System.Windows.Forms.Analyzers\src\System\Windows\Forms\Analyzers\Diagnostics\DiagnosticIDs.cs. - Add the localized title and message to
src\System.Windows.Forms.Analyzers\src\Resources\SR.resx,
then build so the XLF files are synchronized. - Add a descriptor to the shared or language-specific descriptor class.
- Implement the analyzer under the applicable C# or Visual Basic
src\Analyzerstree. Put common
semantic detection in the shared analyzer project; keep syntax APIs language-specific. - Track the rule in
AnalyzerReleases.Unshipped.mdand document it in the applicabledocs\analyzers\WinFormsCSharpAnalyzers.Help.mdordocs\analyzers\WinFormsVisualBasicAnalyzers.Help.mdfile. - Add exact diagnostic ID, argument, path, and span assertions to the applicable test project.
Document why a rule is language-specific when no equivalent syntax exists.
Do not add a code fix unless the transformation is safe and deterministic.
Writing tests
Use inline AnalyzerTestSource values for focused behavior. Use named fixture files under aTestData directory when a realistic source set would obscure the assertion. For Designer rules,
always provide both the user file and its .Designer.cs or .Designer.vb counterpart, and derive the
partial type from Form, UserControl, another Control type, orSystem.ComponentModel.Component. Include negative cases with same-named types in unrelated
namespaces. For VB, use realistic WithEvents/Handles fixtures and distinguish member events fromGenerateMember=False local-variable wiring. Never modernize generated Dispose fixtures merely
to match restrictions that apply only to InitializeComponent.
Create the language-native Roslyn test through AnalyzerTestFactory; do not add another test base
class or block asynchronous calls:
AnalyzerTestCase testCase = new(
ReferenceAssemblies.Net.Net90Windows,
new AnalyzerTestSource("Form1.cs", mainSource),
new AnalyzerTestSource("Form1.Designer.cs", designerSource));
await AnalyzerTestFactory.CreateCSharpAnalyzerTest<MyAnalyzer>(testCase)
.RunAsync(TestContext.Current.CancellationToken);Use AnalyzerConfigFiles for .editorconfig or .globalconfig cases, AdditionalFiles for
non-source analyzer inputs, and AdditionalReferences for metadata paths. Tests that need the latest
WinForms API surface should use CurrentReferences.NetCoreAppReferences and addCurrentReferences.WinFormsRefPath, following the existing WFO2001 tests.
The helper reads the configured SDK's runtime metadata and requires its exact reference pack; an
SDK version is not a runtime-pack version, and another installed preview is not a safe substitute.
Designer diagnostics must include a concise supported repair in the emitted message, not only in
the help page. Test representative rendered messages under an invariant culture as well as IDs,
arguments, paths, spans, suppression, and absence of analyzer exceptions. Preserve separate IDs for
independently repairable constructs.
Syntax coverage boundary
The existing expression rules remain enforced because their constructs cannot round-trip through
the designer's CodeDOM contract. WFO3000 also covers structural using/Using and await/Await,
and the C# variants include expression-bodied initialization and deconstructing foreach. WFO3007
includes coalescing assignment.
Do not infer support for all C# or VB syntax from this list. Standalone patterns, tuples/deconstruction,
implicit object creation, object/collection initializers, queries, VB With, XML/anonymous objects,
and unstructured error handling are not additional rules in this change. Some require source-parser
lowering evidence outside this repository before an accurate restriction and mitigation can be
defined. Unlisted does not mean supported. The current Roslyn package is 4.12; syntax from newer
compiler versions also needs explicit compatibility work rather than guessed syntax matching.
Targeted validation
Run these from the repository root:
dotnet test src\System.Windows.Forms.Analyzers\tests\UnitTests\System.Windows.Forms.Analyzers.Tests.csproj -nologo
dotnet test src\System.Windows.Forms.Analyzers.CSharp\tests\UnitTests\System.Windows.Forms.Analyzers.CSharp.Tests.csproj -nologo
dotnet test src\System.Windows.Forms.Analyzers.VisualBasic\tests\UnitTests\System.Windows.Forms.Analyzers.VisualBasic.Tests\System.Windows.Forms.Analyzers.VisualBasic.Tests.vbproj -nologoBefore reporting the change as build-clean, run the repository-supported .\build.cmd; a plain
solution build does not apply all CI analyzers and warning settings.
Overview (`src/test/integration/DesignSurface/README.md`)
DesignSurface (DemoConsole)
Debugging Native Code in This Project
To fully enable debugging in this project (including native code and symbol resolution), perform the following two steps:
1. Enable Native Code Debugging
This allows Visual Studio to step into native code
Option A β Using Visual Studio GUI:
- Right-click the project in Solution Explorer and select Properties.
- Go to the Debug then Open debug launch profiles UI.
- Check the box Enable native code debugging.
- Save and rebuild the project.
This setting is saved in a user-specific
.csproj.userfile.
Option B β Editing launchSettings.json:
- Open the file
Properties\launchSettings.json. - Under the
"profiles"section, locate the profile matching your project name. - Add or update the property
"nativeDebugging": true.
Example:
{
"profiles": {
"YourProjectName": {
"commandName": "Project",
"nativeDebugging": true
}
}
}2. Configure Symbol Server for Native Debugging (Symweb)
To resolve native symbols when stepping through native code, configure the Symweb server:
- Go to Debug > Options from the Visual Studio toolbar.
- In the left pane, select Debugging > Symbols.
- Under Symbol file (.pdb) locations, click the β (plus) button.
- Enter the following URL:
https://symweb- Press Enter or click OK to save.
After completing both steps above, Visual Studio will be able to debug both managed and native parts of the application, using symbols downloaded from Symweb as needed.
Running the Project Against .NET Framework
This project targets multiple frameworks, including .NET Framework 4.8.1. To run or debug it against the .NET Framework instead of .NET (e.g., net11.0-windows), follow the steps below:
- In Visual Studio, locate the Debug dropdown on the top toolbar (next to the green Start button).
- Click the small downward arrow next to the active configuration. You will see the currently selected framework (typically something like
Framework (net11.0-windows)). - Click the displayed framework name to open a submenu showing all target frameworks for this project.
- Select net481 from the list.
- Build and run the project using any of the usual commands:
- Build:
Ctrl + Shift + B - Start Without Debugging:
Ctrl + F5 - Start With Debugging:
F5
- Build:
This configuration change ensures that Visual Studio builds and runs the project using the .NET Framework target.
Overview (`src/test/integration/System.Windows.Forms.Maui.IntegrationTests/readme.md`)
Porting a Winforms Maui test to xUnit
Summary
This document details how to port an existing WinForms Maui test to xUnit so it can be run as part of our continuous integration builds.
If you run into any problems, please contact Adam Yoblick.
Cloning and building source code
If you have not already done so, clone or fork the repo at https://github.com/dotnet/winforms
Checkout a new branch for your work with the following command:
textgit checkout -b <your-new-branch-name>Run a build and execute integration tests with the following command:
text.\build -integrationTest /m:1If the above step fails, STOP.
See https://github.com/dotnet/winforms/tree/main/Documentation for more verbose instructions on how to build this repo.
Porting the maui test
See the pull request at https://github.com/dotnet/winforms/pull/1240 for an example of how to do this.
Browse to .\src\System.Windows.Forms\tests\IntegrationTests\MauiTests. All Maui tests live under this folder, each in their own folder.
Create a copy of the existing MauiButtonTest folder in the same directory
- Rename the new folder to something more appropriate for the test you are porting (
MauiComboBoxTest, for example) - Browse into the new folder and rename the .csproj to the name you chose in step 3 (
MauiComboBoxTest.csproj, for example) - Delete the existing MauiButtonTest.cs file
- Rename the new folder to something more appropriate for the test you are porting (
Find the source code for the test you are porting and copy it into the new folder you created in the previous step
Clean up the Maui test source:
Rename the file appropriately (
MauiComboBoxTest.cs, for example)Add
using System.Windows.Forms.IntegrationTests.Common;to the using directives.Change the namespace to
System.Windows.Forms.IntegrationTests.MauiTestsRename the class appropriately (
MauiComboBoxTest, for example)Add the following line as the first command in the constructor:
textthis.BringToForeground();Add the following line as the first command in Main():
textThread.CurrentThread.SetCulture("en-US");Make sure each scenario method has the
[Scenario(true)]attributeRemove the entire
[Scenarios]section at the bottom of the fileMake sure each scenario methodβs name describes what the test is actually doing.
For example, the button scenario methods are named as follows:
textClick_Fires_OnClick() Hotkey_Fires_OnClick() Hotkey_DoesNotFire_OnClick()Clean up the code style, following visual studio suggestions (we will catch these in a PR, but it is good to clean up beforehand). For example:
- Use implicit types (var) whenever possible
- All fields should have access modifiers
- Prefer built-in types instead of classes (βstringβ is preferred to βStringβ)
- Only use βthisβ when necessary
- Etc...
Update the WinForms solution
- Open
.\Winforms.slnin Visual Studio - In the solution explorer, browse to
tests\integration\maui - Right-click on the maui folder and click on
Add -> Existing Project... - Add the csproj you created in the previous section
Run the maui test manually
Open a command prompt and browse to the root of your repo.
Build the source code (including the test you just ported):
text.\build-local.ps1Browse to
.\artifacts\bin\<your-test-name>\Debug\net6.0Double click on
<your-test-name>.exeCheck the results.log in the same directory to make sure everything passed
If anything shows as a failure in the log, STOP.
Figure out why the scenario is failing and fix it before moving on.
Run the maui test through the local build
Browse to
.\src\System.Windows.Forms\tests\IntegrationTests\System.Windows.Forms.Maui.IntegrationTestsCopy
WinformsMauiButtonTest.csto a new file in the same directoryRename the new file according to the Maui test you added in the earlier section (
WinformsMauiComboBoxTest.cs, for example)Rename the class to the same thing (
WinformsMauiComboBoxTest, for example)Change the ProjectName string to match the name of the Maui test project you added in the earlier section (
MauiComboBoxTest, for example)Note that this must EXACTLY MATCH the project name for the test runner to find your Maui test executable
Change the name of the single test method to match the name of your project (
MauiComboBoxTest, for example)Open a command prompt and browse to the root of your repo.
Run the following command:
text.\build -integrationTest /m:1Verify all the integration tests pass. If there is a failure, the following files may help:
- xUnit test results summary can be found at
.\artifacts\TestResults\Debug\System.Windows.Forms.Maui.IntegrationTests_net6.0_x64.html - xUnit test outputs can be found at
.\artifacts\log\Debug\System.Windows.Forms.Maui.IntegrationTests_net6.0_x64.log - The results.log for your individual Maui test can be found at
.\artifacts\bin\<your-test-name>\Debug\net6.0\results.log
- xUnit test results summary can be found at
Run the maui test through the CI build
- Create a pull request from your new branch (or fork) to the winforms main at https://github.com/dotnet/winforms/compare
- Once the PR is created (and every time new files are pushed to the PR), the build will kick off
- You can see the result of the build by scrolling down to the checks section and clicking on Show All Checks
- If the build is not successful, try to investigate the failure
- Click on Details next to the dotnet-winforms CI check
- Examine the build output. You can also click on the Summary tab and download build artifacts, which will include the same logs you examined locally
Overview (`src/test/integration/DesignSurface/DemoConsole/README.md`)
DesignSurface (DemoConsole)
Debugging Native Code in This Project
To fully enable debugging in this project (including native code and symbol resolution), perform the following two steps:
1. Enable Native Code Debugging
This allows Visual Studio to step into native code
Option A β Using Visual Studio GUI:
- Right-click the project in Solution Explorer and select Properties.
- Go to the Debug then Open debug launch profiles UI.
- Check the box Enable native code debugging.
- Save and rebuild the project.
This setting is saved in a user-specific
.csproj.userfile.
Option B β Editing launchSettings.json:
- Open the file
Properties\launchSettings.json. - Under the
"profiles"section, locate the profile matching your project name. - Add or update the property
"nativeDebugging": true.
Example:
{
"profiles": {
"YourProjectName": {
"commandName": "Project",
"nativeDebugging": true
}
}
}2. Configure Symbol Server for Native Debugging (Symweb)
To resolve native symbols when stepping through native code, configure the Symweb server:
- Go to Debug > Options from the Visual Studio toolbar.
- In the left pane, select Debugging > Symbols.
- Under Symbol file (.pdb) locations, click the β (plus) button.
- Enter the following URL:
https://symweb- Press Enter or click OK to save.
After completing both steps above, Visual Studio will be able to debug both managed and native parts of the application, using symbols downloaded from Symweb as needed.
Running the Project Against .NET Framework
This project targets multiple frameworks, including .NET Framework 4.8.1. To run or debug it against the .NET Framework instead of .NET (e.g., net11.0-windows), follow the steps below:
- In Visual Studio, locate the Debug dropdown on the top toolbar (next to the green Start button).
- Click the small downward arrow next to the active configuration. You will see the currently selected framework (typically something like
Framework (net11.0-windows)). - Click the displayed framework name to open a submenu showing all target frameworks for this project.
- Select net481 from the list.
- Build and run the project using any of the usual commands:
- Build:
Ctrl + Shift + B - Start Without Debugging:
Ctrl + F5 - Start With Debugging:
F5
- Build:
This configuration change ensures that Visual Studio builds and runs the project using the .NET Framework target.
Overview (`src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/readme.md`)
History
WinForms had documented some NETFX 1.1 APIs as obsolete in NETFX 2.0, but didn't decorate them with the [Obsolete] attributes. We shipped them in .NET3.0, then removed them completely in .NET3.1 - https://learn.microsoft.com/dotnet/core/compatibility/winforms#removed-controls.
The reason to remove these types was that replacement types have been implemented in .NET Framework 2.0 and we had stopped maintaining these classes. Meanwhile, they fell befind in support of the modern features and particularly they don't comply with the accessibility requirements.
Problem
Since then we received 2 requests to re-introduce the public surface in order to facilitate migration from the .NET Framework. Consider an application that supports extensibility plug ins compiled against the .NET Framework. .NET application can load .NET Framework assemblies if .NET Framework types can be forwarded to the .NET implementations, even if those implementations do nothing. Such plug-ins would load and work if they avoid the unsupported code. However, if the unsupported code is executed, the JIT throws a MissingMemberException that is hard to catch and handle.
Solution
We are re-adding the previously removed types as "hollow" types to provide binary compatibility. This "implementation" is copied from the .NET Framework 4.8.1 reference assembly. These types can't be executed, as they should be replaced with the modern counterparts as users migrate their applications. The main advantage is that now the above mentioned plugin would load and would throw PlatformNotSupportedException when the problematic code is executed instead of the missing member exceptions at JIT time. And the developer has an option for conditionally avoid calling the hollow methods.
We should not modify code in this folder.
Description of changes:
- None of the re-added types can be constructed, all constructors throw new PlatformNotSupportedException(), default constructors are suppressed by introducing throwing public constructors if needed.
- All attributes that have been present on the .NET Framework types, are preserved, as they are accessible by reflection.
- All re-introduced types are decorated with [EditorBrowsable(EditorBrowsableState.Never)] attribute that hides them from the intellisense. Type names can be typed in by the developer manually and intellisense would show the same members as it did for .NET Framework projects.
- All re-introduced types are decorated with the ObsoleteAttribute that results in a compile time warning. All types share a single deprecation warning Id, to simplify suppression, but have different explanation messages.
- All re-introduced types are decorated with [Browsable(false)] attribute to not show custom control properties of these types in the property browser.
- All public members are re-introduced with their metadata, except for the private attributes.
- Members inherited from the base classes (Control, for example) are not re-introduced even if they had been overridden in the past because they are not required for binary compatibility, unless they are decorated with different attributes.
- Members that are re-added to the existing types (Form and Control) are returning defaults or doing nothing.
- Non-void public or protected instance members on the restored types
throw null, not thePlatformNotSupportedExceptionto save space. - Void public or protected instance members do nothing.
- Public or protected fields return default values.
- Nullability is disabled for all re-added classes, structs and delegates for compatibility with the .NET Framework.
Use Case
.NET applications can reference .NET Framework 3rd party libraries that use these types. Code will JIT and if the unsupported code is executed, it will throw an exception that can be caught instead of JIT throwing a missing member exception. For example:
if (control.ContextMenu is not null)
{
control.ShowContextMenu();
}
else
{
// show a ContextMenuStrip
}
try
{
LegacyControl.ShowContextMenu();
}
catch (PlatformNotSupportedException)
{
// create a new ContextMenuStrip
}Getting Started (`docs/getting-started.md`)
Getting started with Windows Forms for .NET
This document describes the experience of using Windows Forms on .NET. The Developer Guide describes how to develop features and fixes for Windows Forms.
Installation
Choose one of these options:
Creating new applications
You can create a new WinForms application with dotnet new command, using the following commands:
dotnet new winforms -o MyWinFormsApp
cd MyWinFormsApp
dotnet runDesigning Forms
You can try the Windows Forms Core Designer Visual Studio extension, see Windows Forms Designer documentation. As an alternative, you can use this workaround.
Samples
Check out the .NET Windows Forms samples for both basic and advanced scenarios. Additionally, there is a collection of Windows Forms sample applications on Microsoft Learn.
Porting existing applications
To port your existing Windows Forms application from .NET Framework to .NET 6 or .NET 7, refer to our porting guidelines.
Net11Api 03 ImproveControlRendering.HighRiskReview (`docs/Net11Api_03_ImproveControlRendering.HighRiskReview.md`)
Net11Api_03 ImproveControlRendering High-Risk Review
This document records a review finding that needs native lifecycle investigation before it can be patched safely.
Deferred reveal is not activated during initial display
Reviewed merge base: 8b618e7f5
Reviewed branch tip: 1ee2b58a6
The deferred-reveal implementation attempts to cloak a form from OnHandleCreated, while ShouldUseDeferredAppearanceCloak requires both IsHandleCreated and Visible. During Show, ShowDialog, and Application.Run(form), the handle is created while evaluating HWND, before ShowWindow makes the form visible. The visibility state changes later while processing WM_SHOWWINDOW, and there is no subsequent cloak attempt. The intended initial-display cloak therefore does not activate on the normal display path.
A safe correction requires selecting a one-shot point after handle creation but before the first compositor presentation. Moving the operation into WM_SHOWWINDOW could cloak later hide/show cycles, while removing the visibility check could cloak hidden forms whose handles are created for unrelated reasons.
Before implementation, document and verify the state timeline for:
Show,ShowDialog, andApplication.Run;- hidden forms with pre-created handles;
- handle recreation;
- hide/show cycles;
- owned forms and splash screens;
- DWM composition or cloak-call failure.
The selected design should define a one-shot invariant and explicitly reject stale state after handle recreation. Native verification should inspect the DWM cloak state before first paint and after reveal. Automated tests should cover all display paths and preserve a clear rollback path if compositor timing differs across supported Windows versions.
Net11Api 04 KioskManagerComponent.HighRiskReview (`docs/Net11Api_04_KioskManagerComponent.HighRiskReview.md`)
Net11Api_04 KioskModeManager High-Risk Review
This document records review findings that require an API or lifecycle design decision before they can be patched safely.
FullScreen requested state and actual state differ
KioskModeManager.FullScreen currently returns true when fullscreen has been requested but no target form is available. FullScreenChanged, however, is raised only when the resolved form actually enters or exits fullscreen. This creates two observable state models:
- The property reports requested state through
_pendingFullScreen || _isFullScreen. - The event reports actual window state through changes to
_isFullScreen.
Consequently, setting FullScreen = true without a resolved form changes the property without raising the event. When a form later becomes available, the event is raised even though the property value remains true.
Before changing this behavior, decide whether FullScreen represents requested state or actual window state. The decision must cover:
- initialization and delayed parenting;
- missing, replaced, reparented, and disposed container controls;
- event ordering and two-way data binding;
ToggleFullScreenbehavior while a request is pending;- disposal and failed fullscreen transitions;
- compatibility for applications already observing the property or event.
Tests should define the complete transition matrix for requested, pending, entered, exited, reparented, and disposed states.
Net11Api 05 VisualStylesMode.HighRiskReview (`docs/Net11Api_05_VisualStylesMode.HighRiskReview.md`)
Net11Api_05 VisualStylesMode High-Risk Review
This document records animation infrastructure findings that require architecture and lifecycle decisions before they can be patched safely.
VisualStylesMode transition impact dispatch
Control.OnVisualStylesModeChanged now owns impact processing for effective renderer changes. Overrides that
call base inherit preferred-size cache clearing, style/non-client frame refresh, invalidation, and deferred
layout for metric-affecting changes. The transition carries immutable old/new effective modes through the
existing EventArgs virtual shape; nested property changes cannot overwrite an outer transition, and stale
child cascades are suppressed.
Metric layout requests are collected while the complete affected subtree transitions, then coalesced to one
layout per container. This is required so a container measures only fully updated children and soAutoSize = false text boxes still trigger parent remeasurement. TextBoxBase treats only crossings between
classic/disabled and Net11-or-later rendering as metric changes; Net11-to-Latest shares its preferred-height
and non-client padding metrics and repaints only.
SystemVisualSettings High Contrast transitions enter the same dispatcher with immutable old/new snapshots.
Every affected control compares its own effective mode, so locally configured descendants are included, while
effective-equality controls bypass visual-styles events, invalidation, and layout. The shared transition state
preserves existing reentrant-change suppression and layout coalescing.
System visual settings renderer composition
Animated renderers subscribe only to their owning control's instance settings cascade. Accent transitions clear
their cached accent color; the control cascade repaints the tree. Disabling client-area animation completes an
active transition at progress 1 and unregisters it from the per-thread manager. Re-enabling does not restart a
completed transition; a later state change starts a new one.
TextBoxBase derives modern focus-border thickness and focus-band height from the system focus-border metrics,
text-scale factor, and device DPI. These metrics affect only modern chrome. The existing never-invert client
carve, small-control flat fallback, and classic-mode metrics remain unchanged.
GroupBox content geometry
The Net11 FlatStyle.Standard GroupBox renders its caption above a borderless rectangular surface. ItsDisplayRectangle therefore starts lower and is shorter than the classic etched-frame rectangle. This is the
largest compatibility lever in the GroupBox change: the mode is opt-in, the paint and layout paths share the
same metrics, and the VisualStylesMode impact dispatcher remeasures AutoSize containers when the effective
renderer crosses the classic/modern boundary. The caption aligns to the control Padding, and the smaller bottom
content inset preserves separation when GroupBoxes are stacked. FlatStyle.Flat uses a rounded Windows-accent
outline; FlatStyle.Popup uses a Windows-accent header. FlatStyle.System remains native and does not use these
metrics.
ComboBox adapter routing
Net11 routes FlatStyle.Standard ComboBox controls through the WinForms adapter path for the first time so the
field can share TextBoxBase's rounded chrome metrics and color scheme. Flat uses the same scheme with a
square-corner border; Popup keeps the rounded Standard geometry but uses the Windows accent color for its
border. All three styles add one logical pixel of framework inset on top of the now-designer-visible public
Padding. The adapter changes preferred height across the classic/modern boundary; FlatStyle.System remains
native and bypasses the adapter.
Animation timing and thread ownership
The timer now uses an absolute Stopwatch schedule with a high-resolution waitable timer on supported Windows
versions and a coarse 30 Hz fallback. It no longer changes process-wide timer resolution, and residual spinning is
bounded to the sub-millisecond remainder. Registration/start/stop transitions are generation-owned, callback
dispatch is allocation-free in steady state, and stale generations cannot dispatch after replacement.
AnimationManager is now per UI thread. Each manager captures that thread's synchronization context, derives
animation progress from the timer tick timeline, and is disposed when its message loop exits. A renderer fault
quarantines that renderer without stopping unrelated animations on the same thread.
The remaining power risk is idle registration lifetime: after a UI thread starts its first animation, its manager
keeps one timer registration until Application.ThreadExit, even when no renderer is currently running. The pacer
therefore continues waking and dispatching an empty frame callback. A future optimization can make manager
registration lazy while preserving the generation and thread-ownership invariants established by the rework.
Building (`docs/building.md`)
Building Windows Forms
Prerequisites
Follow the prerequisites listed at Developer Guide.
Building
Building from command line (Preferred)
- Run
.\build.cmdfrom the repository root. This builds theWinforms.slnusing the default config (Debug|Any CPU). - To specify a build configuration, add
-configurationfollowed by the config such as.\build -configuration Release.
Note that this does not build using your machine-wide installed version of the dotnet sdk. It builds using the repo-local .NET SDK specified in the global.json in the repository root.
Building from Visual Studio
- .NET 6.0 and above branches need VisualStudio 2022 to build.
- Run
.\restore.cmdfrom the repository root. - Run
.\start-vs.cmdfrom the repository root. This will prepend the repo-local .NET SDK to the path, and openWinforms.slnin Visual Studio. - You should now be able to build as you normally would.
Building from Visual Studio Code
- (Optional) Run
.\restore.cmdfrom the repository root. - Run
.\start-code.cmdfrom the repository root. This will prepend the repo-local .NET SDK to the path, and openWinforms.slnin Visual Studio Code. - You should now be able to build and test as you normally would from command line (or VS Code, if you have set it).
Build outputs
- All build outputs are generated under the
artifactsfolder. - Binaries are under
artifacts\bin.- For example,
System.Windows.Forms.dllcan be found underartifacts\bin\System.Windows.Forms\Debug\net6.0.
- For example,
- Logs are found under
artifacts\log. - Packages are found under
artifacts\packages.
Running apps from VS
- Right click on the project you wish to run and select 'Set as startup project'.
- F5.
Running apps from command line
- Build the solution:
.\build.cmd - Navigate to the project you wish to run, e.g. to run AccessibilityTests test app:text
winforms> pushd .\src\System.Windows.Forms\tests\AccessibilityTests winforms\src\System.Windows.Forms\tests\AccessibilityTests> dotnet run - (Alternatively) Navigate to
.\artifacts\bin\AccessibilityTests\Debug\net6.0and run the app manually.
Troubleshooting build errors
- Most build errors are compile errors and can be dealt with accordingly.
- Other error may be from MSBuild tasks. You need to examine the build logs to investigate.
- The logs are generated at
.\artifacts\log\Debug\Build.binlog - The file format is an MSBuild Binary Log. Install the MSBuild Structured Log Viewer to view them.
- The logs are generated at
- Windows Forms uses Visual Studio MSBuild but for certain features we require the latest MSBuild from .NET Core/.NET SDK. If you are on an official version of Visual Studio (i.e. not a Preview version), then you may need to enable previews for .NET Core/.NET SDKs in VS.
- you can do this in VS under Tools->Options->Environment->Preview Features->Use previews of the .Net Core SDK (Requires restart)
Creating a package
To create the Microsoft.Private.Winforms package, run .\build -pack
Localization
If you need to add a new localization string or update an existing one, follow these steps:
- Modify
Resource\SR.resxfile adding or updating necessary strings in the project that contains the said strings. (It is often faster/easier to open SR.resx in an XML editor). - Regenerate the localization files by rebuilding the solution/project from Visual Studio, or by executing
.\build.cmdcommand. You can also build just the modified project by runningdotnet buildfrom the project's root.
Coding Style (`docs/coding-style.md`)
Coding Style
Our coding style is informed by the .NET Runtime coding style and the latest "Framework Design Guidelines" (currently third edition).
When we establish guidelines we consider how they impact the ability to comprehend the code. The easier the code is to understand, the easier it is to maintain. There are a few core guiding principles:
- Code should be clear and unambiguous.
- Code should be efficient and concise, using the minimum amount of code necessary to achieve the desired functionality without sacrificing readability or maintainability (and as such we lean into new C# language features).
- Guidelines are mutable where they inhibit our guiding principles.
There are a few key things we strive for to attain our clarity goals:
- Minimal code block nesting.
- Minimal scrolling for code blocks (almost no horizontal and limited vertical).
- Minimal code visibility (via access modifiers, type nesting, and local functions).
- All context captured in source (via code or comments).
Windows Forms dates back to the mid 1990s (originally Windows Foundation Classes). It has seen over 25 years of language and coding evolution. We believe that updating the code to modern style makes the code easier to maintain and update in the longer term. While we do not update everything as the style guidelines evolve, we do make continual investments in cleaning the codebase as an engineering best practice.
A number of the following detailed guidelines are captured in our .editorconfig and as such you'll get the appropriate feedback in Visual Studio. We're iterating to enable more automated analyzers in the future.
Spacing
Enabling viewing white space will make conforming to these rules easier. In Visual Studio: "View White Space (Ctrl+R, Ctrl+W)" or "Edit -> Advanced -> View White Space".
- Use four spaces of indentation (never use tabs). XML blocks should get a single space indent (this includes XML comments).
- Lines should not have trailing white space or more than one space between code elements (
=can be aligned when there is notable value in doing so, such as bit flag values). - Avoid more than one empty line at any time. For example, do not have two blank lines between members of a type.
- Closing brackets (
}) on a line by themself should get a blank line after them unless followed by another line with just a closing bracket.
Line Breaks
- Lines should be less than 120 characters long to limit horizontal scrolling in development tools. If it improves clarity, they can be slightly over, but they never should be longer than 150. Statements should not be broken into multiple lines when they fall under the 120 character limit.
- When using expression body definitions (
=>) they should be on the same line if they fit, otherwise they should be broken after the=>and indented once on the next line. - When breaking arguments to a method, all arguments should be indented on individual lines at one indent in.
- When breaking logical statements, all logical operators (
&&,||, etc.) at a given parenthetical scope should be indented on individual lines at one indent in. - When breaking ternary operators (
?:), they should be broken into two singly indented lines, starting with?and:.
Code Samples
More complicated line breaks:
if (_trackColumn != previousColumnIndex
&& !(previousColumnIndex == -1 && hti._col == _trackColumn)
&& (dataGridViewColumnNext is null || _trackColumn != dataGridViewColumnNext.Index))if (Focused
&& !IsCurrentCellInEditMode
&& (EditMode == DataGridViewEditMode.EditOnEnter
|| (EditMode != DataGridViewEditMode.EditProgrammatically && CurrentCellInternal.EditType is null)))Why => isn't at the beginning of a line:
// Further breaks logically align
internal bool SingleVerticalBorderAdded =>
!_layout.RowHeadersVisible
&& (AdvancedCellBorderStyle.All == DataGridViewAdvancedCellBorderStyle.Single
|| CellBorderStyle == DataGridViewCellBorderStyle.SingleVertical);
// Same example, broken alignment
internal bool SingleVerticalBorderAdded
=> !_layout.RowHeadersVisible
&& (AdvancedCellBorderStyle.All == DataGridViewAdvancedCellBorderStyle.Single
|| CellBorderStyle == DataGridViewCellBorderStyle.SingleVertical);Code Blocks
- Use Allman style braces, where each brace begins on a new line.
ifstatements must use code blocks with the exception of single line parameter validation at the beginning of a method.usingstatements must use code blocks. Simple using declarations (which don't use code blocks) are preferred over using statements when possible. In the rare case that there are multipleusingstatements in a row, they should share a code block to reduce nesting.fixedstatements must use code blocks. When there are multipleusingstatements in a row, they should share a code block to reduce nesting.unsafeshould generally be applied to either whole methods or classes (when many methods are unsafe). When unsafe blocks are strictly necessary (say in an async or yield method) they must use code blocks.
Comments
- Code comments should be on their own line and precede the code they refer to. In condition blocks, comments should be inside the blocks (e.g., never before the
else). Switch expression conditions have no blocks and should have the comment before the relevant condition line. - Avoid comments labeling the end of a block (never
if {} else {} // else). - Methods and properties should have XML comments, not
\\comments.publicAPI comments should align with the documentation on https://learn.microsoft.com. - XML comment blocks that provide no further clarification are not required, with the exception of public API. (You don't have to document the return value if it is obvious, for example.)
- Do not replicate comments from base methods or interfaces. Use
\\\when docs aren't automatically inherited or you need to modify only a part of the documentation. - Avoid
\* *\comments.
General Naming
- Avoid abbreviations and type names for variable or property names (e.g.
Point firstPointnotpt1). - Names should describe their use, not their type (e.g.
Rectangle boundsoverRectangle rectangle). - Use
nameof(...)instead of"..."whenever possible and relevant.
General Visibility
- Always specify the visibility, even if it's the default (e.g.
private string _foonotstring _foo). Visibility should be the first modifier (e.g.public abstractnotabstract public).
Namespace Usings
- Namespace
usings should be specified at the top of the file, beforenamespacedeclarations, and should be sorted alphabetically, with the exception ofSystem.*namespaces, which are to be placed on top of all others. - Global
usings go should in a file calledglobalusings.cs.
Namespaces
- Use file-scoped namespaces.
- Files should be in sub folders that match the namespaces. Additional organizational folders can be created within namespace folders when necessary for clarity.
Types
- All types should be in their own files. This includes nested types.
- All internal and private classes should be sealed when they are not derived from.
- Use Pascal Casing to name all types. The only exception is for interop types, which should match the native casing.
- Consider primary constructors for "simple" structs and classes where there is only one constructor with no logic other than field assignment.
Fields
- Type constants, statics, and fields should be specified at the top within the type declaration.
- Use
_camelCasefor internal and private fields and usereadonlywhere possible. Prefix instance fields with_, static fields withs_and thread static fields witht_. - Use
PascalCasingto name all constants. The only exception is for interop code where the constant value should match the naming in the native code. publicorinternalfields should not be used (use properties).protectedproperties should be preferred over fields.
Properties
- Use
PascalCasingto name all properties. - Use auto-implemented properties when possible.
- Prefer methods over properties for expensive code or code with significant side effects.
- Use expression body definitions (
=>) for single line getters and setters.
Methods
- Use
PascalCasingto name all methods. - Constructors should precede all other methods and properties.
- Use expression body definitions (
=>) for single line methods. - Avoid methods with a single calling method. Use local functions. Don't use a method at all in these cases if the logic is simple and not replicated.
- Use static methods when instance fields are not accessed, and consider passing fields in to make methods static to clarify what a method modifies.
- Prefer methods where top level blocks fit in a single editor screen (around 25 lines of code). Local functions can be used to break the logic into digestible chunks.
- Keep block nesting to a minimum (preferably no more than 3 indents, 5 at the maximum). Use early outs and inverted conditionals to help manage nesting.
- Exit (return) early from methods to reduce nesting.
Variables
- We never use
varfor built-in types, e.g. don't usevar count = 2, instead useint count = 2.varshould only be used when the type name inhibits code readability. - Target-typed
new()should be used to remove redundancy, not var. For example:FileStream stream = new(...);, notvar stream = new FileStream(...);. Using target-typednew()for succinctness is allowed where the type is reasonably obvious, e.g.Point[] points = [new(1, 2), new(5, 6)]; - Collection expressions are preferred, e.g.
Point[] points = [new(1, 2)];overPoint[] points = new Point[] { new(1, 2) };
General
- Use language keywords instead of BCL types (e.g.
int, string, floatinstead ofInt32, String, Single, etc.) for both type references as well as method calls (e.g.int.Parseinstead ofInt32.Parse). - Always use interpolated strings when composing / formatting strings.
- Use raw string literals for multi-line strings.
Conditions
- Prefer ternary operators (
?:) overif .. elsewhen there is only one line in each clause. - Prefer pattern matching in conditions.
- Prefer null-coalescing operators (
??and??=). - Prefer switch expressions over switch statements where possible.
Nullability
- Prefer pattern matching to check variable type and null state (e.g.
if (parameter is Form form),if (manager.GetService() is { } container), etc.). - Using the postfix
!null-forgiving operator to override null analysis must always get a descriptive comment. Prefer to refactor code to avoid this where possible. If we do not control the code in question prefer to throwArgumentExceptionwhen an associated argument is known orInvalidOperationExceptionotherwise. Do not let code fall intoNullReferenceException.
Visual Basic
Forthcoming
Dataobject Serialization (`docs/dataobject-serialization.md`)
WinForms Clipboard/DataObject updates for .NET 10
The .NET 10 cycle introduces major updates to the clipboard and dragβandβdrop stack in System.Windows.Forms. These changes were driven by the removal of BinaryFormatter from the runtime, which previously handled serialization of custom types for OLE data transfer. The new APIs provide explicit typeβsafe methods and use JSON instead of binary serialization, while still allowing an optβin fallback to BinaryFormatter for legacy applications.
Background
BinaryFormatter was used when a custom type was placed on the clipboard for a user defined data format or in a dragβandβdrop operation. The BinaryFormatter is insecure and has been removed from the base runtime. WinForms has introduced new, safer APIs and deprecated older APIs. WinForms also now makes you explicitly enable clipboard and drag drop scenarios for BinaryFormatter so you don't get unexpected usage if you need to enable it for other scenarios.
Best Practices
- Avoid enabling the
BinaryFormatterfor clipboard and drag and drop scenarios if at all possible. - Use the recommended built-in types (listed below) and
SetDataAPIs. - For custom types, use the new
SetDataAsJson<T>()APIs or serialize manually asstringorbyte[]data. - Always use
TryGetData<T>()to get data (notGetData()). - Read legacy data using the
NrbfDecoder.
Recommended builtβin types
Without BinaryFormatter, WinForms can still transfer certain intrinsic types using a builtβin manual serializer in the BinaryFormatter data format, NRBF (.NET Remoting Binary Format). Supported types include:
- Primitive types:
bool,byte,char,decimal,double,short,int,long,sbyte,ushort,uint,ulong,float,string,TimeSpanandDateTime. - Arrays or
List<T>of the above primitive types. For maximum resilience, avoidstring[]andList<string>as they can contain nulls. - System.Drawing types:
Bitmap,PointF,RectangleF,Point,Rectangle,SizeF,Size, andColor.
When data is one of these types, WinForms transfers it without the user needing to implement any special logic. For custom types with DataFormats that aren't predefined, you should use JSON or your own serialization using raw string or byte data.
Safety
As with any serialization, you should take great care when processing the data you receive. If you follow all of the "Best Practices" above Windows Forms will guarantee that only the types you've requested will be created. We cannot guarantee what the data looks like within the types (other than that we will only create specified types) and we cannot constrain the size of the deserialized data.
For example, if you deserialize an integer, it can contain any value. It could be negative, it could be zero. Your code should be able to handle any possible value- it may not be what you expect. It could be set to a value unexpected by your code, either accidentally or deliberately.
New APIs in .NETΒ 10
`TryGetData` β constrained deserialization of data
Several overloads of TryGetData<T> exist on Clipboard, DataObject, ITypedDataObject, DataObjectExtensions and the VB ClipboardProxy. These methods:
- Attempt to retrieve data in a given format and deserialize it to the requested type
T. - Return a boolean indicating success instead of throwing.
- When enabling
BinaryFormatter, requires aresolvercallback to restrict deserialization to known types.
Example using the simplest overload (no resolver). This will never fall back to BinaryFormatter; it only succeeds if the data was serialized with SetDataAsJson<T>, one of the builtβin safe NRBF types (see above), or if the data was set in-process without the copy flag (not the default):
// Reading a Person previously stored with SetDataAsJson
if (Clipboard.TryGetData("MyApp.Person", out Person? person))
{
Console.WriteLine($"Person: {person.Name} is {person.Age}");
}
else
{
Console.WriteLine("No person data on clipboard or type mismatch");
}A resolver overload can be used when you need to support legacy binary formats and have fully enabled the BinaryFormatter. See the "Enabling Full BinaryFormatter Support" below for details.
`SetDataAsJson` β write custom types without BinaryFormatter
SetDataAsJson<T> is a new overload on the Clipboard, DataObject and VBΒ ClipboardProxy classes. It serializes simple T types to JSON using System.Text.Json. For example:
public record Person(string Name, int Age);
// Serialize to JSON and put on the clipboard
Person person = new("Alisha", 34);
Clipboard.SetDataAsJson(person);Internally DataObject.SetDataAsJson<T> writes the JSON to the underlying OLE data stream in the NRBF format in a way that it can be manually extracted without using the BinaryFormatter.
`ITypedDataObject` interface β enabling typed data retrieval
Custom data objects used with dragΒ &Β drop should implement the ITypedDataObject interface. This interface declares the typed TryGetData<T> overloads. Implementing it signals to WinForms that your data object is capable of deserializing data into specific types; if it is not implemented, calls to the typed APIs throw NotSupportedException. The DataObject class already implements ITypedDataObject.
Summary of new API signatures
| API (class) | Description | Remarks |
|---|---|---|
DataObject.SetDataAsJson<T>(string format, T data) and DataObject.SetDataAsJson<T>(T data) |
Serialize an object to JSON and store it under the specified format (or the typeβs full name). | Uses System.Text.Json. |
Clipboard.SetDataAsJson<T>(string format, T data) |
Clears the clipboard and stores JSON data in a new DataObject. |
Equivalent VB method: ClipboardProxy.SetDataAsJson(Of T). |
DataObject.TryGetData<T>(string format, Func<TypeName,Type> resolver, bool autoConvert, out T data) |
Attempts to read data and deserialize it; resolver controls allowed types when falling back to binary deserialization; autoConvert indicates whether OLE conversion should be attempted. |
Returns true when successful; does not throw. |
DataObject.TryGetData<T>(string format, bool autoConvert, out T data) |
Same as above without a resolver; does not fall back to binary serialization. | |
DataObject.TryGetData<T>(string format, out T data) |
Uses default autoConvert=true; no resolver. |
|
DataObject.TryGetData<T>(out T data) |
Infers the format from the full name of T. |
|
Clipboard.TryGetData<T> overloads |
Static methods that obtain the current clipboard data and delegate to the corresponding DataObject overloads. |
Overloads include versions with and without resolver and autoConvert. |
ITypedDataObject |
Interface declaring typed TryGetData methods. |
Implement on custom data objects used for dragΒ &Β drop. |
DataObjectExtensions.TryGetData<T> |
Extension methods on IDataObject that call through to ITypedDataObject; throws when the underlying object does not implement ITypedDataObject. |
Enables typed retrieval without explicitly casting. |
ClipboardProxy (VB) new methods |
Exposes SetDataAsJson(Of T), TryGetData(Of T) and TryGetData(Of T)(resolver) in VisualΒ Basicβs My.Computer.Clipboard. |
VB apps can use typed clipboard APIs without referencing the System.Windows.Forms namespace directly. |
Enabling full `BinaryFormatter` support (not recommended)
Although the default behavior removes BinaryFormatter, a full fallback is still available for legacy applications. To enable it you must do all of the following:
Reference the
System.Runtime.Serialization.Formatterspackage: Add a PackageReference to your project file:xml<ItemGroup> <PackageReference Include="System.Runtime.Serialization.Formatters" Version="10.0.0"/> </ItemGroup>This compatibility package is unsupported and dangerous; use it only as a last resort.
Set the runtime switch to enable unsafe serialization: In your project file, set the property
EnableUnsafeBinaryFormatterSerializationtotrue. Without this switch, calls to binary serialization throw.xml<PropertyGroup> <EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization> </PropertyGroup>Set the WinFormsβspecific switch: WinForms adds an appβcontext switch
Windows.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization. Without this switch, WinForms will not fall back toBinaryFormattereven if the general serialization switch is enabled. Set this flag in yourruntimeconfig.jsonfile:json{ "configProperties": { "Windows.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization": true } }Use the resolver overload to control which types are allowed: Even with
BinaryFormatterenabled, you must specify a resolver when callingTryGetData<T>so you only deserialize known types. The existing, deprecated,GetDataAPIs will continue to work, but provide no additional constraints. A sample type resolver follows:csharpstatic Type MyExactMatchResolver(TypeName typeName) { (Type type, TypeName typeName)[] allowedTypes = [ (typeof(MyClass1), TypeName.Parse(typeof(MyClass1).AssemblyQualifiedName)), (typeof(MyClass2), TypeName.Parse(typeof(MyClass2).AssemblyQualifiedName)) ]; foreach (var (type, name) in allowedTypes) { // Check the type name if (name.FullName != typeName.FullName) { continue; } // You should also consider checking the `.AssemblyName` here as well. return type; } // Always throw to prevent the BinaryFormatter from implicitly loading assemblies. throw new NotSupportedException($"Can't resolve {typeName.AssemblyQualifiedName}"); }
Only after completing all of the first three steps above will WinForms behave as in older versions and automatically use BinaryFormatter for unknown types.
Compatibility
Predefined formats and primitive types
Types already defined in DataFormats and the primitive types described in the beginning of the document just work. This ensures compatibility with existing applications and common data exchange scenarios.
Reading JSON downβlevel
Even though SetDataAsJson<T> is only available in .NETΒ 10, you can roundβtrip JSON data across processes and frameworks by storing the JSON yourself (as UTF-8 byte[] or string) if you're able to change all of the consumer code.
Reading legacy NRBF / binary data in .NETΒ 10
While .NET 10 removes BinaryFormatter by default, WinForms still includes safe support for the types mentioned above and provides mechanisms to read legacy binary payloads without referencing BinaryFormatter. The simplest way to read NRBF data is to use the NrbfDecoder SerializationRecord:
if (dataObject.TryGetData("LegacyFormat", out SerializationRecord? record))
{
// Process the serialization record
}You can also retrieve raw data as a MemoryStream for custom processing.
Links
- BinaryFormatter migration guide
- Windows Forms migration guide for BinaryFormatter
- Windows Forms and Windows Presentation Foundation BinaryFormatter OLE guidance
- System.Text.Json documentation
- .NET Core runtime configuration settings
- Windows Forms Clipboard class
- DataObject class
- NRBF format specification
Debugging (`docs/debugging.md`)
Debugging
There are two recommended ways to debug Windows Forms binaries. Both require that you first build from source.
Once you are ready to debug your changes on an existing Windows Forms application, please follow one of the two following recommended techniques for relating your changes back to your project. You should then be able to set breakpoints and debug as expected.
If you do not want to modify your local SDK, you may with to perform technique 2. However if you do not want to add an additional reference to your project, technique 1 may be better for you.
1. Copy your changes into the SDK
Copy the resulting assembly(-ies)β―fromβ―your local build.
- They are located at
[Path-to-repo]\winforms\artifacts\bin\System.Windows.Forms\Debug\net7.0 - And need to be copied to the system dotnet folder
[Drive]:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\[Version]
:warning: If you have updated any public APIs, you'll need to overwrite the ref assemblies too (since that's what VS uses when resolving types):
- The ref assemblies compile to
[Path-to-repo]\winforms\artifacts\bin\System.Windows.Forms\Debug\net7.0\ref - These need to be copied to
[Drive]:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\[Version]
where:
- [Drive] is your OS drive (for example, C:),
- [Path-to-repo] is the additional path to our repository from the base drive, and
- [Version] is your WindowsDesktop version directory (for example, 5.0.0-rc.2.20464.5).
NOTE Make sure to make a backup copy of assemblies you replace, you will modify your SDK. Alternatively you can repair the install or reinstall.
2. Point your project to your experimental binary(-ies)
Add references to the binary(-ies) to your project ported to .NET. For example, for System.Windows.Forms, you should add the following reference:
<ItemGroup>
<Reference Include="[Drive]:[Path-to-repo]\winforms\artifacts\bin\System.Windows.Forms\Debug\net9.0\System.Drawing.Common.dll" />
<Reference Include="[Drive]:[Path-to-repo]\winforms\artifacts\bin\System.Windows.Forms\Debug\net9.0\System.Private.Windows.Core.dll" />
<Reference Include="[Drive]:[Path-to-repo]\winforms\artifacts\bin\System.Windows.Forms\Debug\net9.0\System.Windows.Forms.dll" />
<Reference Include="[Drive]:[Path-to-repo]\winforms\artifacts\bin\System.Windows.Forms.Primitives\Debug\net9.0\System.Windows.Forms.Primitives.dll" />
<Reference Include="[Drive]:[Path-to-repo]\winforms\artifacts\bin\System.Windows.Forms.Design\Debug\net9.0\System.Windows.Forms.Design.dll" />
</ItemGroup>where:
- [Drive] is the drive you have our repository in, and
- [Path-to-repo] is the additional path to our repository from the base drive.
Designer Accessibility (`docs/designer-accessibility.md`)
Working around accessibility issues in the Windows Forms Out of Process Designer
The introduction of Windows Forms (WinForms) to .NET Core 3.1 and beyond is enabling developers to migrate their applications from .NET Framework to the more modern .NET versions. In order to support the designing of these applications within Visual Studio the team was required to create an entirely new Out of Process (OOP) Designer, which is currently still in a preview state. For more information on the technical reasons for this change, and the current status of the designer, see our recent blog post.
There are several features that still need to be implemented in the OOP designer, foremost of which is supporting the use of Assistive Technologies such as NVDA, Narrator, and Jaws while using the OOP designer. As much as the team wanted to fully support accessibility right out of the gate with this new designer, technical reasons prevented getting an early start on this feature, and resulted in unfortunate user experiences and deadlocks under assistive technology tooling in certain scenarios.
While the team is rebuilding the experience under the new model, it is straightforward to work around the current accessibility issues by using the classic .NET Framework in-process designer and building output assemblies in both .NET and .NET Framework 4.7.2 (or 4.8). In order to multi-target and use the .NET Framework designer use the following steps:
Create your .NET WinForms 3.1 (C# only), 5 or 6 app (both VB and C#).
Before the designer by opening a Form or a UserControl, edit the .csproj
file.In that file you will find the following entry, telling the compiler what
.NET version to build against:
"><PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>- Change the
<TargetFramework>net6.0-windows</TargetFramework>to the following:<TargetFrameworks>net472;net6.0-windows</TargetFrameworks>
Note: it is important to use net472 (or net48 if you want to target against .NET Framework 4.8) first in the list of targeted frameworks. This tells Visual Studio to load the in-process .NET Framework rather than the .NET OOP WinForms designer.
Once youβve completed the above steps, you can build your application and interact with the forms directly as you have always done. When you build binaries, they will be built targeting both .NET 6 and .NET Framework 4.7.2.
:warning: There are limitations around only using the features and types available in both .NET Framework 4.7.2 and .NET 6. If you want to use .NET 6 features, you will need to use C# preprocessor directives.
We will be communicating out as soon as full accessibility support is available in the WinForms .NET OOP designer so that you can remove the need to multi-target. In the meantime, this workaround should allow you to build your .NET Windows Forms applications with the respective accessibility features supported at design time.
Happy Coding!
Developer Guide (`docs/developer-guide.md`)
Developer Guide
The following document describes the setup and workflow that is recommended for working on the Windows Forms project. It assumes that you have read the Contributing Guide.
The Issue Guide describes our approach to using GitHub issues.
Machine Setup
Windows Forms development requires the following workloads and components be selected when installing Visual Studio 2022 (17.0.0):
- Required Workloads:
- .NET Desktop Development
- Desktop development with C++
- Required Individual Components:
- Windows 10 SDK
- C++ CMake tools for Windows
- Required Windows Features
- 'Media Features' including 'Windows Media Player Legacy (App)' (Windows 11) or 'Windows Media Player' (Windows 10)
- Useful Visual Studio extensions:
- Editor guidelines, see https://github.com/dotnet/winforms/pull/4836 for more information
- VS Color Output
- Productivity Power Tools
Optional: To get the latest C# features in Visual Studio or to experiment with the latest runtime when not using the start-vs.cmd, install the latest SDK build.
Workflow
We use the following workflow for building as well as testing features and fixes.
You first need to fork then clone this Windows Forms repository. This is a one-time task.
More Information
- Shared WinForms/WPF Infrastructure - Important guidance for contributors working on clipboard/OLE code
- How to write good docs for .NET
- .NET Docs and Guidelines
- "help wanted" issues
Issue Guide (`docs/issue-guide.md`)
Issue Guide
This page outlines how WinForms team thinks about and handles issues.
For us, issues on GitHub represent actionable work that should be done at some future point.
It may be as simple as a small product or test bug or as large as the work tracking the design of a new feature.
We will keep issues open even if the WinForms team internally has no plans to address them in an upcoming release, as long as we believe they are in line with our direction.
How to file issues
You can help us streamline our response time to your feedback and ideas by filing high-quality reports.
Known Issues
If you encounter an issue using the latest .NET 5.0 SDK, please:
- Explore this repositories issues, and then
- Open a new issue if there is none.
Bug reports
In general, try to be specific. Get straight to the main point. Leave additional details, options, and alternatives to the end (hint: separate them visually). Don't write long bug reports, unless you have to.
- Include a minimal reproduction (repro) in your bug if at all possible (chop off dependencies, remove as much code as possible). If it is not possible, say why.
:warning: Note: Yes, it may take some time to minimize a repro from your larger app - but that is exactly what we would do in most cases anyway. Issues with clear, concise reproduction guidelines are easier for us to investigate; such issues will have higher chance of being addressed quickly. - Include call-stacks, symptom descriptions, and differences between actual and expected behaviors.
Feature and API suggestions
Assume that the reader has minimal knowledge and experience with writing apps/libraries that would benefit from the feature, so try and write to that audience. Provide clear description of your suggestion, and explain scenarios in which it would be helpful and why (motivation).
Prepare code examples that showcase the need and general usefulness of the proposal. Specifically:
- Code that shows the surface area of the API.
- Code that shows real world scenarios, and how they would otherwise be handled.
- Details showing the usage/consumption of the proposed new package, and alternatives (e.g. not having this be a separate package).
:warning: All new API or changes to public API must undergo an API review process. Learn more what the API review process entails.
Here are few example of a good API proposal:
Assignee
We assign each issue to assignee, when the assignee is ready to pick up the work and start working on it.
If the issue is not assigned to anyone and you want to pick it up, please say so - we will assign the issue to you.
If the issue is already assigned to someone, please coordinate with the assignee before you start working on it.
Up-votes on issues
Up-votes on first post of each issue are useful hint for our prioritization.
We can sort issues by number of up-votes, and we will review the top list on a regular basis.
Triage rules
Guidance for triage of issues for Windows Forms team members:
- Issue has no Assignee, unless someone is working on the issue at the moment.
- Use help wanted as much as possible, ideally with a quick note about next steps / complexity of the issue.
- Set milestone to Future, unless you can 95%-commit you can fund the issue in specific milestone.
- Each issue has exactly one "issue type" label (bug, enhancement, api-suggestion, test-bug, test-enhancement, question, documentation, etc.).
- Don't be afraid to say no, or close issues β just explain why and be polite.
- Don't be afraid to be wrong - just be flexible when new information appears.
Feel free to use other labels if it helps your triage efforts (for example, needs-more-info, design-discussion, tenet-compatibility, etc.).
Motivation for triage rules
Issue has no Assignee, unless someone is working on the issue at the moment.
- Motivation: Observation is that contributors are less likely to grab assigned issues, no matter what the repository rules say.
Use help wanted as much as possible, ideally with a quick note about next steps / complexity of the issue.
NB: Per https://up-for-grabs.net, such issues should be no longer than few nights' worth of work. They should be actionable (that is, no mysterious CI failures that can't be tested in the open).Set milestone to Future, unless you can 95%-commit you can fund the issue in specific milestone.
- Motivation: Helps communicate desire/timeline to community. Can spark further priority/impact discussion.
Each issue has exactly one "issue type" label (bug, enhancement, api-suggestion, test-bug, test-enhancement, question, documentation, etc.).
- Don't be afraid to be wrong when deciding 'bug' vs. 'test-bug' (flip a coin if you must). The most useful values for tracking are 'api-*' vs. 'enhancement', 'question', and 'documentation'.
Pull-Request (PR) Guide
Each PR has to have reviewer approval from at least one Windows Forms team member who is not author of the change, before it can be merged.
Please don't set any labels on PRs.
- exceptions:
- NO-MERGE may be supplied by the WinForms team in order to indicate that the PR should be halted; a reason should be given in the comments
- waiting-on-testing may be supplied by the WinForms team to inform the PR author(s) that their PR is being delayed due to internal, manual testing; this action does not require action by the author(s).
- Motivation: All the important info (issue type label, API approval label, etc.) is already captured on the associated issue.
- exceptions:
Push PRs forward, don't let them go stale (response every 5+ days, ideally no PRs older than 2 weeks).
Close stuck or long-term blocked PRs (for example, due to missing API approval, etc.) and reopen them once they are unstuck.
- **Motivation: Keep only active PRs. WIP (work-in-progress) PRs should be rare and should not become stale (2+ weeks old). If a PR is stale and there is not immediate path forward, consider closing the PR until it is unblocked/unstuck.
Link PR to related issue via auto-closing (add "Fixes #12345" into your PR description).
List Of Diagnostics (`docs/list-of-diagnostics.md`)
List of Diagnostics Produced by Windows Forms .NET
We have three kinds of diagnostic messages: obsoletions, code analyzer diagnostics, and experimental feature compiler errors.
Obsoletions
Per the Better Obsoletion design and similar to the runtime diagnostics, we now have a strategy for marking existing APIs as [Obsolete]. This takes advantage of the new diagnostic ID and URL template mechanisms introduced to ObsoleteAttribute in .NET 5.
The diagnostic ID values reserved for obsoletions are WFDEV001 through WFDEV999. When obsoleting an API, claim the next three-digit identifier in the WFDEV### sequence and add it to the list below. The URL template for all obsoletions is https://aka.ms/winforms-warnings/{0}. The {0} placeholder is replaced by the compiler with the WFDEV### identifier.
Acceptance Criteria for Adding an Obsoletion
- Add the obsoletion to the table below, claiming the next diagnostic ID.
- Ensure the description is meaningful within the context of this table and without requiring the context of the calling code.
- Add new constants to
src\Common\src\Obsoletions.cs, following the existing conventions:- A
...Messageconst using the same description added to the table below. - A
...DiagnosticIdconst for theWFDEV###ID.
- A
- If adding the
<Obsolete>attribute to the Microsoft.VisualBasic.Forms assembly, edit thesrc\Microsoft.VisualBasic.Forms\src\Obsoletions.vbfile. - Annotate
srcfiles by referring to the constants defined inObsoletions.cs:- Specify the
UrlFormat = Obsoletions.SharedUrlFormat. - Example:c#
[Obsolete( Obsoletions.DomainUpDownAccessibleObjectMessage, DiagnosticId = Obsoletions.DomainUpDownAccessibleObjectDiagnosticId, UrlFormat = Obsoletions.SharedUrlFormat)] - If the
Obsoletionstype is not available in the C# project, link it into the project:xml<Compile Include="..\..\Common\src\Obsoletions.cs" Link="Common\Obsoletions.cs" />
- Specify the
- Create the new
aka.mslink- Point it to the repo's
.mdfile in thedocsfolder that describes your analyzer until thelearnsite docs are completed in the redirection manager - Set the security group to the team alias
- Point it to the repo's
- Apply the
:book: documentation: breakinglabel to the PR that introduces the obsoletion. - Document the breaking change
- In the breaking-change issue filed in dotnet/docs, specifically mention that this breaking change is an obsoletion with a
WFDEVdiagnostic ID. - Create another issue to add documentation to the
learnwebsite in the dotnet/docs-desktop repo - The documentation team will produce a PR that adds the warning or error documentation to the
learnsite page, and we will review it. - Once the docs PR is published, get the newly created link to the doc and associate it with the corresponding
aka.msname in the redirection manager - Connect with
@gewarren,@adegeo, or@merriemcgawwith any questions.
- In the breaking-change issue filed in dotnet/docs, specifically mention that this breaking change is an obsoletion with a
Obsoletion Diagnostics (`WFDEV001` - `WFDEV999`)
| Diagnostic ID | Introduced | Description |
|---|---|---|
WFDEV001 |
NET7.0 | (Internal) Casting to/from IntPtr is unsafe, use WParamInternal. |
WFDEV001 |
NET7.0 | (Internal) Casting to/from IntPtr is unsafe, use LParamInternal. |
WFDEV001 |
NET7.0 | (Internal) Casting to/from IntPtr is unsafe, use ResultInternal. |
WFDEV002 |
NET8.0 | DomainUpDown.DomainUpDownAccessibleObject is no longer used to provide accessible support for DomainUpDown controls. Use ControlAccessibleObject instead. |
WFDEV003 |
NET8.0 | DomainUpDown.DomainItemAccessibleObject is no longer used to provide accessible support for DomainUpDown items. |
WFDEV004 |
NET10.0 | Form.OnClosing, Form.OnClosed and the corresponding events are obsolete. Use Form.OnFormClosing, Form.OnFormClosed, Form.FormClosing and Form.FormClosed instead. |
WFDEV005 |
NET10.0 | Clipboard.GetData(string) method is obsolete. Use Clipboard.TryGetData<T> methods instead. |
WFDEV005 |
NET10.0 | DataObject.GetData methods are obsolete. Use the corresponding DataObject.TryGetData<T> instead. |
WFDEV005 |
NET10.0 | ClipboardProxy.GetData(As String) method is obsolete. Use ClipboardProxy.TryGetData(Of T)(As String, As T) instead. |
WFDEV006 |
NET10.0 | ContextMenu is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code. Use ContextMenuStrip instead. |
WFDEV006 |
NET10.0 | DataGrid is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code. Use DataGridView instead. |
WFDEV006 |
NET10.0 | MainMenu is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code. Use MenuStrip instead. |
WFDEV006 |
NET10.0 | Menu is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code. Use ToolStripDropDown and ToolStripDropDownMenu instead. |
WFDEV006 |
NET10.0 | StatusBar is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code. Use StatusStrip instead. |
WFDEV006 |
NET10.0 | ToolBar is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code. Use ToolStrip instead. |
Analyzer Diagnostics
When adding a new analyzer
- Add the diagnostic ID to the table below.
- The current IDs for C#, VB, and language-agnostic analyzers are defined in DiagnosticIDs.cs. A complete list of IDs, including those that were shipped and then replaced, is available in the
AnalyzerReleases.Shipped.mdandAnalyzerReleases.Unshipped.mdfiles. - Starting in NET9.0, we are using the
WFO####format for code analyzer diagnostic IDs.
- The current IDs for C#, VB, and language-agnostic analyzers are defined in DiagnosticIDs.cs. A complete list of IDs, including those that were shipped and then replaced, is available in the
- Create a new Descriptor in the analyzer code by calling DiagnosticDescriptorHelper.Create
- This method will generate the help link in the https://aka.ms/winforms-warnings/WFO#### format.
- Follow the documentation steps 5-7 described for obsoletions above.
| Diagnostic ID | Introduced | Removed | Description |
|---|---|---|---|
WFAC001 |
NET6.0 | NET9.0 | Unsupported project type. |
WFAC002 |
NET6.0 | NET9.0 | Unsupported property value. |
WFAC010 |
NET6.0 | NET9.0 | Unsupported high DPI configuration. |
WFO0001 |
NET9.0 | Only projects with OutputType=WindowsApplication supported. |
|
WFO0002 |
NET9.0 | ArgumentException: Project property '{0}' cannot be set to '{1}' | |
WFO0002 |
NET9.0 | ArgumentException: Project property '{0}' cannot be set to '{1}'. Reason: {2}. | |
WFO0003 |
NET9.0 | Remove high DPI settings from {0} and configure via Application.SetHighDpiMode API or '{1}' project property. | |
WFO0003 |
NET9.0 | Remove high DPI settings from {0} and configure via '{1}' property in Application Framework. | |
WFO1000 |
NET9.0 | Property '{0}' does not configure the code serialization for its property content. | |
WFO2001 |
NET9.0 | Task is being passed to InvokeAsync without a cancellation token |
|
WFO1001 |
NET10.0 | Type {0} does not implement ITypedDataObject interface |
Experimental Feature Compiler Errors
See Preview APIs - .NET | Microsoft Learn for more information.
Documentation for experimental features is available in the Experimental Help file.
- Add the diagnostic ID to the table below.
- The current IDs are defined in DiagnosticIDs.cs.
- Add an
[Experimental](https://learn.microsoft.com/dotnet/api/system.diagnostics.codeanalysis.experimentalattribute)attribute to your API.- Example:
c#[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)] - Follow the documentation steps 5-7 described for obsoletions above.
| Diagnostic ID | Introduced | Removed | Description |
|---|---|---|---|
WFO5001 |
NET9.0 | System.Windows.Forms.Application.SetColorMode(System.Windows.Forms.SystemColorMode) is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. |
|
WFO5001 |
NET9.0 | System.Windows.Forms.SystemColorMode is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. |
|
WFO5002 |
NET9.0 | System.Windows.Forms.Form.ShowAsync is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. |
|
WFO5003 |
NET10.0 | System.Windows.Forms.IAsyncDropTarget is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. |
Net11 Visualstyles Layout Guidance (`docs/net11-visualstyles-layout-guidance.md`)
.NET 11 VisualStyles layout guidance
VisualStylesMode.Net11 modernizes framework-defined control chrome without adding a theming API. Colors and
metrics come from Windows state, including dark mode, accent color, High Contrast, DPI, focus metrics, and text
scale. High Contrast always selects classic, backward-compatible painting throughEffectiveVisualStylesMode.
Modern chrome can require more adornment space than classic chrome. When a naturally sized control opts in, the
control grows so its client area remains usable. This is intentional: glyphs and focus indicators designed for
96 DPI are not sufficient at modern display densities, and controls must also react to system text-scale changes.
Choose an adaptive container
Use TableLayoutPanel or FlowLayoutPanel for controls whose preferred size can change. These containers already
handle localization, font changes, DPI changes, and AutoSize content, so they also handle modern visual-style
metrics.
Controls with intrinsic sizing, such as Button, single-line TextBox, ComboBox, and Label, update their
preferred size when their effective renderer changes. Controls with AutoSize enabled request layout
automatically.
Fixed-bounds exceptions
RichTextBox and multiline TextBox do not have intrinsic single-line or IntegralHeight behavior. Switching
their visual style does not resize their bounds. If the modern non-client frame needs more room, their client area
shrinks slightly within the existing bounds.
That behavior is deliberate. Size and align fixed-bounds editors programmatically for the surrounding workflow
instead of relying on a global pixel-perfect height.
Anchor when consuming size; Dock when pushing size
Use Anchor when a child consumes or aligns to space determined by its container. Use Dock when the child's
content should push the size of its container.
Inside an AutoSize TableLayoutPanel, anchor a control left and right when it should stretch to a column width
that the table computes:
TextBox customerName = new()
{
Anchor = AnchorStyles.Left | AnchorStyles.Right,
AutoSize = true
};
tableLayoutPanel.Controls.Add(customerName, column: 1, row: 0);Inside a constituent UserControl whose height should be driven by a single child, dock that child:
public sealed class SearchEditor : UserControl
{
private readonly TextBox _queryTextBox = new()
{
Dock = DockStyle.Top,
AutoSize = true
};
public SearchEditor()
{
AutoSize = true;
AutoSizeMode = AutoSizeMode.GrowAndShrink;
Controls.Add(_queryTextBox);
}
}Do not use DockStyle.Fill as a substitute for AutoSize. Fill consumes the bounds already assigned by the parent;
it does not tell an AutoSize parent what preferred height the child needs.
Equal-width OK and Cancel buttons
An AutoSize table with two 50 percent columns guarantees equal button widths. Each button is AutoSize and anchored
left and right, so both columns take the width required by the wider preferred button.
/* Detailed source-code truncated for AI context efficiency. */The pattern remains stable when localization, DPI, text scale, or a VisualStylesMode transition changes either
button's preferred width or height.
Align TextBox and ComboBox rows
Modern non-Simple ComboBox fields share their height metrics with an AutoSize, single-line modern TextBox usingBorderStyle.Fixed3D. ComboBox adds one logical pixel of internal style inset on top of its public Padding; apply
the same Padding to the TextBox when exact field-height parity is required. Put both in an AutoSize row, anchor
them left and right, and avoid hard-coded heights:
TableLayoutPanel editorTable = new()
{
AutoSize = true,
AutoSizeMode = AutoSizeMode.GrowAndShrink,
ColumnCount = 2,
RowCount = 2
};
editorTable.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
editorTable.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
editorTable.RowStyles.Add(new RowStyle(SizeType.AutoSize));
editorTable.RowStyles.Add(new RowStyle(SizeType.AutoSize));
Label nameLabel = new()
{
Anchor = AnchorStyles.Left,
AutoSize = true,
Text = "Name"
};
TextBox nameEditor = new()
{
Anchor = AnchorStyles.Left | AnchorStyles.Right,
AutoSize = true,
Padding = new Padding(1)
};
Label categoryLabel = new()
{
Anchor = AnchorStyles.Left,
AutoSize = true,
Text = "Category"
};
ComboBox categoryEditor = new()
{
Anchor = AnchorStyles.Left | AnchorStyles.Right,
DropDownStyle = ComboBoxStyle.DropDownList
};
editorTable.Controls.Add(nameLabel, 0, 0);
editorTable.Controls.Add(nameEditor, 1, 0);
editorTable.Controls.Add(categoryLabel, 0, 1);
editorTable.Controls.Add(categoryEditor, 1, 1);Do not derive a ComboBox height from ItemHeight. Item height controls list content; the modern selection field
uses TextBox-compatible padding and focus metrics. ComboBox Padding is designer-visible in .NET 11 and is
serialized only when it differs from Padding.Empty.
Account for GroupBox DisplayRectangle
In modern FlatStyle.Standard and FlatStyle.Popup, the GroupBox caption sits above a modern surface while
the DisplayRectangle preserves the classic etched-frame rectangle. FlatStyle.Popup uses a subtle
accent-tinted header band and accent border to distinguish the header without changing the content rectangle.
The caption aligns with the control Padding, and existing child layouts remain stable when switching visual
styles.
Use the GroupBox as a real layout container:
GroupBox addressGroup = new()
{
AutoSize = true,
AutoSizeMode = AutoSizeMode.GrowAndShrink,
Dock = DockStyle.Top,
FlatStyle = FlatStyle.Standard,
Text = "Address"
};
TableLayoutPanel addressTable = new()
{
AutoSize = true,
AutoSizeMode = AutoSizeMode.GrowAndShrink,
Dock = DockStyle.Top
};
addressGroup.Controls.Add(addressTable);Avoid positioning children outside DisplayRectangle; it remains the supported content boundary.
The modern caption is derived from the ambient font at paint time. WinForms preserves the ambient font family,
size, and style while still following system text scale.
React to live text-scale changes
Place controls in AutoSize rows and let the layout engine react to the system event:
TableLayoutPanel settingsTable = new()
{
AutoSize = true,
AutoSizeMode = AutoSizeMode.GrowAndShrink,
ColumnCount = 1,
RowCount = 2
};
settingsTable.RowStyles.Add(new RowStyle(SizeType.AutoSize));
settingsTable.RowStyles.Add(new RowStyle(SizeType.AutoSize));
GroupBox optionsGroup = new()
{
AutoSize = true,
AutoSizeMode = AutoSizeMode.GrowAndShrink,
Text = "Options"
};
ComboBox optionSelector = new()
{
Anchor = AnchorStyles.Left | AnchorStyles.Right,
DropDownStyle = ComboBoxStyle.DropDownList
};
settingsTable.Controls.Add(optionsGroup, 0, 0);
settingsTable.Controls.Add(optionSelector, 0, 1);Do not recreate controls when text scale changes. WinForms clears preferred-size caches, updates native selection
field metrics where necessary, and requests layout while preserving handles and selection.
Migration tooling
Automated migration tools can replace fixed pixel layouts with adaptive containers, identify fixed-bounds editor
exceptions, and review accessibility and text-scale behavior. Planned WinForms agent skills will cover High DPI
layout, accessibility, text-scale-adaptive layouts, and pixel-perfect-to-Fluent refactoring. Treat generated
changes as a starting point and verify each workflow at the application's supported DPI, language, and
accessibility settings.
Porting Guidelines (`docs/porting-guidelines.md`)
Port existing applications to .NET 5.0
We suggest doing migration in a separate branch or, if you're not using version
control, creating a copy of your project so you have a clean state to go back
to if necessary.
The migration process includes two steps: preparing your project for porting to
.NET and porting itself.
For additional information and assistance, we recommend checking out this article on the dotnet blog as well as the accompanying video tutorial.
Breaking changes
Review the following resources that describe breaking changes between Windows Forms on .NET Framework and .NET:
- The Breaking changes in Windows Forms (.NET Framework to .NET Core) describes breaking changes you need to be aware of when migrating your applications from .NET Framework
- The Breaking changes in Windows Forms (.NET Core to .NET) describes breaking changes you need to be aware of when migrating your applications from .NET Core 3.x versions to .NET 5.0 onwards.
Prepare your project for porting
Run .NET Portability Analyzer first to determine if there are
any APIs your application depends on that are missing in .NET. If there
are, you have a few options.- Remove not supported APIs or replace them with those, that are included in
.NET - Separate your code into different projects: the one that contains only
.NET supported APIs and another with APIs not supported in .NET.
Migrate only the first project.
- Remove not supported APIs or replace them with those, that are included in
Start from a working solution. Ensure the solution opens, builds, and runs without any issues.
Replace
packages.configwithPackageReference. If your project uses
NuGet packages, you will need to add the same NuGet packages to the new .NET
project. .NET projects support onlyPackageReferencefor adding
NuGet packages. To move your NuGet references frompackages.configto your
project file, right-click onpackages.config-> Migrate packages.config
to PackageReference....You can learn more about this migration in our docs.
Migrate to the SDK-style .csproj file. The new SDK-style
.csprojformat is leaner and easier to read. To be able to simply copy-paste your references from the old project to the new one, you first need to migrate your old project file to SDK-style so both project are in the same format. You can either do it by hand or use a third-party tool CsprojToVs2017.After using the tool you still might need to delete some reference by hand; for example:
xml<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="Microsoft.CSharp" /> <Reference Include="System.Net.Http" />After you've migrated to the new SDK-style format, ensure your project builds and runs successfully.
Configure assembly file generation. Most existing projects include an
AssemblyInfo.csfile in the Properties folder. The new project style uses a different approach and generates the same assembly attributes as part of the build process. As a result, you might end up with twoAssemblyInfo.csfiles and your build will fail. There are two ways to resolve this problem. You can either:Disable
AssemblyInfo.csgeneration on build by setting the property:xml<GenerateAssemblyInfo>false</GenerateAssemblyInfo>Move the static values from
AssemblyInfo.csto properties in the new.csprojfile.
Build and run to make sure you did not introduce any issues while preparing your project. Now it is time to port it.
Port your project
Automated porting
- Try porting your project using
try-converttool, which we built to help migrations.
NB: The tool may not work for all possible projects, in that case please read on.
Manual porting
Add .NET Windows Forms project. Add a new .NET 5.0 Windows Forms project to the solution.
**Add
**. Copy theelements from the.csprojfile of the original project to the new project's.csprojfile.
Note: The new project format does not use theNameandProjectGuidelements, so you can safely delete those.Restore/Build. At this point, it's a good idea to restore/build to make sure all dependencies are properly configured.
Link files. Link all files from your existing .NET Framework WinForms project to the .NET 5.0 WinForms project by adding following to the
.csprojfile.xml<ItemGroup> <Compile Include="..\<Your .NET Framework Project Name>\**\*.cs" /> <EmbeddedResource Include="..\<Your .NET Framework Project Name>\**\*.resx" /> </ItemGroup>Align default namespace and assembly name. Since you're linking to designer generated files (for example,
Resources.Designer.cs) you generally want to make sure that the .NET version of your application uses the same namespace and the same assembly name. Copy the following settings from your .NET Framework project:xml<PropertyGroup> <RootNamespace></RootNamespace> <AssemblyName></AssemblyName> </PropertyGroup>Run new project. Set your new .NET project as StartUp Project and run it. Make sure everything works.
Copy or leave linked. Now instead of linking the files, you can actually copy them from the old .NET Framework WinForms project to the new .NET 5.0 WinForms project. After that you can get rid of the old project.
Migration tips
Include the System.Windows.Forms.Datavisualization Pack
If you wish to use types previously associated with the Charting control in the .NET Framework, you should add a package reference to the NuGet package of Data Visualization ported to .NET. For more information about Data Visualization and the Charting control in .NET, including a sample application demonstrating its use, see the winforms-datavisualization repository
Include the Windows.Compatibility Pack
Windows applications like Windows Forms and WPF often use APIs that aren't referenced by default in .NET. The reason is that .NET tries to reduce the risk that new applications accidentally depend on legacy technologies or on APIs that are Windows-only. However, when porting existing Windows applications, neither of these two aspects is a concern. To simplify your porting efforts, you can simply reference the Windows Compatibility Pack, which will give
you access to many more APIs.
dotnet add package Microsoft.Windows.CompatibilityMigrating WCF clients
.NET has its own implementation of System.ServiceModel with some
differences:
- It is available as a set of NuGet packages (also included in the Windows
Compatibility Pack). - There are unsupported features that you should review.
- The binding and endpoint address must be specified in the service client constructor. Otherwise, if you reuse the
ServiceReferencecreated by Visual Studio, you may get the following error:
System.PlatformNotSupportedException: 'Configuration files are not supported.'Additional Types and Namespaces
You can search for additional types which you may need in porting your apps to .NET on .NET API Catalog. For example, when you search for the type System.AppDomain, you will see that the type has been moved to System.Runtime.Extensions namespace starting in .NET 2.0.
Release Activity (`docs/release-activity.md`)
Runtime release procedures
Milestone releases (dotnet/winforms + dotnet/windowsdesktop)
Merge any outstanding PRs, if necessary
Ensure the HEAD of the main branch builds successfully and all tests pass
Publish any unpublished API - run .\eng\ApiCompatibility\mark-shipped.cmd
Commit and wait for a successful build
(Milestone releases) Create "release/X.Y-qwe" branch (e.g. release/6.0-preview5) and push it into the main public trunk and the internal trunk.
Wait for both build successfully.
- Main public trunk: https://github.com/dotnet/winforms/
- Internal trunk:
Disable loc on "release/X.Y-qwe" branch (e.g.: https://github.com/dotnet/winforms/pull/6753)
Switch back to the main branch
Update the Service Fabric bot with the new release label in .\github\fabricbot.json. Refer to .\docs\infra\automation.md, if have questions.
Update the branding (e.g.: https://github.com/dotnet/winforms/commit/719b2112778ffd1e7ded559ea2736b4011428117)
Commit and wait for a successful build.
Create new milestone for the new release before updating following bot service.
Move work items to this new milestone if they were on previous milestone and close the previous milestone.
Update necessary reports with the progress
If you are responsible for dotnet/windowsdesktop repo, make sure you change the branding there too.
Servicing releases (dotnet/winforms + dotnet/windowsdesktop)
NET Servicing - Overview (azure.com)
Merge internal servicing branch (i.e. internal/internal/release/*) into the public branch, if necessary.
Update the branding (e.g.: https://github.com/dotnet/winforms/commit/480bd703c5af93b9e23dc7435c6029d685be7097)
Commit and wait for a successful build.
Ensure the HEAD of the servicing branch builds successfully and all tests pass
Update dotnet/installer with the released version (e.g.: https://github.com/dotnet/installer/commit/34ed90f436809631e254b9e15628cbcad7307629)
Create a new milestone.
Close the current milestone on the GitHub, ensuring all open issues/PRs are either closed or bumped to the following milestone
Merge any outstanding PRs, if necessary
Roadmap (`docs/roadmap.md`)
Windows Forms Roadmap
This roadmap communicates priorities for evolving and extending the scope of Windows Forms. For general information regarding .NET plans, see .NET roadmap.
This repository is a community effort and we welcome community feedback on our plans. The best way to give your feedback is to open an issue in this repo.
We also invite contributions. The help wanted issues on GitHub are a good place to start.
Future
Here is the list of improvements we are planning to work on in the future.
Accessibility improvements
Including support for standard WCAG2.1, such as enabling tooltips for controls on Tab (#2726), etc.
High DPI improvements
- Fix existing scaling bugs in Per Monitor DPI aware applications
- Enable all controls to support Per Monitor V2 mode
- Add a new "clean" way of calculating location/size information in PMA mode
Performance improvements
Establish a base line and improve runtime and designer performance.
Testing infrastructure
Add testing infrastructure and improve test coverage.
Some potential features
We also are gathering feedback regarding what other features you'd like to see in WinForms. Please let us know if any of those or something else would be useful for your applications. You an create a feature request in this repo or vote for an existing one.
Add and improve wrappers for missing Win32 controls
Such as Ribbon Control, Balloons, SearchBox, improvements around ListView, etc.
Add support for OS themes in WinForms applications
Enable dark theme, etc.
Present
Windows Forms runtime
Under considerations.A number of aspects are being considered including (but not limited to) layout engine and high DPI support, theming, new controls and components.
Windows Forms Designer
We continue the work on the designer to to achieve the parity with the .NET Framework designer.
This involves adding the remaining controls and features and improving stability and performance for the designer.
Runtime Configuration (`docs/runtime-configuration.md`)
Support for application runtime configuration in Windows Forms applications
Overview
.NET Windows Forms applications currently have limited application
configurations capabilities that are defined via MSBuils properties and are emitted into source code using source
generators at compile time. This document outlines expansion of those application-wide configurations further to cover runtime config options for .NET Windows Forms applications.
.NET Framework runtime configuration
.NET Framework Windows Forms applications use app.config to define runtime configurations and application-wide settings. The following are the various sections in the app.config that define the application's runtime behavior.
AppContext switches
These settings are used to opt-in or opt-out of a particular feature from Windows Forms runtime. Refer to the AppContext Switches documentation for more information.
<configuration>
<runtime>
<AppContextSwitchOverrides value="Switch.System.Globalization.NoAsyncCurrentCulture=true" />
</runtime>
</configuration>`System.Windows.Forms.ApplicationConfigurationSection` section
This was introduced in .NET Framework 4.7, and it is primarily used by Windows Forms runtime to enable high DPI and other accessibility improvements. Please refer to the ApplicationConfigurationSection documentation for more information.
<configuration>
<System.Windows.Forms.ApplicationConfigurationSection>
</System.Windows.Forms.ApplicationConfigurationSection>
</configuration>Application-wide and user-specific settings
These are commonly defined via the Settings designer in Visual Studio, which serializes those into app.config. Refer to the Application Settings documentation for more information.
<userSettings>
<WinFormsApp2.Properties.Settings>
<setting name="ButtonName" serializeAs="String">
<value>LocalButton</value>
</setting>
</WinFormsApp2.Properties.Settings>
</userSettings>
<applicationSettings>
<WinFormsApp2.Properties.Settings>
<setting name="BoxName" serializeAs="String">
<value>LocalBox</value>
</setting>
</WinFormsApp2.Properties.Settings>
</applicationSettings>.NET 7+ runtime configuration
app.config has limited support in .NET due to performance and reliability reasons. .NET runtime and other .NET teams use runtimeconfig.json to define .NET runtime configurations and appsettings.json to define application-level settings. In this proposal, we are leveraging runtimeconfig.json to define Windows Forms runtime configurations.
While this proposal is focusing on providing an alternative solution for existing configuration sections AppContextSwitchOverrides and System.Windows.Forms.ApplicationConfigurationSection that are primarily used for specifying feature flags impacting Windows Forms runtime behavior, we will be looking into alternatives for Application Settings that doesn't require app.config in the future releases of .NET.
Goals:
- A replacement for
AppContextSwitchOverridesandSystem.Windows.Forms.ApplicationConfigurationSectionofapp.config. - Users should be able to update/modify Windows Forms applications runtime configurations without recompiling the application.
- The existing applications should be able to seamlessly upgrade to the new configuration model when targeting .NET 7+ runtime.
- The existing application configuration MSBuild properties continue to work.
Out of scope:
- App settings that are serialized by the Settings designer/editor page. Applications should continue to use the current model.
- Dynamic/real-time (re-)loading of configuration values from
runtimeconfig.json. - Unification of comple/runtime configurations into one place, e.g.,
runtimeconfig.json.
Design proposal
Windows Forms switches will be added to runtimeConfig.template.json to the configProperties section with the rest of the .NET switches. To avoid name conflics Windows Forms specific switches will be prefixed with System.Windows.Forms:
{
"configProperties": {
"System.Globalization.UseNls": true,
"System.Net.DisableIPv6": true,
"System.GC.Concurrent": false,
"System.Threading.ThreadPool.MinThreads": 4,
"System.Threading.ThreadPool.MaxThreads": 25,
// Windows Forms specific switches
"System.Windows.Forms.ScaleTopLevelFormMinMaxSizeForDpi": true,
"System.Windows.Forms.CustomEnumProperty": "EnumValue"
}
}Reading Windows Forms runtime configurations
When a project is built, an [appname].runtimeconfig.json file is generated in the output directory. If a runtimeconfig.template.json file exists in the same folder as the project file, any configuration options it contains are inserted into the [appname].runtimeconfig.json file.
This proposal focuses primarily on enabling the runtime configurations for Windows Forms applications. A support for analyzers and source generators may be considered in the future .NET releases. We will revisit this implementation to improve user-experience further, as we make progress.
For example, the content of [appname].runtimeconfig.json generated from above content:
{
"runtimeOptions": {
"tfm": "net7.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "7.0.0-preview.7.22375.6"
},
{
"name": "Microsoft.WindowsDesktop.App",
"version": "7.0.0-preview.7.22377.1"
}
],
"configProperties": {
"System.Globalization.UseNls": true,
"System.Net.DisableIPv6": true,
"System.GC.Concurrent": false,
"System.Threading.ThreadPool.MinThreads": 4,
"System.Threading.ThreadPool.MaxThreads": 25,
"System.Windows.Forms.ScaleTopLevelFormMinMaxSizeForDpi": true,
"System.Windows.Forms.StringProperty": "string",
"System.Windows.Forms.CustomProperty": "CustomValue"
}
}
}Enabling modern rendering for composite controls
ToolStrip, MenuStrip, and DataGridView use Classic rendering when VisualStylesMode
is Net11 or Latest by default. Applications can opt in to modern rendering with these
switches:
{
"configProperties": {
"System.Windows.Forms.ToolStripModernRendering": true,
"System.Windows.Forms.DataGridViewModernRendering": true
}
}System.Windows.Forms.ToolStripModernRendering controls both ToolStrip and MenuStrip.
Both switches default to false.
The target framework information added to [appname].runtimeconfig.json file always match with the application target framework irrespective of runtime/SDK installed on the machine or runtime used (roll-forward scenarios) by the Windows Forms application.
.NET runtime support in reading `runtimeconfig.json`
The Windows Forms runtime is leveraging the support provided by .NET runtime in reading [appname].runtimeconfig.json file, and thus get all plumbing required for various hosting and environment scenarios. The Windows Forms runtime adds the following wrapper on top of .NET runtime implementation to improve the performance by caching configuration options, and also by defining custom defaults values for the Windows Forms specific feature flags. This wrapper is very similar to the .NET runtime's implementation.
The below example illustrates how to define ScaleTopLevelFormMinMaxSizeForDpi feature flag, opt-in by default, for an application targeting .NET 8 or later running on Windows 10:
/* Detailed source-code truncated for AI context efficiency. */The Windows Forms runtime then uses the static LocalAppContextSwitches class to access the runtime configurations. The below sample demonstrates the use of the feature switch System.Windows.Forms.ScaleTopLevelFormMinMaxSizeForDpi to scale min/max size of a container control:
/* Detailed source-code truncated for AI context efficiency. */Shared Wpf Infrastructure (`docs/shared-wpf-infrastructure.md`)
Shared WinForms/WPF Infrastructure
Overview
This document describes infrastructure components shared between WinForms (in the dotnet/winforms repository) and WPF (in the dotnet/wpf repository). Understanding this sharing is critical for contributors and maintainers when making changes to shared code, as modifications can impact both UI stacks.
Key takeaway: Changes to System.Private.Windows.Core and certain other shared assemblies in this repository affect both WinForms and WPF applications. Always consider cross-stack implications when modifying these components.
Which Components Are Shared
The primary shared component is System.Private.Windows.Core, which contains low-level Windows interop and OLE/COM support used by both WinForms and WPF. The most critical shared subsystem is the clipboard and OLE data handling implementation.
System.Private.Windows.Core
Location: src/System.Private.Windows.Core/
Key shared areas:
- OLE/Clipboard implementation:
src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.NativeToManagedAdapter.cs- Adapts nativeIDataObjectto managed interfaceComposition.ManagedToNativeAdapter.cs- Adapts managedIDataObjectto native COMDataObjectCore.cs- Core data object implementationClipboardCore.cs- Core clipboard operationsBinaryFormatUtilities.cs- Binary format handling for serialization- Related support classes for format conversion, drag-and-drop, and data transfer
Why this matters: These classes handle fundamental clipboard and drag-and-drop operations. Bugs in this code can manifest in both WinForms and WPF applications, and fixes here benefit both stacks.
How Sharing Works
The sharing mechanism involves packaging, transport, and ingestion into the Windows Desktop shared framework:
Packaging in dotnet/winforms:
- The
Microsoft.Private.Winformstransport package is built from this repository - This package bundles
System.Private.Windows.Coreand other shared assemblies - See:
pkg/Microsoft.Private.Winforms/README.md
- The
Ingestion by WPF:
- The
dotnet/wpfrepository consumes theMicrosoft.Private.WinformsNuGet package - Props and targets from
pkg/Microsoft.Private.Winforms/sdk/dotnet-wpf/are used by WPF's SDK - WPF builds integrate these assemblies into their artifact set
- The
Windows Desktop Shared Framework:
- Both WinForms and WPF assemblies are bundled into the
Microsoft.WindowsDesktop.Appshared framework - The
dotnet/windowsdesktoprepository orchestrates this bundling - Applications targeting Windows Desktop get a single unified set of assemblies at runtime
- Both WinForms and WPF assemblies are bundled into the
Result: At runtime, WinForms and WPF applications in the same process share a single instance of System.Private.Windows.Core.dll, ensuring consistent behavior for clipboard, OLE, and related operations.
Concrete Examples and Case Studies
Case Study 1: IStream Double-Release (WPF #11401, WinForms #14257/#14296)
Issue: When clipboard data was stored using TYMED_ISTREAM, the code would wrap the IStream pointer in a ComScope and then call ReleaseStgMedium. This caused a double-release: ComScope.Dispose() would release the stream, and then ReleaseStgMedium would release it again, leading to crashes or undefined behavior.
Root cause: Located in Composition.NativeToManagedAdapter.cs, method TryGetIStreamData<T>
Fix in PR #14257:
// Don't wrap in ComScope - ReleaseStgMedium will release the stream.
Com.IStream* pStream = (Com.IStream*)medium.hGlobal;Impact:
- WPF #11401: Reported as a WPF issue, but the bug was in shared code in dotnet/winforms
- WinForms #14257: Fixed in main branch
- WinForms #14296: Draft PR to backport the fix to release/10.0
- Outcome: Single fix in dotnet/winforms resolved the issue for both WinForms and WPF
Lesson: Bugs in shared clipboard/OLE code often surface in both stacks. When WPF reports a clipboard or data transfer issue, check System.Private.Windows.Core code in this repository.
Case Study 2: GetData Failure and Uninitialized STGMEDIUM (WPF #11402, WinForms #14257)
Issue: When clipboard content changed between QueryGetData (which checks availability) and GetData (which retrieves the data), GetData would fail but the code would still attempt to read from the uninitialized STGMEDIUM structure. This led to reading garbage data or crashing.
Root cause: Located in Composition.NativeToManagedAdapter.cs, method TryGetHGLOBALData<T>
Fix in PR #14257:
// If GetData failed, don't try to read from the medium - it may contain uninitialized data.
// (This can easily happen when the clipboard content changes between QueryGetData and GetData calls.)
if (hr.Failed)
{
return false;
}Impact:
- WPF #11402: Reported as a WPF clipboard reliability issue
- WinForms #14257: Fixed in main branch along with other hardening improvements
- Outcome: Better error handling and null returns instead of corrupted data, benefiting both stacks
Lesson: Race conditions and error handling in shared code require careful validation. Changes should anticipate contention scenarios common in clipboard operations.
Additional Improvements in PR #14257
- UTF-8 HGLOBAL hardening: Improved string reading to prevent malformed data from causing exceptions
- Test coverage: Added mocks and tests for STGMEDIUM handling scenarios
- Constants: Added
CLIPBRD_E_CANT_OPENand other clipboard-related error constants
Advantages of Sharing
- Consistency: Clipboard and OLE behavior is identical across WinForms and WPF applications
- Single fix benefits both: Bug fixes in shared code automatically improve both stacks
- Shared test coverage: Tests in dotnet/winforms validate behavior for WPF as well
- Reduced duplication: Avoids maintaining two separate implementations of complex OLE/COM interop
- Better quality: Concentrated development and review effort on a single implementation
Liabilities and Risks
- Coupling: Changes in dotnet/winforms can break WPF, and vice versa (though WPF changes rarely touch these components)
- Regressions affect both stacks: A bug introduced in shared code will impact both WinForms and WPF
- Validation requirements: Changes must be tested in both WinForms and WPF scenarios
- Discoverability: It may not be obvious to contributors that code changes impact WPF
Guidance for Contributors
When Working on Shared Code
Which code is shared?
- Primarily
src/System.Private.Windows.Core/, especially theOle/directory - If in doubt, check if the code is in
System.Private.Windows.Coreor referenced inpkg/Microsoft.Private.Winforms/
Before making changes:
- Consider WPF impact: Ask yourself: "Could this change affect WPF clipboard, drag-and-drop, or data transfer?"
- Search for related WPF issues: Check the dotnet/wpf issue tracker for similar problems
- Review existing tests: Look at test coverage in
src/System.Private.Windows.Core.Tests/andsrc/System.Windows.Forms.Tests/for clipboard/OLE scenarios
When making changes:
- Prefer small, targeted fixes: Especially for release branches (e.g.,
release/10.0), minimize scope to reduce risk - Larger refactorings belong in main: Structural changes and broad refactorings should target the
mainbranch - Add or update tests: Ensure your changes have test coverage, including error handling and edge cases
- Add cross-repo references: If your PR addresses a WPF issue, link it in the PR description:text
Fixes dotnet/wpf#11401 Relates to dotnet/wpf#11402 - Document breaking changes: If behavior changes in observable ways, call it out explicitly
Validation checklist:
- Run WinForms tests:
build.cmd -test - Test clipboard operations manually:
- Copy/paste text between applications
- Copy/paste images
- Try custom data formats if your change affects them
- Test drag-and-drop scenarios
- If possible, validate with a WPF application
- WPF development follows the same patterns (
build.cmd,start-vs.cmd, etc.) - Consider testing via manual insertion of the updated shared DLL or repointing to a locally built transport package
- WPF development follows the same patterns (
- Check for memory leaks or COM reference counting issues in loops or stress scenarios
Code review considerations:
- Ensure error handling is robust (null checks, HRESULT validation, etc.)
- Look for potential double-release or use-after-free patterns in COM/OLE code
- Verify that
ComScopedisposal is correct and that different release mechanisms don't overlap - Consider thread safety and race conditions (clipboard content can change at any time)
- Look for changes in state return (different values, exceptions). Changes must be carefully considered and have strong justification to minimize breaking changes.
- Document all breaking changes.
- OLE / clipboard state is shared machine wide. It is expected that state can change at any time. Handle state change gracefully.
- Avoid second guessing application intent and shared OLE state. If the data is not retrieved successfully just accept the result, don't retry. Existing retry logic can be kept- if API users want retry logic they should either implement their own wrappers or present new API requests where intent can be made explicit.
Backporting to Release Branches
When backporting clipboard/OLE fixes to release branches (e.g., for WPF servicing):
- Minimize risk: Only backport critical bug fixes, not refactorings or enhancements
- Preserve behavior: Avoid changing API signatures or introducing observable behavioral changes
- Test thoroughly: Release branches require extra validation since they're servicing supported releases
- Coordinate with WPF team: Reach out on GitHub or in discussions if the issue originated in WPF
Related Open Issues
The following issues track ongoing work and awareness in this area:
Additional Resources
- Packaging and transport:
pkg/Microsoft.Private.Winforms/README.md - Clipboard/DataObject API changes:
docs/dataobject-serialization.md - Contributing to WinForms:
CONTRIBUTING.md - Issue guidelines:
docs/issue-guide.md - WPF repository: https://github.com/dotnet/wpf
- Windows Desktop repository: https://github.com/dotnet/windowsdesktop
Summary
Shared infrastructure between WinForms and WPF provides significant benefits but requires awareness and care when making changes. Contributors should:
- Recognize when they're working on shared code (especially
System.Private.Windows.Core) - Consider the impact on both UI stacks
- Add appropriate test coverage
- Link related WPF issues in PRs
- Validate changes in both WinForms and WPF scenarios when possible
- Prefer small, targeted changes for release branches
By following these guidelines, we can maintain high-quality, reliable clipboard and OLE functionality for both WinForms and WPF applications.
Testing In Vs (`docs/testing-in-vs.md`)
Windows Forms Testing in Visual Studio
Visual Studio executes unit tests differently from the command line. This page describes some of those differences and covers common problems and workarounds.
Assert.Same() failures
Problem
Assert.Same() calls that pass when running tests from the command line can often fail when running through Visual Studio. This has to do with a quirk in the xUnit Visual Studio test adapter.
Cause
- When using
[Theory]combined with[InlineData],[ClassData], or[MemberData], the xUnit VS test adapter does performs "discovery enumeration", before the test is actually executed- It does this to populate the Test Explorer window with all the test cases before they run
- The process running this enumeration executes in a different App domain than the tests themselves
- This means objects passed in to the test methods have different references than expected (when comparing strings, for example)
Solution(s)
There are two ways to get around this problem:
Use a custom *DataAttribute (inheriting from the appropriate *DataAttributeBase class) and set
DisableDiscoveryEnumeration = truein the constructor- For an example of this, see MauiDataAttribute.cs
- This is not really preferred, and should only be used when your test data can't be resolved at compile-time
OR
Use Assert.Equal() when comparing against objects created by
[InlineData],[ClassData], or[MemberData]- You can still use Assert.Same() in all other cases
Tests don't execute
Problem
- When launching tests through Visual Studio, no tests execute
- Click on
View -> Outputand selectTestsfrom theShow output from:dropdown - You might see an error that looks like
Testhost process exited with error: It was not possible to find any compatible framework version. The specified framework 'Microsoft.NETCore.App', version 'XXX' was not found
Cause (long)
- The .net core SDK and the .net core runtime (Microsoft.NetCore.App) are two different things.
- The SDK targets a specific NetCore.App. This version is automatically downloaded into your local .dotnet folder when you build from command line, and is also installed when you install a dotnet sdk machine-wide.
- If you donβt specify an explicit NetCore.App version, you will use the one that the SDK targets.
- If youβre interested in seeing what this version is, look at
<yourRepoRoot>\.dotnet\sdk\<yourSdkVersion>\dotnet.runtimeconfig.json
- The winforms (runtime) repo DOES specify an explicit NetCore.App version, and we use the one published by the core-setup repo.
- If you look at Directory.Build.Targets, you can see where this is set
$(VSRedistCommonNetCoreSharedFrameworkx6490PackageVersion)is set in Versions.props, and gets automatically updated when we ingest dependencies from core-setup. (These PR's are automatically merged in if the build passes)- You can see where the dependency comes from by looking at Version.Details.xml
- The reason we use the version from core-setup instead of the version built-in to the SDK is we donβt want to wait for the runtime to come all the way through the SDK repo to consume it
- Running tests from the command line (.\build -test) works because the build scripts automatically download both the sdk AND the NetCore.App that you need AND add the .dotnet folder to your PATH when running.
- You can verify this by checking the .dotnet folder in your repo root. The NetCore.App is in the shared folder.
- However, Visual Studio doesnβt care about your local .dotnet folder. It always looks at your machine-wide installs (unless you manually add things to your PATH before launching VS)
- If you donβt have the required SDK installed machine-wide, your code wonβt build (through VS).
- If you donβt have the required NetCore.App installed machine-wide, your code wonβt run (through VS).
- This is why unit tests can fail immediately through VS.
- Note that just installing the SDK IS NOT ENOUGH if you are using an explicit NetCore.App version.
- Since the command line builds automatically download everything you need, we just have to tell VS where this stuff lives
Solution(s)
There are two ways to get around this problem:
Add your local
<repoRoot>\.dotnetfolder to the front of your PATH variable- This is not preferred because Visual Studio will always look here first, and can break other things, like if you need to build against a higher version of the SDK
OR
Close VS, run
<repoRoot>\build-local.ps1, then re-open VS- This will do a restore (to download the stuff), do a build, then symlink the stuff you need from your local .dotnet folder to your machine-wide SDK install folder
(C:\Program Files\dotnet\...). Then VS will know about it too π
- This will do a restore (to download the stuff), do a build, then symlink the stuff you need from your local .dotnet folder to your machine-wide SDK install folder
Testing (`docs/testing.md`)
Windows Forms Testing
This document describes our approach to testing.
- Building tests
- Unit Tests
- Rendering Tests
- Functional Tests
- Sequential collection
- Testing for Accessibility
- Running and debugging crashed tests
Building tests
Tests are automatically built when running .\build since all test projects are referenced in Winforms.sln at the repository root.
Unit Tests
Running unit tests
Unit testing from the command line
To execute unit tests, run .\build -test
If all the tests are successful, you should see something like this:
Running tests: E:\src\repos\github\winforms\artifacts\bin\System.Windows.Forms.Tests\Debug\net6.0\System.Windows.Forms.Tests.dll [net6.0|x64]
Tests succeeded: E:\src\repos\github\winforms\artifacts\bin\System.Windows.Forms.Tests\Debug\net6.0\System.Windows.Forms.Tests.dll [net6.0|x64]
Build succeeded.
0 Warning(s)
0 Error(s)Troubleshooting command-line unit test errors
When testing from the command line, a failed test should look something like this:
Running tests: E:\src\repos\github\winforms\artifacts\bin\System.Windows.Forms.Tests\Debug\net6.0\System.Windows.Forms.Tests.dll [net6.0|x64]
XUnit : error : Tests failed: E:\src\repos\github\winforms\artifacts\TestResults\Debug\System.Windows.Forms.Tests_net6.0_x64.html [net6.0|x64] [E:\src\repos\github\winforms\src\System.Windows.Forms\tests\UnitTests\System.Windows.Forms.Tests.csproj]
XUnit : error : Tests failed: E:\src\repos\github\winforms\artifacts\TestResults\Debug\System.Windows.Forms.Tests_net6.0_x64.html [net6.0|x64] [E:\src\repos\github\winforms\src\System.Windows.Forms\tests\UnitTests\System.Windows.Forms.Tests.csproj]
Build FAILED.- The test summary can be found under artifacts\log
- To see the actual test(s) that failed, along with their error message(s), open the .html file that is displayed in the error message (which is always under
artifacts\TestResults)
Unit testing from Visual Studio
To test from Visual Studio, start Visual Studio via .\start-vs.cmd, and test how you normally would (using the Test Explorer, for example)
Troubleshooting Visual Studio unit test errors
- When testing from Visual Studio, test errors show up as normal in the Test Explorer.
- To troubleshoot, debug the selected test and set breakpoints as you normally would.
- For common issues when running tests through Visual Studio, see Testing in Visual Studio
Adding new unit tests
Tests are built and executed by file name convention
- Every WinForms binary has its own folder under src in the repository root (src\System.Windows.Forms, for example)
- Each of those folders has a tests folder under it (src\System.Windows.Forms\tests, for example)
- Each tests folder contains an xUnit test project (System.Windows.Forms.Tests.csproj)
- These test projects automatically build when running
.\build - The tests from these projects automatically execute when running
.\build -test.
It is also possible to execute individual tests viadotnet test --filter <filter expression>command by switching to the desired test project directory first.
- These test projects automatically build when running
Therefore, you just need to put your tests in the right place in order for them to run
- Browse to the tests folder for the binary you are testing.
- There should be one file per class being tested, and the file name should match the class name followed by a "Tests" suffix.
- For example, if I wanted to test the
Buttonclass in System.Windows.Forms.dll, I would look for a ButtonTests.cs under src\System.Windows.Forms\tests. - For example, if I wanted to test the
Button.ButtonAccessibleObjectclass in System.Windows.Forms.dll, I would look for a Button.ButtonAccessibleObjectTests.cs under src\System.Windows.Forms\tests.
- For example, if I wanted to test the
- If the file exists, add your tests there. If it doesn't exist, feel free to create it.
- Note that you don't have to modify the csproj at all. Since the project is a Microsoft.NET.Sdk project, all source files next to it are automatically included.
Unit Test best practices
Naming
- Test files names should match the class they are testing followed by a "Tests" suffix.
- For example, tests for the
Buttonclass should be in ButtonTests.cs. - For example, tests for the
Button.ButtonAccessibleObjectclass should be in Button.ButtonAccessibleObjectTests.cs.
- For example, tests for the
- Test names should start with the class they are testing.
- For example, all tests for the
Buttonclass should start with "Button".
- For example, all tests for the
- Test names should end with a description of what the test does - this is very useful when viewing test results, and when browsing in the test explorer. As far as naming conventions are concerned we don't mandate a specific one, as long as a test name clearly communicates its purpose.
- For example,
Button_AutoSizeModeGetSetorMyButton_Click_should_throw_ArgumentNullException.
- For example,
Decoration
- All tests that deal with UI controls or types that require synchronization context must be decorated with
WinFormsFactorWinFormsTheoryattributes. - Other tests can be decorated with
StaFact,StaTheory,FactorTheory
Dispose created objects
- All tests creating disposable objects (e.g. UI controls) must dispose them. Otherwise it could lead to memory leaks (bad but not terminal) and race conditions that could lead to deadlocked tests (terminal).cs
[WinFormsFact] public void ButtonBase_GetAutoSizeMode_Invoke_ReturnsExpected() { using SubButtonBase control = new(); Assert.Equal(AutoSizeMode.GrowOnly, control.GetAutoSizeMode()); }
Theory tests
Quite often there may be multiple tests that test exactly the same functionality but with different input parameters (e.g. null, "", " " for a string argument).
In such cases instead of creating multiple tests it is preferred to have a theory test, which is in another words a data-driven test.
When writing theories note the following:
theory test must be correctly decorated
theories must avoid creating UI controls. E.g. instead of writing:
cspublic static IEnumerable<object[]> GetButton_TestData() { yield return new object[] { new Button() }; yield return new object[] { new Button() { Text = "bla" } }; } [WinFormsTheory] [MemberData(nameof(GetButton_TestData))] public void Ctor_Control_DataGridViewCellStyle(Button button) { ... }prefer the following:
cspublic static IEnumerable<object[]> GetButton_TestData() { yield return new object[] { string.Empty }; yield return new object[] { "bla" }; } [WinFormsTheory] [MemberData(nameof(GetButton_TestData))] public void Ctor_Control_DataGridViewCellStyle(string buttonText) { using Button button = new() { Text = buttonText }; ... }theories must not reuse disposable components.
In situations where following the above recommendation could be impractical, it is maybe acceptable to create disposable controls for each theory data, e.g.:cspublic static IEnumerable<object[]> GetButton_TestData() { yield return new object[] { new Button(), new DataGridViewCellStyle() }; yield return new object[] { new Button() { Text = "bla" }, new DataGridViewCellStyle() }; }xUnit tries its best to dispose of disposable objects.
However objects must not be shared across theories, as it could lead to unknown state, e.g.cs// ** DO NOT DO THIS! ** public static IEnumerable<object[]> GetButton_TestData() { Button button = new(); yield return new object[] { button, new DataGridViewCellStyle() }; yield return new object[] { button { Text = "bla" }, new DataGridViewCellStyle() }; // the button could already be disposed by the time this theory runs }
Also be mindful of VS-specific behaviours: https://xunit.net/faq/theory-data-stability-in-vs
Strategy
Unit tests should be part of the same PR as code changes
- Unit tests must be added for any change to public APIs.
- We will accept unit tests for internal/private methods as well. Some non-public API can be accessed directly (e.g.
internal), some via subclassing (e.g.virtual) or via the public surface. However there are plenty of instances where a non-public API can't be easily accessed or arranged for. In this cases we useTestAccessorpattern to arrange, act and assert.
Code Coverage
- In Visual Studio Test Explorer, select all tests, right click and execute 'Analyze code coverage for selected tests' command. This will run all tests and give a summary of blocks covered in 'Code Coverage Results' window. The summary can be drilled down to method level.
- Any code change accompanied with unit tests is expected to increase code coverage for the code modified.
Avoid duplicating tests just for different inputs
- Use
WinFormsTheory,StaTheoryorTheoryattributes for this, followed by either[InlineData]or[MemberData]. See existing tests for examples on how to use these attributes - The exception to this is if the code behavior is fundamentally different based on the inputs. For example, if a method throws an
ArgumentExceptionfor invalid inputs, that should be a separate test.
One test (or test data) per code path please
- The most common exception to this is when testing a property, most people test get/set together
Whenever possible, mock up dependencies to run tests in isolation
- For example, if your method accepts an abstraction, use Moq to mock it up
- Search for Mock in the existing tests for examples, and see Moq for details on how to use Moq.
Rendering Tests
We use Enhance Metafiles (or EMF for short) to validate rendering operations, i.e. assert that correct shapes were drawn with expected colours and brushes.
A typical "rendering" assertion test would looks something like this:
[WinFormsFact]
public void MyControl_Rendering()
{
// 1. Create a control to validate rendering for.
// 2. Add the control to a form, and make sure the form is created
using Form form = new Form();
using MyControl control = new() { ... };
form.Controls.Add(control);
Assert.NotEqual(IntPtr.Zero, form.Handle);
// Create an Enhance Metafile into which we will render the control
using EmfScope emf = new();
DeviceContextState state = new DeviceContextState(emf);
// Render the control
control.PrintToMetafile(emf);
// We can see the rendering steps by invoking this command:
// string records = emf.RecordsToString();
// Assert the rendering was as expected
emf.Validate(
state,
...
);
}Functional Tests
:warning: There is a very blurry line between unit and functional tests in Windows Forms realm. A lot of our implementations depend on ambient contexts (such as Win32, COM, etc.). We classify tests as "functional" or "integration" that require process-wide settings (such as visual styles) or require user-like interactions (e.g. mouse gestures).
The general purpose functional test suite is the WinFormsControlsTest. There is an xUnit project that executes various commands against this binary.
Running functional tests
Functional testing from the command line
To execute functional tests, run .\build -integrationTest
You will see various windows open and close very quickly. If all the tests are successful, you should see something like this:
Running tests: E:\src\repos\github\winforms\artifacts\bin\System.Windows.Forms.IntegrationTests\Debug\net6.0\System.Windows.Forms.IntegrationTests.dll [net6.0|x64]
Tests succeeded: E:\src\repos\github\winforms\artifacts\bin\System.Windows.Forms.IntegrationTests\Debug\net6.0\System.Windows.Forms.IntegrationTests.dll [net6.0|x64]
Build succeeded.
0 Warning(s)
0 Error(s)Troubleshooting command-line functional test errors
Since these run in xUnit, functional test errors can be examined in the same way as unit test failures.
Functional testing from Visual Studio
To test from Visual Studio, open Winforms.sln in Visual Studio and test how you normally would (using the Test Explorer, for example)
Troubleshooting Visual Studio functional test errors
- When testing from Visual Studio, test errors show up as normal in the test explorer.
- To troubleshoot, debug the selected test and set breakpoints as you normally would.
Adding new functional tests
Functional tests are built and executed by file name convention
- Every WinForms binary has its own folder under src in the repository root (src\System.Windows.Forms, for example)
- Each of those folders has a tests folder under it (src\System.Windows.Forms\tests, for example), each of which may contain an IntegrationTests folder
- Each of these folders contains an IntegrationTest xUnit project (System.Windows.Forms.IntegrationTests.csproj)
- These test projects automatically build when running
.\build - The tests from these projects automatically execute when running
.\build -integrationTest
- These test projects automatically build when running
Therefore, you just need to put your tests in the right place in order for them to run
- Browse to the tests folder for the binary you are testing
- There should be one file per class being tested, and the file name should match the class name.
- For example, if I wanted to test the
Buttonclass in System.Windows.Forms.dll, I would look for a Button.cs under src\System.Windows.Forms\tests
- For example, if I wanted to test the
- If the file exists, add your tests there. If it doesn't exist, feel free to create it.
- Note that you don't have to modify the csproj at all. Since the project is a Microsoft.NET.Sdk project, all source files next to it are automatically included
Sequential Collection
A sequential collection is a grouping of unit tests that are executed in a specific order.
- All unit tests in the
Sequentialcollection are executed sequentially.
- All unit tests in the
Unit tests that involve the following situations should be included in the
Sequentialcollection:Clipboard operations is sensitive to the state of the system and interfere with each other if run in parallel.
- Clipboard APIs: Any test that interacts with the system Clipboard, such as setting or retrieving data.
- Drag and Drop: Tests that simulate drag-and-drop operations, which often involve the Clipboard.
- Copy/Paste: Tests that perform copy and paste actions, which rely on the Clipboard to transfer data.
- Register Formats: Tests that register custom Clipboard formats.
E.g.
cs[Collection("Sequential")] public class ClipboardTests { [WinFormsFact] public void RichTextBox_OleObject_IncompleteOleObject_DoNothing() { using RichTextBox control = new(); control.Handle.Should().NotBe(IntPtr.Zero); using MemoryStream memoryStream = new(); using Bitmap bitmap = new(100, 100); bitmap.Save(memoryStream, Drawing.Imaging.ImageFormat.Png); Clipboard.SetData("Embed Source", memoryStream); control.Text.Should().BeEmpty(); }Other tests that rely on a global state, refer to existing tests
Testing for Accessibility
Our goal is to make writing accessible WinForms applications easy. Specifically, all default property values should yield accessible experience. To test that controls are accessible, find or add the changed control to AccessibilityTests application and run Accessibility Insights for Windows on it.
Running and debugging crashed tests
At times tests may fail in way that makes it difficult to debug (e.g., the test runner process crashes). In this case, it's good to replicate the issue locally and collect a memory dump to have a better understanding whether the issue is caused by the change in Windows Forms SDK, or, for example, by the .NET runtime.
- Configure your workstation to automatically collect memory dumps following Collecting User-Mode Dumps guide guide.
- Run
.\start-code.cmd - In the VS Code terminal run the following commands to run the tests:text
.\restore.cmd pushd .\src\System.Windows.Forms\tests\UnitTests dotnet test - Upon the test process crash, navigate to the collected memory dump and inspect it in WinDbg (or MSFT internal) (or another tool of your choice).
βIf you need to debug an x86-related issue, run tests with -a x86 argument, e.g., dotnet test -a x86, and then use the 32-bit version of WinDbg to open the dump.
Accessibility Accessible Role Controltype (`docs/accessibility/accessible-role-controltype.md`)
Consistency of AccessibleRole and "ControlType" for accessible objects
This document describes a behavior and relations of AccessibleRole property of
controls and
ToolStrip items
and "ControlType" accessibility property.
- Overview
- AccessibleRole-"ControlType" mapping
- Default "ControlType" values
- Exceptional cases and additional info
Overview
All accessible objects have "ControlType" (a part of modern UIA) and "LegacyIAccessible.Role"
(further "Role" - a part of outdated MSAA) accessibility properties.

Accessibility tools (Inspect,
Narrator,
etc.) request Role property of accessible objects to
get "Role" accessibility property value. Role property value of
ControlAccessibleObject,
ToolStripItemAccessibleObject,
and their descendants can be set by the developer using public AccessibleRole property.

Accessibility tools call internal GetPropertyValue method with ControlTypePropertyId
argument to get "ControlType" value.

.NET Framework 4.7.2 implements the following AccessibleRole property and"ControlType" accessibility property relations:
when Control's or ToolStripItem's AccessibleRole property is set to a non-default
value by the developer, the native accessibility proxy implementation would
set "ControlType" property to a matching value.
.NET Framework 4.8, .NET Core 3.0 and 3.1, .NET 5.0 lost
this behavior for all controls that implement UIA providers because
they provide custom implementation of UIA properties and no longer rely
on UIA proxy to match roles to control types. .NET 6.0 restores the .NET Framework 4.7.2 behavior.
AccessibleRole-"ControlType" mapping
When a developer changes AccessibleRole property value of a control
or a ToolStrip item (for example: control.AccessibleRole = AccessibleRole.List;), "ControlType" accessibility
property value of the corresponding accessible object will
be changed according to the following list (except some cases described below):
| AccessibleRole | ControlType |
|---|---|
| Alert | Pane |
| Animation | Pane |
| Application | Window |
| Border | Pane |
| ButtonDropDown | SplitButton |
| ButtonDropDownGrid | Button |
| ButtonMenu | MenuItem |
| Caret | Pane |
| Cell | DataItem |
| Character | Pane |
| Chart | Pane |
| CheckButton | CheckBox |
| Client | Pane |
| Clock | Button |
| Column | Pane |
| ColumnHeader | Header |
| ComboBox | ComboBox |
| Cursor | Pane |
| Default | Pane |
| Diagram | Pane |
| Dial | Pane |
| Dialog | Pane |
| Document | Document |
| DropList | Pane |
| Equation | Pane |
| Graphic | Image |
| Grip | Thumb |
| Grouping | Group |
| HelpBalloon | Pane |
| HotkeyField | Pane |
| Indicator | Thumb |
| IpAddress | Pane |
| Link | Hyperlink |
| List | List |
| ListItem | ListItem |
| MenuBar | MenuBar |
| MenuItem | MenuItem |
| MenuPopup | Menu |
| None | Pane |
| Outline | Tree |
| OutlineButton | Pane |
| OutlineItem | TreeItem |
| PageTab | TabItem |
| PageTabList | Tab |
| Pane | Pane |
| ProgressBar | ProgressBar |
| PropertyPage | Pane |
| PushButton | Button |
| RadioButton | RadioButton |
| Row | Pane |
| RowHeader | Header |
| ScrollBar | ScrollBar |
| Separator | Separator |
| Slider | Slider |
| Sound | Pane |
| SpinButton | Spinner |
| SplitButton | SplitButton |
| StaticText | Text |
| StatusBar | StatusBar |
| Table | Table |
| Text | Edit |
| TitleBar | TitleBar |
| ToolBar | ToolBar |
| ToolTip | ToolTip |
| WhiteSpace | Pane |
| Window | Window |
Default "ControlType" values
Here is the list of common controls and ToolStrip items with
their accessible objects "ControlType" accessibility property values
by default - when AccessibleRole property value of these controls
and ToolStrip items is not set:
Control control = new Control();
// control.AccessibleRole = AccessibleRole.List; - Role is not setor equals AccessibleRole.Default:
Control control = new Control();
control.AccessibleRole = AccessibleRole.Default; // Role is set to the default value| Control or item | Default ControlType |
|---|---|
| Button | Button |
| CheckBox | CheckBox |
| CheckedListBox | List |
| ComboBox | ComboBox |
| ContextMenuStripβ ToolStripMenuItemββ΄ ToolStripMenuItemββ΄ ToolStripComboBoxββ΄ ToolStripTextBoxββ΄ ToolStripSeparatorβ ToolStripComboBoxβ ToolStripTextBoxβ ToolStripSeparator | MenuMenuItemPaneComboBoxEditSeparatorComboBoxEditSeparator |
| DataGridView β DataGridViewEditingPanel | Table Pane |
| DateTimePicker | ComboBox |
| DomainUpDownβ UpDownButtonsβ UpDownEdit | SpinnerSpinnerEdit |
| FlowLayoutPanel | Pane |
| Form | Window |
| GroupBox | Group |
| HScrollBar | ScrollBar |
| Label | Text |
| LinkLabel | Text |
| ListBox | List |
| ListView | List |
| MenuStripβ ToolStripMenuItemββ΄ ToolStripMenuItemββ΄ ToolStripComboBoxββ΄ ToolStripTextBoxββ΄ ToolStripSeparatorβ ToolStripComboBoxβ ToolStripTextBox | MenuBarMenuItemMenuItemComboBoxEditSeparatorComboBoxEdit |
| MaskedTextBox | Edit |
| MonthCalendar | Calendar/Table |
| NumericUpDownβ UpDownButtonsβ UpDownEdit | SpinnerSpinnerEdit |
| PictureBox | Pane |
| ProgressBar | ProgressBar |
| PropertyGrid | Pane |
| RadioButton | RadioButton |
| RichTextBox | Document |
| ScrollBar | ScrollBar |
| SplitContainer | Pane |
| Splitter | Pane |
| StatusStripβ ToolStripStatusLabelβ ToolStripProgressBarβ ToolStripSplitButtonβ ToolStripDropDownButtonββ΄ ToolStripMenuItemββ΄ ToolStripComboBoxββ΄ ToolStripTextBoxββ΄ ToolStripSeparator | StatusBarText/LinkProgressBarButtonButtonMenuItemComboBoxEditSeparator |
| TabControl | Tab |
| TableLayoutPanel | Pane |
| TabPage | Pane |
| TextBox | Edit |
| TextBoxBase | Edit |
| ToolStripβ ToolStripItemβ ToolStripComboBoxβ ToolStripDropDownβ ToolStripNumericUpDownβ ToolStripOverflowβ ToolStripProgressBarβ ToolStripTextBoxβ ToolStripButtonβ ToolStripLabelβ ToolStripSplitButtonβ ToolStripDropDownButtonβ ToolStripSeparatorβ ToolStripOverflowButton | ToolBarButtonComboBoxMenuSpinnerMenuProgressBarEditButtonText/LinkButtonButtonSeparatorMenuItem |
| TrackBar | Slider |
| TreeView | Tree |
| VScrollBar | ScrollBar |
| TrackBar | Slider |
| TreeView | Tree |
| VScrollBar | ScrollBar |
Exceptional cases and additional info
Here is the list of controls and
ToolStripitems that didn't change
their accessible objects"ControlType"values depending onRolein .NET Framework 4.7.2
but in in .NET 6.0 they can do it correctly:ListView-"ControlType"was alwaysListToolStripMenuItem-"ControlType"was alwaysMenu itemToolStripSplitButton-"ControlType"was alwaysButtonToolStripDropDownButton-"ControlType"was alwaysButton
RichTextBox: at this moment, we don't provide a managed accessible object for it.
It doesn't support setting a customAccessibleRoleand
changing"ControlType"respectively (in .NET Framework 4.7.2 too).
It always has"Role"asDocumentand"ControlType"asDocument.TreeView: at this moment, we don't provide a managed accessible object for it.
It doesn't support setting a customAccessibleRoleand
changing"ControlType"respectively (in .NET Framework 4.7.2 too).
It always has"Role"value asOutlineand"ControlType"value asTree.DateTimePicker: its accessible object has non-standard"LocalizedControlType"accessibility property value by default.
But it changes if to changeAccessibleRoleproperty value of the control.Form: default"Role"value isClientbut default"ControlType"value
isWindow. This is an exception and does not correspond to the list above.
In the rest,Formwith a customAccessibleRolevalue works as expected.MonthCalendar: ifAccessibleNameproperty value is null or empty
default"ControlType"of its accessible object isCalendar,
ifAccessibleNameis a some text - default"ControlType"isTable.ToolStripLabel: ifIsLinkproperty value equalsfalse
its accessible object default"ControlType"value isText,
and"ControlType"isHyperlinkifIsLinkistrue.ToolStripStatusLabel: ifIsLinkproperty value equalsfalse
its accessible object default"ControlType"value isText,
and"ControlType"isHyperlinkifIsLinkistrue.ToolStripNumericUpDown: this item is used in PrintPreviewDialog control.ToolStripOverflowButton: needs to use ToolStrip.OverflowButton
property to get this item and set a customAccessibleRolevalue.DataGridViewEditingPanel: default"Role"isClient.
Needs to use DataGridView.EditingPanel to get the control and set
a custom value for itsAccessibleRole.
Accessibility Listview Expected Behavior (`docs/accessibility/listview-expected-behavior.md`)
ListView accessibility objects expected behavior
This document describes the expected behavior of ListView , ListView, ListViewSubItem accessibility objects when using Inspect.
Overview
ListView is a Win32 native control provided by Windows.
Windows Forms runtime provides a support for UI Automation and, thus, responsible for providing required accessible objects with correct properties and actions to make all aspects of the ListView control accessible for customers.
ListView
Accessibility tree for ListView
ListView accessibility tree must contain all visible items based on the selected View view.
- Details view

- LargeIcon view
![]()
- List view

- SmallIcon view
![]()
- Tile view

ListViewGroup
The ListView always contains one or more ListViewGroup, but depending on a configuration of the operating system, application and/or the ListView itself, these groups can be visible or not.
Accessibility tree for a ListView with displayed ListViewGroups
A ListViewGroup is display if the following conditions are met:
- An application has visual styles are enabled.
- A
ListViewis not in a virtual mode. - The ShowGroups property has
truevalue - A
ListViewhas one or more groups - A
ListViewis not in aListview
ListViewGroup listViewGroup = new ListViewGroup("Test group");
listView.Groups.Add(listViewGroup);
listView.ShowGroups = true;
listView.Columns.Add(new ColumnHeader { Text = "Column 1" });
listView.Columns.Add(new ColumnHeader { Text = "Column 2" });
listView.Columns.Add(new ColumnHeader { Text = "Column 3" });
listView.Columns.Add(new ColumnHeader { Text = "Column 4" });
listView.Items.Add(new ListViewItem(new string[] { "Item 1", "SubItem 11", "SubItem 12", "SubItem 13" }, imageIndex: 0, group: listViewGroup));
listView.Items.Add(new ListViewItem(new string[] { "Item 2", "SubItem 21", "SubItem 22", "SubItem 23" }, imageIndex: 0, group: listViewGroup));
listView.Items.Add(new ListViewItem(new string[] { "Item 3", "SubItem 31" }, imageIndex: 0));- Details view

- LargeIcon view
![]()
- List view

- SmallIcon view
![]()
- Tile view

Accessibility tree for a ListView with disabled visual styles
ListViewGroups are not visible when visual styles are disabled.
ListViewGroup listViewGroup = new ListViewGroup("Test group");
listView.Groups.Add(listViewGroup);
listView.ShowGroups = true;
listView.Columns.Add(new ColumnHeader { Text = "Column 1" });
listView.Columns.Add(new ColumnHeader { Text = "Column 2" });
listView.Columns.Add(new ColumnHeader { Text = "Column 3" });
listView.Columns.Add(new ColumnHeader { Text = "Column 4" });
listView.Items.Add(new ListViewItem(new string[] { "Item 1", "SubItem 11", "SubItem 12", "SubItem 13" }, imageIndex: 0, group: listViewGroup));
listView.Items.Add(new ListViewItem(new string[] { "Item 2", "SubItem 21", "SubItem 22", "SubItem 23" }, imageIndex: 0, group: listViewGroup));
listView.Items.Add(new ListViewItem(new string[] { "Item 3", "SubItem 31" }, imageIndex: 0));- Details view

- LargeIcon view
![]()
- List view

- SmallIcon view
![]()
- Tile view

Accessibility tree for a ListView with disabled ListViewGroups
ListViewGroups are not visible when ShowGroups property is set to false.
ListViewGroup listViewGroup = new ListViewGroup("Test group");
listView.Groups.Add(listViewGroup);
listView.ShowGroups = false;
listView.Columns.Add(new ColumnHeader { Text = "Column 1" });
listView.Columns.Add(new ColumnHeader { Text = "Column 2" });
listView.Columns.Add(new ColumnHeader { Text = "Column 3" });
listView.Columns.Add(new ColumnHeader { Text = "Column 4" });
listView.Items.Add(new ListViewItem(new string[] { "Item 1", "SubItem 11", "SubItem 12", "SubItem 13" }, imageIndex: 0, group: listViewGroup));
listView.Items.Add(new ListViewItem(new string[] { "Item 2", "SubItem 21", "SubItem 22", "SubItem 23" }, imageIndex: 0, group: listViewGroup));
listView.Items.Add(new ListViewItem(new string[] { "Item 3", "SubItem 31" }, imageIndex: 0));- Details view

- LargeIcon view
![]()
- List view

- SmallIcon view
![]()
- Tile view

Accessibility tree for a ListView in virtual mode
ListViewGroups are not visible when ListView is in virtual mode.
ListViewGroup listViewGroup = new ListViewGroup("Test group");
listView.Groups.Add(listViewGroup);
listView.VirtualMode = virtualMode;
listView.VirtualListSize = 3;
listView.Columns.Add(new ColumnHeader { Text = "Column 1" });
listView.Columns.Add(new ColumnHeader { Text = "Column 2" });
listView.Columns.Add(new ColumnHeader { Text = "Column 3" });
listView.Columns.Add(new ColumnHeader { Text = "Column 4" });
ListViewItem listItem1 = new ListViewItem(new string[] { "Item 1", "SubItem 11", "SubItem 12", "SubItem 13" }, imageIndex: 0, group: listViewGroup));
ListViewItem listItem2 = new ListViewItem(new string[] { "Item 2", "SubItem 21", "SubItem 22", "SubItem 23" }, imageIndex: 0, group: listViewGroup));
ListViewItem listItem3 = new ListViewItem(new string[] { "Item 3", "SubItem 31", "SubItem 32", "SubItem 33" }, imageIndex: 0));
listView.RetrieveVirtualItem += (s, e) =>
{
e.Item = e.ItemIndex switch
{
0 => listItem1,
1 => listItem2,
2 => listItem3,
_ => throw new NotImplementedException()
};
};
listItem1.SetItemIndex(listView, 0);
listItem2.SetItemIndex(listView, 1);
listItem3.SetItemIndex(listView, 2);- Details view

- LargeIcon view
![]()
- List view

- SmallIcon view
![]()
- Tile view
The ListView in virtual mode does not support "Tile" view
Accessibility tree for a ListView without ListViewGroups
ListViewGroups are not visible when ListView has no groups.
listView.ShowGroups = true;
listView.Columns.Add(new ColumnHeader { Text = "Column 1" });
listView.Columns.Add(new ColumnHeader { Text = "Column 2" });
listView.Columns.Add(new ColumnHeader { Text = "Column 3" });
listView.Columns.Add(new ColumnHeader { Text = "Column 4" });
listView.Items.Add(new ListViewItem(new string[] { "Item 1", "SubItem 11", "SubItem 12", "SubItem 13" }, imageIndex: 0));
listView.Items.Add(new ListViewItem(new string[] { "Item 2", "SubItem 21", "SubItem 22", "SubItem 23" }, imageIndex: 0));
listView.Items.Add(new ListViewItem(new string[] { "Item 3", "SubItem 31" }, imageIndex: 0));- Details view

- LargeIcon view
![]()
- List view

- SmallIcon view
![]()
- Tile view

ListViewSubItem
Accessibility tree for a ListViewItem
A ListViewSubItem is display if the following conditions are met:
- A
ListViewis in aListorDetailsviews. - A
ListViewhas one or more ListViewSubItems - A
ListViewhas one or more Columns - The
ListViewItemhas a TileSize enough to displayListViewSubItems(only forTileview)
listView.Columns.Add(new ColumnHeader { Text = "Column 1" });
listView.Columns.Add(new ColumnHeader { Text = "Column 2" });
listView.Columns.Add(new ColumnHeader { Text = "Column 3" });
listView.Columns.Add(new ColumnHeader { Text = "Column 4" });
listView.Items.Add(new ListViewItem(new string[] { "Item 1", "SubItem 11", "SubItem 12", "SubItem 13" }, imageIndex: 0));
listView.Items.Add(new ListViewItem(new string[] { "Item 2", "SubItem 21", "SubItem 22", "SubItem 23" }, imageIndex: 0));
listView.TileSize = new Size(100, 100);- Details view

- LargeIcon view
![]()
This view does not support ListViewSubItems
- List view

This view does not support ListViewSubItems
- SmallIcon view
![]()
This view does not support ListViewSubItems
- Tile view

Accessibility tree for a ListViewItem (Details view)
It is typical for this mode that when displaying accessibility objects for ListViewSubItems in the Inspect tree, the main parameter is not the number of ListViewSubItems, but the number of Columns that the user can see.
In the case when the number of Columns and ListViewSubItems is the same, the user sees that all Columns contain data and all ListViewSubItems are displayed in the Accessibility tree.
listView.Columns.Add(new ColumnHeader { Text = "Column 1" });
listView.Columns.Add(new ColumnHeader { Text = "Column 2" });
listView.Columns.Add(new ColumnHeader { Text = "Column 3" });
listView.Columns.Add(new ColumnHeader { Text = "Column 4" });
listView.Items.Add(new ListViewItem(new string[] { "Item 1", "SubItem 11", "SubItem 12", "SubItem 13" }, imageIndex: 0));
listView.Items.Add(new ListViewItem(new string[] { "Item 2", "SubItem 21", "SubItem 22", "SubItem 23" }, imageIndex: 0));
If the number of Columns is less than the number of ListViewSubItems, then the user sees only those ListViewSubItems for which there are Columns. As a result, only ListViewSubItems with matching Columns are displayed in the Inspector tree. ListViewSubItems without corresponding Columns on the screen and in the Accessibility tree are not displayed.
listView.Columns.Add(new ColumnHeader { Text = "Column 1" });
listView.Columns.Add(new ColumnHeader { Text = "Column 2" });
listView.Items.Add(new ListViewItem(new string[] { "Item 1", "SubItem 11", "SubItem 12", "SubItem 13" }, imageIndex: 0));
listView.Items.Add(new ListViewItem(new string[] { "Item 2", "SubItem 21", "SubItem 22", "SubItem 23" }, imageIndex: 0));
If the number of Columns is greater than the number of ListViewSubItems, then a fake accessibility object is displayed for non-existent ListViewSubItems. This is because whether they exist or not, the user sees the cell for the ListViewSubItem, so it should have an accessibility object to interact with the Accessibility tree.
listView.Columns.Add(new ColumnHeader { Text = "Column 1" });
listView.Columns.Add(new ColumnHeader { Text = "Column 2" });
listView.Columns.Add(new ColumnHeader { Text = "Column 3" });
listView.Columns.Add(new ColumnHeader { Text = "Column 4" });
listView.Items.Add(new ListViewItem(new string[] { "Item 1", "SubItem 11" }, imageIndex: 0));
listView.Items.Add(new ListViewItem(new string[] { "Item 2", "SubItem 21" }, imageIndex: 0));
Accessibility tree for a ListViewItem (Tile view)
This view takes many factors into account when displaying ListViewSubItems.
The first ListViewSubItem (with 0 index) is never shown in the Accessibility tree, as its data is displayed within the ListViewItem. ListViewItem.FragmentNavigate(NavigateDirection.FirstChild), ListViewItem.FragmentNavigate(NavigateDirection.LastChild) andListViewSubItem.FragmentNavigate(NavigateDirection.PreviousSibling) methods, also never return a 0-indexed ListViewSubItem
All ListViewSubItem is display if the following conditions are met:
- A
ListViewhas one or more ListViewSubItems - A
ListViewhas one or more Columns - The
ListViewItemhas a TileSize enough to displayListViewSubItems
listView.Columns.Add(new ColumnHeader { Text = "Column 1" });
listView.Columns.Add(new ColumnHeader { Text = "Column 2" });
listView.Columns.Add(new ColumnHeader { Text = "Column 3" });
listView.Columns.Add(new ColumnHeader { Text = "Column 4" });
listView.Items.Add(new ListViewItem(new string[] { "Item 1", "SubItem 11", "SubItem 12", "SubItem 13" }, imageIndex: 0));
listView.Items.Add(new ListViewItem(new string[] { "Item 2", "SubItem 21", "SubItem 22", "SubItem 23" }, imageIndex: 0));
listView.TileSize = new Size(100, 100);
If the number of Columns is less than the number of ListViewSubItems, then only ListViewSubItems with corresponding Columns are displayed. The rest of the ListViewSubItems are hidden both on the screen and in the Accessibility tree.
listView.Columns.Add(new ColumnHeader { Text = "Column 1" });
listView.Columns.Add(new ColumnHeader { Text = "Column 2" });
listView.Items.Add(new ListViewItem(new string[] { "Item 1", "SubItem 11", "SubItem 12", "SubItem 13" }, imageIndex: 0));
listView.Items.Add(new ListViewItem(new string[] { "Item 2", "SubItem 21", "SubItem 22", "SubItem 23" }, imageIndex: 0));
listView.TileSize = new Size(100, 100);
If the number of ListViewSubItems is less than the number of Columns, then only existing ListViewSubItems are displayed in the Accessibility tree (as opposed to the Details view), since nonexistent ListViewSubItems are not displayed on the screen and the user cannot interact with them.
listView.Columns.Add(new ColumnHeader { Text = "Column 1" });
listView.Columns.Add(new ColumnHeader { Text = "Column 2" });
listView.Columns.Add(new ColumnHeader { Text = "Column 3" });
listView.Columns.Add(new ColumnHeader { Text = "Column 4" });
listView.Items.Add(new ListViewItem(new string[] { "Item 1", "SubItem 11", "SubItem 12" }, imageIndex: 0));
listView.Items.Add(new ListViewItem(new string[] { "Item 2", "SubItem 21", "SubItem 22" }, imageIndex: 0));
listView.TileSize = new Size(100, 100);
If the TileSize of the ListViewItem is too small, then only those ListViewSubItems that are included in the visible area of ββthe ListViewItem are displayed. The rest of the ListViewSubItems are hidden both on the screen and in the Accessibility tree.
listView.Columns.Add(new ColumnHeader { Text = "Column 1" });
listView.Columns.Add(new ColumnHeader { Text = "Column 2" });
listView.Columns.Add(new ColumnHeader { Text = "Column 3" });
listView.Columns.Add(new ColumnHeader { Text = "Column 4" });
listView.Items.Add(new ListViewItem(new string[] { "Item 1", "SubItem 11", "SubItem 12", "SubItem 13" }, imageIndex: 0));
listView.Items.Add(new ListViewItem(new string[] { "Item 2", "SubItem 21", "SubItem 22", "SubItem 23" }, imageIndex: 0));
listView.TileSize = new Size(100, 40);
ListViewSubItems are not displayed when visual styles are disabled for an application. As a result, ListViewSubItems are also hidden in the Accessibility tree
listView.Columns.Add(new ColumnHeader { Text = "Column 1" });
listView.Columns.Add(new ColumnHeader { Text = "Column 2" });
listView.Columns.Add(new ColumnHeader { Text = "Column 3" });
listView.Columns.Add(new ColumnHeader { Text = "Column 4" });
listView.Items.Add(new ListViewItem(new string[] { "Item 1", "SubItem 11", "SubItem 12", "SubItem 13" }, imageIndex: 0));
listView.Items.Add(new ListViewItem(new string[] { "Item 2", "SubItem 21", "SubItem 22", "SubItem 23" }, imageIndex: 0));
listView.TileSize = new Size(100, 100);
Accessibility Monthcalendar Expected Behavior (`docs/accessibility/monthcalendar-expected-behavior.md`)
MonthCalendarAccessibleObject expected properties and behavior
This document describes the expected behavior of MonthCalendarAccessibleObject
when using accessibility tools such as Inspect, Narrator or Accessibility Insights, and
expected accessibility properties of all visible items
of the MonthCalendar control.
Overview
MonthCalendar is a native Windows control, wrapped by Windows Forms SDK. Windows Forms provide a UIA support for all
MonthCalendar accessible objects and their children making the MonthCalendar control accessible for Windows Forms end users.
Expected properties and actions of a MonthCalendar and its parts in different accessibility tools are listed below.
:point_up: A checked item means that the current implementation provides the full support, and the unchecked item means the expected implementation is currently unavailable.
Accessibility Tools
Inspect
Accessibility tree
MonthCalendar accessibility tree must contain all visible items based on its view.
- Month view

- Year view

- Decade view

- Century view

Accessibility properties
- Month view
MonthCalendar:
-
ControlType= "calendar" always -
IsEnabled=true, if the control is enabled -
HasKeyboardFocus=true, if the control is in focus -
IsKeyboardFocusable=true, if the calendar is enabled -
HelpText= "MonthCalendar(Control)" - Correct grid Column and Row count
-
Nameis empty, if it is not set -
Role= "table" -
Value= selected dates (e.g. "Saturday, April 10, 2021 - Wednesday, April 14, 2021") - Column and row headers =
null -
State= "focusable" + "focused", if the control is in focus - Supports Grid, LegacyIAccessible, Table, Value patterns
Previous/Next buttons:
-
Name= "Previous" or "Next" -
ControlType= "button" -
IsKeyboardFocusable=false -
IsEnabled=true, if the control is enabled and there are next/previous calendars -
HasKeyboardFocus=false - Has a default action and description
-
Role= "push button" -
State= "normal" - Supports Invoke and LegacyIAccessible
Today button:
-
Name= a button text (e.g. "Today: 3/20/2021") -
ControlType= "button" -
IsKeyboardFocusable=false -
HasKeyboardFocus=false -
IsEnabled=true, if the control is enabled - Has a default action and description
-
Role= "push button" -
State= "normal" - Supports Invoke and LegacyIAccessible
Calendar:
-
IsEnabled=true, if the control is enabled -
ControlType= "pane" -
HasKeyboardFocus=true, if the control is in focus and the calendar contains the focused cell -
IsKeyboardFocusable=true, if the calendar is enabled - Has correct GridItem properties
-
Role== "client" -
State= "focusable, selectable" + has "focused", "selected", if the calendar contains the focused cell - Doesn't have TableItems columns and rows
- Supports GridItem, LegacyIAccessible, TableItem patterns
Calendar header button:
-
Name= the button text (e.g. "March 2021") -
HasKeyboardFocus=false -
IsKeyboardFocusable=false -
IsEnabled=true, if the control is enabled -
DefaultAction= "Click" -
Role= "push button" -
State= "normal" - Supports Invoke and LegacyIAccessible
Calendar body:
-
Name= the header text (e.g. March 2021) -
HasKeyboardFocus=true, if the control is in focus and the calendar contains the focused cell -
IsKeyboardFocusable=true, if the calendar is enabled -
IsEnabled=true, if the control is enabled -
ControlType= "table" - Correct grid Column and Row count (headers are not included)
-
Role= "table" -
State= "default" - Supports Grid, LegacyIAccessible, Table patterns
Calendar row:
-
Nameis empty -
HasKeyboardFocus=true, if the control is in focus and the row contains the focused cell -
IsEnabled=true, if the control is enabled -
IsKeyboardFocusable=true, if the calendar is enabled -
ControlType= "pane" -
Role= "row" -
State= "normal" -
Description= "Week {number}" for date rows.Descriptionis empty for a header row - Supports LegacyIAccessible pattern
Cell of the header row (day of week):
-
Name= the cell text (e.g. "Mon" or "Fri") -
IsEnabled=true, if the control is enabled -
ControlType= "header" -
HasKeyboardFocus= alwaysfalse -
IsKeyboardFocusable=false -
Role= "column header" -
State= "normal" - Doesn't have a
Description - Doesn't have a
DefaultAction - Supports LegacyIAccessible pattern
The first cell of date rows (week number):
-
Name= "Week {the cell text}" (e.g. "Week 12" or "Week 36" - a week number) -
IsEnabled=true, if the control is enabled -
ControlType= "header" -
HasKeyboardFocus= alwaysfalse -
IsKeyboardFocusable=false -
Role= "row header" -
State= "normal" - Doesn't have a
Description - Doesn't have a
DefaultAction - Supports LegacyIAccessible pattern
Date cell:
-
Name= the day long name (e.g. "Wednesday, July 14, 2021") -
IsEnabled=true, if the control is enabled -
ControlType= "DataItem" ("item" in the accessibility tree) -
HasKeyboardFocus=true, if the cell is focused and the control in focus -
IsKeyboardFocusable=true, if the control is enabled - Correct GridItem pattern properties
-
Description= "Week {number}, {day of week}" (e.g. "Week 10, Friday") -
DefaultAction= "Click" -
Role= "cell" -
State= "focusable, selectable", if the control is enabled (the order of the states doesn't matter),
"selected, focusable, selectable", if the cell is selected,
"focused, selected, focusable, selectable", if the cell is selected and focused.
:warning: Important point: if a user selects several cells, all of them should have "selected" state, but only one of them should have "focused" state. - Correct TableItem column and row headers items
- Supports Invoke, GridItem, LegacyIAccessible, TableItem patterns
- Year view
MonthCalendar:
-
ControlType= "calendar" always -
IsEnabled=true, if the control is enabled -
HasKeyboardFocus=true, if the control is in focus -
IsKeyboardFocusable=true, if the calendar is enabled -
HelpText= "MonthCalendar(Control)" - Correct grid Column and Row count
-
Nameis empty, if it is not set -
Role= "table" -
Value= a selected month (e.g. "September 2022") - Column and row headers = null
-
State= "focusable" + "focused" if the control is in focus - Supports Grid, LegacyIAccessible, Table, Value patterns
Previous/Next buttons:
-
Name= "Previous" or "Next" -
ControlType= "button" -
IsKeyboardFocusable=false -
IsEnabled=true, if the control is enabled and there are next/previous calendars -
HasKeyboardFocus=false - Has a default action and description
-
Role= "push button" -
State= "normal" - Supports Invoke and LegacyIAccessible
Today button:
-
Name= a button text (e.g. "Today: 3/20/2021") -
ControlType= "button" -
IsKeyboardFocusable=false -
HasKeyboardFocus=false -
IsEnabled=true, if the control is enabled - Has a default action and description
-
Role= "push button" -
State= "normal" - Supports Invoke and LegacyIAccessible
Calendar:
-
IsEnabled=true, if the control is enabled -
ControlType= "pane" -
HasKeyboardFocus=true, if the control is in focus and the calendar contains the focused cell -
IsKeyboardFocusable=true, if the calendar is enabled - Has correct GridItem properties
-
Role== "client" -
State= "focusable, selectable" + has "focused", "selected", if the calendar contains the focused cell - Doesn't have TableItems columns and rows
- Supports GridItem, LegacyIAccessible, TableItem patterns
Calendar header button:
-
Name= the button text (e.g. "2021") -
HasKeyboardFocus=false -
IsKeyboardFocusable=false -
IsEnabled=true, if the control is enabled - Has a default action
-
Role= "push button" -
State= "normal" - Supports Invoke and LegacyIAccessible
Calendar body:
-
Name= the header text (e.g. "2021") -
HasKeyboardFocus=true, if the control is in focus and the calendar contains the focused cell -
IsKeyboardFocusable=true, if the calendar is enabled -
IsEnabled=true, if the control is enabled -
ControlType= "table" - Correct grid Column and Row count (headers are not included)
-
Role= "table" -
State= "default" - Supports Grid, LegacyIAccessible, Table patterns
Calendar row:
-
Nameis empty -
HasKeyboardFocus=true, if the control is in focus and the row contains the focused cell -
IsEnabled=true, if the control is enabled -
IsKeyboardFocusable=true, if the calendar is enabled -
ControlType= "pane" -
Role= "row" -
State= "normal" -
Descriptionis empty - Supports LegacyIAccessible pattern
Month cell:
-
Name= the cell text (e.g. "May") -
IsEnabled=true, if the control is enabled -
ControlType= "DataItem" ("item" in the accessibility tree) -
HasKeyboardFocus=true, if the cell is focused -
IsKeyboardFocusable=true, if the control is enabled - Correct GridItem pattern properties
-
Descriptionis empty -
Role= "cell" -
State= "focusable, selectable" if the control is enabled. (the order of the states doesn't matter)
"focused, selected, focusable, selectable" if the cell is selected and focused
:warning: Important point: if a user can't select several cells in this view, so only one cell should have "selected" state, and this cell should have "focused" state. - Doesn't have TableItem column and row headers items
- Supports GridItem, LegacyIAccessible, TableItem patterns
- Decade view
MonthCalendar:
-
ControlType= "calendar" always -
IsEnabled=true, if the control is enabled -
HasKeyboardFocus=true, if the control is in focus -
IsKeyboardFocusable=true, if the calendar is enabled -
HelpText= "MonthCalendar(Control)" - Correct grid Column and Row count
-
Nameis empty, if it is not set -
Role= "table" -
Value= a selected year (e.g. "2022") - Column and row headers = null
-
State= "focusable" + "focused" if the control is in focus - Supports Grid, LegacyIAccessible, Table, Value patterns
Previous/Next buttons:
-
Name= "Previous" or "Next" -
ControlType= "button" -
IsKeyboardFocusable=false -
IsEnabled=true, if the control is enabled and there are next/previous calendars -
HasKeyboardFocus=false - Has a default action and description
-
Role= "push button" -
State= "normal" - Supports Invoke and LegacyIAccessible
Today button:
-
Name= a button text (e.g. "Today: 3/20/2021") -
ControlType= "button" -
IsKeyboardFocusable=false -
HasKeyboardFocus=false -
IsEnabled=true, if the control is enabled - Has a default action and description
-
Role= "push button" -
State= "normal" - Supports Invoke and LegacyIAccessible
Calendar:
-
IsEnabled=true, if the control is enabled -
ControlType= "pane" -
HasKeyboardFocus=true, if the control is in focus and the calendar contains the focused cell -
IsKeyboardFocusable=true, if the calendar is enabled - Has correct GridItem properties
-
Role== "client" -
State= "focusable, selectable" + has "focused", "selected", if the calendar contains the focused cell - Doesn't have TableItems columns and rows
- Supports GridItem, LegacyIAccessible, TableItem patterns
Calendar header button:
-
Name= the button text (e.g. "2020-2029") -
HasKeyboardFocus=false -
IsKeyboardFocusable=false -
IsEnabled=true, if the control is enabled - Has a default action
-
Role= "push button" -
State= "normal" - Supports Invoke and LegacyIAccessible
Calendar body:
-
Name= the header text (e.g. "2020-2029") -
HasKeyboardFocus=true, if the control is in focus and the calendar contains the focused cell -
IsKeyboardFocusable=true, if the calendar is enabled -
IsEnabled=true, if the control is enabled -
ControlType= "table" - Correct grid Column and Row count (headers are not included)
-
Role= "table" -
State= "default" - Supports Grid, LegacyIAccessible, Table patterns
Calendar row:
-
Nameis empty -
HasKeyboardFocus=true, if the control is in focus and the row contains the focused cell -
IsEnabled=true, if the control is enabled -
IsKeyboardFocusable=true, if the calendar is enabled -
ControlType= "pane" -
Role= "row" -
State= "normal" -
Descriptionis empty - Supports LegacyIAccessible pattern
Year cell:
-
Name= the cell text (e.g. "2020") -
IsEnabled=true, if the control is enabled -
ControlType= "DataItem" ("item" in the accessibility tree) -
HasKeyboardFocus=true, if the cell is focused -
IsKeyboardFocusable=true, if the control is enabled - Correct GridItem pattern properties
-
Descriptionis empty -
Role= "cell" -
State= "focusable, selectable" if the control is enabled. (the order of the states doesn't matter)
"focused, selected, focusable, selectable" if the cell is selected and focused
:warning: Important point: if a user can't select several cells in this view, so only one cell should have "selected" state, and this cell should have "focused" state. - Doesn't have TableItem column and row headers items
- Supports GridItem, LegacyIAccessible, TableItem patterns
- Century view
MonthCalendar:
-
ControlType= "calendar" always -
IsEnabled=true, if the control is enabled -
HasKeyboardFocus=true, if the control is in focus -
IsKeyboardFocusable=true, if the calendar is enabled -
HelpText= "MonthCalendar(Control)" - Correct grid Column and Row count
-
Nameis empty, if it is not set -
Role= "table" -
Value= a selected decade (e.g. "2020-2029") - Column and row headers = null
-
State= "focusable" + "focused" if the control is in focus - Supports Grid, LegacyIAccessible, Table, Value patterns
Previous/Next buttons:
-
Name= "Previous" or "Next" -
ControlType= "button" -
IsKeyboardFocusable=false -
IsEnabled=true, if the control is enabled and there are next/previous calendars -
HasKeyboardFocus=false - Has a default action and description
-
Role= "push button" -
State= "normal" - Supports Invoke and LegacyIAccessible
Today button:
-
Name= a button text (e.g. "Today: 3/20/2021") -
ControlType= "button" -
IsKeyboardFocusable=false -
HasKeyboardFocus=false -
IsEnabled=true, if the control is enabled - Has a default action and description
-
Role= "push button" -
State= "normal" - Supports Invoke and LegacyIAccessible
Calendar:
-
IsEnabled=true, if the control is enabled -
ControlType= "pane" -
HasKeyboardFocus=true, if the control is in focus and the calendar contains the focused cell -
IsKeyboardFocusable=true, if the calendar is enabled - Has correct GridItem properties
-
Role== "client" -
State= "focusable, selectable" + has "focused", "selected", if the calendar contains the focused cell - Doesn't have TableItems columns and rows
- Supports GridItem, LegacyIAccessible, TableItem patterns
Calendar header button:
-
Name= the button text (e.g. "2000-2099") -
HasKeyboardFocus=false -
IsKeyboardFocusable=false -
IsEnabled=true, if the control is enabled - Has a default action
-
Role= "push button" -
State= "normal" - Supports Invoke and LegacyIAccessible
Calendar body:
-
Name= the header text (e.g. "2000-2099") -
HasKeyboardFocus=true, if the control is in focus and the calendar contains the focused cell -
IsKeyboardFocusable=true, if the calendar is enabled -
IsEnabled=true, if the control is enabled -
ControlType= "table" - Correct grid Column and Row count (headers are not included)
-
Role= "table" -
State= "default" - Supports Grid, LegacyIAccessible, Table patterns
Calendar row:
-
Nameis empty -
HasKeyboardFocus=true, if the control is in focus and the row contains the focused cell -
IsEnabled=true, if the control is enabled -
IsKeyboardFocusable=true, if the calendar is enabled -
ControlType= "pane" -
Role= "row" -
State= "normal" -
Descriptionis empty - Supports LegacyIAccessible pattern
Decade cell:
-
Name= the cell text (e.g. "2020-2029") -
IsEnabled=true, if the control is enabled -
ControlType= "DataItem" ("item" in the accessibility tree) -
HasKeyboardFocus=true, if the cell is focused -
IsKeyboardFocusable=true, if the control is enabled - Correct GridItem pattern properties
-
Descriptionis empty -
Role= "cell" -
State= "focusable, selectable" if the control is enabled. (the order of the states doesn't matter)
"focused, selected, focusable, selectable" if the cell is selected and focused
:warning: Important point: if a user can't select several cells in this view, so only one cell should have "selected" state, and this cell should have "focused" state. - Doesn't have TableItem column and row headers items
- Supports GridItem, LegacyIAccessible, TableItem patterns
Accessibility actions
Here are described accessibility actions of supported patterns.
:point_up: The "Focus" action has unexpected behavior, because when you call it from Inspect,
the testing form loses focus. Then the form gets focus, in this case,
MonthCalendar.OnGotFocus handler works, that raises accessibility focus event for the focused cell accessible object.
- Month view
MonthCalendar:
- Focus - focuses on the focused cell
- Grid.GetItem- returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- Value.SetValue - does nothing
- LegacyIAccessible.Select - does nothing, because the MonthCalendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Previous/Next buttons:
- Focus - the button is not keyboard focusable, so does nothing
- Invoke.Invoke - clicks the button (moves to the previous/next month)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (works in the debug mode only, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Today button:
- Focus - the button is not keyboard focusable, so does nothing
- Invoke.Invoke - clicks the button (moves to the today cell)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar:
- Focus - focuses on the focused cell, if the calendar contains it. And does nothing, if the calendar doesn't contain the focused cell
- LegacyIAccessible.Select - does nothing, because the calendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar header button:
- Focus - the button is not keyboard focusable, so does nothing
- Invoke.Invoke - clicks the button (changes the calendar view)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar body:
- Focus - focuses on the focused cell, if the calendar contains it. And does nothing, if the calendar doesn't contain the focused cell
- Grid.GetItem - returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- LegacyIAccessible.Select - does nothing, because the body is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar row:
- Focus - focuses on the focused cell, if the row contains it. And does nothing, if the row doesn't contain the focused cell
- LegacyIAccessible.Select - does nothing, because the row is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Cell of the header row (day of week):
- Focus - does nothing
- LegacyIAccessible.Select - does nothing, because the header cell is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
The first cell of date rows (week numbers):
- Focus - does nothing
- LegacyIAccessible.Select - does nothing, because the header cell is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Date cell:
- Focus - focuses on the focused cell
- Invoke.Invoke - clicks the cell (select it)
- LegacyIAccessible.Select - selects the cell
- LegacyIAccessible.DoDefaultAction - selects the cell
- LegacyIAccessible.SetValue - does nothing
- Year view
MonthCalendar:
- Focus - focuses on the focused cell
- Grid.GetItem- returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- Value.SetValue - does nothing
- LegacyIAccessible.Select - does nothing, because the MonthCalendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Previous/Next buttons:
- Focus - the button is not keyboard focusable, so does nothing
- Invoke.Invoke - clicks the button (moves to the previous/next month)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (works in the debug mode only, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Today button:
- Focus - the button is not keyboard focusable, so does nothing
- Invoke.Invoke - clicks the button (moves to the today cell)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar:
- Focus - focuses on the focused cell, if the calendar contains it. And does nothing, if the calendar doesn't contain the focused cell
- LegacyIAccessible.Select - does nothing, because the calendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar header button:
- Focus - the button is not keyboard focusable, so does nothing
- Invoke.Invoke - clicks the button (changes the calendar view)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar body:
- Focus - focuses on the focused cell, if the calendar contains it. And does nothing, if the calendar doesn't contain the focused cell
- Grid.GetItem - returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- LegacyIAccessible.Select - does nothing, because the body is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar row:
- Focus - focuses on the focused cell, if the row contains it. And does nothing, if the row doesn't contain the focused cell
- LegacyIAccessible.Select - does nothing, because the row is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Month cell:
- Focus - focuses on the focused cell
- Invoke.Invoke - clicks the cell (changes the view)
- LegacyIAccessible.Select - selects the cell
- LegacyIAccessible.DoDefaultAction - click the cell
- LegacyIAccessible.SetValue - does nothing
- Decade view
MonthCalendar:
- Focus - focuses on the focused cell
- Grid.GetItem- returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- Value.SetValue - does nothing
- LegacyIAccessible.Select - does nothing, because the MonthCalendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Previous/Next buttons:
- Focus - the button is not keyboard focusable, so does nothing
- Invoke.Invoke - clicks the button (moves to the previous/next month)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (works in the debug mode only, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Today button:
- Focus - the button is not keyboard focusable, so does nothing
- Invoke.Invoke - clicks the button (moves to the today cell)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar:
- Focus - focuses on the focused cell, if the calendar contains it. And does nothing, if the calendar doesn't contain the focused cell
- LegacyIAccessible.Select - does nothing, because the calendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar header button:
- Focus - the button is not keyboard focusable, so does nothing
- Invoke.Invoke - clicks the button (changes the calendar view)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar body:
- Focus - focuses on the focused cell, if the calendar contains it. And does nothing, if the calendar doesn't contain the focused cell
- Grid.GetItem - returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- LegacyIAccessible.Select - does nothing, because the body is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar row:
- Focus - focuses on the focused cell, if the row contains it. And does nothing, if the row doesn't contain the focused cell
- LegacyIAccessible.Select - does nothing, because the row is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Year cell:
- Focus - focuses on the focused cell
- Invoke.Invoke - clicks the cell (changes the view)
- LegacyIAccessible.Select - selects the cell
- LegacyIAccessible.DoDefaultAction - click the cell
- LegacyIAccessible.SetValue - does nothing
- Century view
MonthCalendar:
- Focus - focuses on the focused cell
- Grid.GetItem- returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- Value.SetValue - does nothing
- LegacyIAccessible.Select - does nothing, because the MonthCalendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Previous/Next buttons:
- Focus - the button is not keyboard focusable, so does nothing
- Invoke.Invoke - clicks the button (moves to the previous/next month)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (works in the debug mode only, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Today button:
- Focus - the button is not keyboard focusable, so does nothing
- Invoke.Invoke - clicks the button (moves to the today cell)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar:
- Focus - focuses on the focused cell, if the calendar contains it. And does nothing, if the calendar doesn't contain the focused cell
- LegacyIAccessible.Select - does nothing, because the calendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar header button:
- Focus - the button is not keyboard focusable, so does nothing
- Invoke.Invoke - clicks the button (changes the calendar view)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar body:
- Focus - focuses on the focused cell, if the calendar contains it. And does nothing, if the calendar doesn't contain the focused cell
- Grid.GetItem - returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- LegacyIAccessible.Select - does nothing, because the body is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar row:
- Focus - focuses on the focused cell, if the row contains it. And does nothing, if the row doesn't contain the focused cell
- LegacyIAccessible.Select - does nothing, because the row is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Decade cell:
- Focus - focuses on the focused cell
- Invoke.Invoke - clicks the cell (changes the view)
- LegacyIAccessible.Select - selects the cell
- LegacyIAccessible.DoDefaultAction - click the cell
- LegacyIAccessible.SetValue - does nothing
ElementProviderFromPoint (get an item when hovering by mouse) behavior
- Returns a MonthCalendar accessible object, if mouse isn't hovering any element
- Returns a "Previous" button accessible object, if mouse is hovering it
- Returns a "Next" button accessible object, if mouse is hovering it
- Returns a "Today" button accessible object, if mouse is hovering the "Today" text, or to the right of the text
- Returns a calendar header button accessible object (e.g. "March 2021"), if mouse is hovering it
- Returns a day of week cell accessible object (e.g. "Sat"), if its MonthCalendar is in the Month view and mouse is hovering it
- Returns a week number cell accessible object (e.g. "18"), if
ShowWeekNumber=true,- its MonthCalendar is in the Month view
- mouse is hovering this cell
- Returns a day/month/year/years cell accessible object, if this cell is visible, and mouse is hovering it
- Returns an accessible object of a gray cell of the next/previous calendar, if it is, and if mouse is hovering it Screenshot

- Returns a calendar accessible object, if its Bounds contains mouse coordinates, and if there is no any cell in this point Screenshot

- Returns a MonthCalendar accessible object, if
MaxDateandMinDateare set, and several months are missing there (they are invisible) and if mouse is hovering some invisible calendar Screenshot
- Returns an accessible object of the first week number of a calendar, if mouse is hovering it, and if the calendar first row is partial (a specific case - there is a workaround that fixes a bug of Win API) Screenshot

- Doesn't return week number cells accessible objects of the last calendar, if
MaxDateis set, and mouse is hovering them Screenshot
- Returns a correct accessible object, that is visible, if
MaxDate,MinDate,FirstDayOfWeekare changed or the display range is changed via the "Next"/"Previous" buttons
Narrator
- Month view
- Announces dates when moving through them
- Moves through all the accessibility tree nodes in the "Scan" mode
- Moves through all the accessibility tree nodes in the "Scan" mode after the display range is changed
- Focuses on the focused cell when the control gets focus
- Focuses on the focused cell, if
MaxDate,MinDate,FirstDayOfWeekare changed or the display range is changed via the "Next"/"Previous" buttons
- Year view
- Announces dates when moving through them
- Moves through all the accessibility tree nodes in the "Scan" mode
- Moves through all the accessibility tree nodes in the "Scan" mode after the display range is changed
- Focuses on the focused cell when the control gets focus
- Focuses on the focused cell, if
MaxDate,MinDate,FirstDayOfWeekare changed or the display range is changed via the "Next"/"Previous" buttons
- Decade view
- Announces dates when moving through them
- Moves through all the accessibility tree nodes in the "Scan" mode
- Moves through all the accessibility tree nodes in the "Scan" mode after the display range is changed
- Focuses on the focused cell when the control gets focus
- Focuses on the focused cell, if
MaxDate,MinDate,FirstDayOfWeekare changed or the display range is changed via the "Next"/"Previous" buttons
- Century view
- Announces dates when moving through them
- Moves through all the accessibility tree nodes in the "Scan" mode
- Moves through all the accessibility tree nodes in the "Scan" mode after the display range is changed
- Focuses on the focused cell when the control gets focus
- Focuses on the focused cell, if
MaxDate,MinDate,FirstDayOfWeekare changed or the display range is changed via the "Next"/"Previous" buttons
AccessibilityInsights
- Month view
- There are no any AI errors
- The accessibility tree is correct
- AI gets a correct visible accessible object when hovering the mouse (an element from the point)
- AI sees correct item patterns and does supported pattern Actions correctly
MonthCalendar:
- Grid.GetItem- returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- Value.SetValue - does nothing
- LegacyIAccessible.Select - does nothing, because the MonthCalendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Previous/Next buttons:
- Invoke.Invoke - clicks the button (moves to the previous/next month)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button
- LegacyIAccessible.SetValue - does nothing
Today button:
- Invoke.Invoke - clicks the button (moves to the today cell)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar:
- LegacyIAccessible.Select - does nothing, because the calendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar header button:
- Invoke.Invoke - clicks the button (changes the calendar view)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar body:
- Grid.GetItem - returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- LegacyIAccessible.Select - does nothing, because the body is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar row:
- LegacyIAccessible.Select - does nothing, because the row is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Cell of the header row (day of week):
- LegacyIAccessible.Select - does nothing, because the header cell is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
The first cell of date rows (week numbers):
- LegacyIAccessible.Select - does nothing, because the header cell is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Date cell:
- Invoke.Invoke - clicks the cell (select it)
- LegacyIAccessible.Select - selects the cell
- LegacyIAccessible.DoDefaultAction - selects the cell (AI issue)
- LegacyIAccessible.SetValue - does nothing
- Year view
- There are no any AI errors
- The accessibility tree is correct.
- AI gets a correct visible accessible object when hovering the mouse (an element from the point).
- AI sees correct items patterns and does supported pattern Actions correctly:
MonthCalendar:
- Grid.GetItem- returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- Value.SetValue - does nothing
- LegacyIAccessible.Select - does nothing, because the MonthCalendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Previous/Next buttons:
- Invoke.Invoke - clicks the button (moves to the previous/next month) (AI issue)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (AI issue)
- LegacyIAccessible.SetValue - does nothing
Today button:
- Invoke.Invoke - clicks the button (moves to the today cell)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar:
- LegacyIAccessible.Select - does nothing, because the calendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar header button:
- Invoke.Invoke - clicks the button (changes the calendar view)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar body:
- Grid.GetItem - returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- LegacyIAccessible.Select - does nothing, because the body is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar row:
- LegacyIAccessible.Select - does nothing, because the row is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Month cell:
- Invoke.Invoke - clicks the cell (select it)
- LegacyIAccessible.Select - selects the cell
- LegacyIAccessible.DoDefaultAction - selects the cell (AI issue)
- LegacyIAccessible.SetValue - does nothing
- Decade view
- There are no any AI errors
- The accessibility tree is correct.
- AI gets a correct visible accessible object when hovering the mouse (an element from the point).
- AI sees correct items patterns and does supported pattern Actions correctly:
MonthCalendar:
- Grid.GetItem- returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- Value.SetValue - does nothing
- LegacyIAccessible.Select - does nothing, because the MonthCalendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Previous/Next buttons:
- Invoke.Invoke - clicks the button (moves to the previous/next month) (AI issue)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (AI issue)
- LegacyIAccessible.SetValue - does nothing
Today button:
- Invoke.Invoke - clicks the button (moves to the today cell)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar:
- LegacyIAccessible.Select - does nothing, because the calendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar header button:
- Invoke.Invoke - clicks the button (changes the calendar view)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar body:
- Grid.GetItem - returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- LegacyIAccessible.Select - does nothing, because the body is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar row:
- LegacyIAccessible.Select - does nothing, because the row is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Year cell:
- Invoke.Invoke - clicks the cell (select it)
- LegacyIAccessible.Select - selects the cell
- LegacyIAccessible.DoDefaultAction - selects the cell (AI issue)
- LegacyIAccessible.SetValue - does nothing
- Century view
- There are no any AI errors
- The accessibility tree is correct.
- AI gets a correct visible accessible object when hovering the mouse (an element from the point).
- AI sees correct items patterns and does supported pattern Actions correctly:
MonthCalendar:
- Grid.GetItem- returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- Value.SetValue - does nothing
- LegacyIAccessible.Select - does nothing, because the MonthCalendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Previous/Next buttons:
- Invoke.Invoke - clicks the button (moves to the previous/next month) (AI issue)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (AI issue)
- LegacyIAccessible.SetValue - does nothing
Today button:
- Invoke.Invoke - clicks the button (moves to the today cell)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar:
- LegacyIAccessible.Select - does nothing, because the calendar is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar header button:
- Invoke.Invoke - clicks the button (changes the calendar view)
- LegacyIAccessible.Select - does nothing, because the button is not selectable
- LegacyIAccessible.DoDefaultAction - clicks the button (doesn't work, it's Inspect Issue)
- LegacyIAccessible.SetValue - does nothing
Calendar body:
- Grid.GetItem - returns OK for the correct row and column, returns FAIL for incorrect arguments (doesn't work, it's Inspect Issue)
- LegacyIAccessible.Select - does nothing, because the body is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Calendar row:
- LegacyIAccessible.Select - does nothing, because the row is not selectable
- LegacyIAccessible.DoDefaultAction - does nothing
- LegacyIAccessible.SetValue - does nothing
Decade cell:
- Invoke.Invoke - clicks the cell (select it)
- LegacyIAccessible.Select - selects the cell
- LegacyIAccessible.DoDefaultAction - selects the cell (AI issue)
- LegacyIAccessible.SetValue - does nothing
Behavior test cases
- Month view
- Case: Change the Today date (set
TodayDateof a MonthCalendar)
Expected: Nothing happens - Case: Click on a gray date cell (of the next or previous calendars)
Expected: The monthCalendar changes the display range. Its accessibility tree rebuilds. - Case: Size of the control is changed that the control changes calendars count
Expected: The accessibility tree is rebuilt. ElementProviderFromPoint returns visible items correctly - Case: A calendar of a MonthCalendar has non-full rows
Expected: Inspect sees only visible items in that row - Case: A calendar of a MonthCalendar has some empty rows
Expected: These rows are not in the accessibility tree - Case: The first week number cell in the first calendar in a MonthCalendar is in a non-full row
Expected: Inspect sees that cell correctly with the correct name - Case: The last week number cells of the last non-full calendar have the same values for empty rows
Expected: They are not in the accessibility tree - Case: Select some dates (e.g. 10-15th of September), move to right, thereby the focused cell
will be in right (e.g. 15th of September). SetMinDateof the calendar less then the selected range (e.g. 1st of September).
Expected: The selected range doesn't change. The focused cell doesn't change.
The focused cell has "focused" accessibility state (check Inspect). - Case: Select some dates (e.g. 10-15th of September), move to left, thereby the focused cell
will be in left (e.g. 10th of September). SetMinDateof the calendar less then the selected range (e.g. 1st of September).
Expected: The selected range doesn't change. The focused cell doesn't change.
The focused cell has "focused" accessibility state (check Inspect). - Case: Select some dates (e.g. 10-15th of September), move to right, thereby the focused cell
will be in right (e.g. 15th of September). SetMaxDateof the calendar more then the selected range (e.g. 20th of September).
Expected: The selected range doesn't change. The focused cell doesn't change.
The focused cell has "focused" accessibility state (check Inspect). - Case: Select some dates (e.g. 10-15th of September), move to left, thereby the focused cell
will be in left (e.g. 10th of September). SetMaxDateof the calendar more then the selected range (e.g. 20th of September).
Expected: The selected range doesn't change. The focused cell doesn't change.
The focused cell has "focused" accessibility state (check Inspect). - Case: Select some dates (e.g. 10-15th of September), move to right, thereby the focused cell
will be in right (e.g. 15th of September). SetMinDateof the calendar more then the start of the selected range,
but less then the end of the selected range (e.g. 13th of September).
Expected: The selected range changes. The focused cell doesn't change.
The focused cell has "focused" accessibility state (check Inspect). - Case: Select some dates (e.g. 10-15th of September), move to left, thereby the focused cell
will be in left (e.g. 10th of September). SetMinDateof the calendar more then the start of the selected range,
but less then the end of the selected range (e.g. 13th of September).
Expected: The selected range changes. The focused cell changes (13th of September).
The new focused cell has "focused" accessibility state (check Inspect). - Case: Select some dates (e.g. 10-15th of September), move to right, thereby the focused cell
will be in right (e.g. 15th of September). SetMaxDateof the calendar more then the start of the selected range,
but less then the end of the selected range (e.g. 13th of September).
Expected: The selected range changes. The focused cell changes (13th of September).
The new focused cell has "focused" accessibility state (check Inspect). - Case: Select some dates (e.g. 10-15th of September), move to left, thereby the focused cell
will be in left (e.g. 10th of September). SetMaxDateof the calendar more then the start of the selected range,
but less then the end of the selected range (e.g. 13th of September).
Expected: The selected range changes. The focused cell cell doesn't change.
The focused cell has "focused" accessibility state (check Inspect). - Case: Select some dates (e.g. 10-15th of September), move to right, thereby the focused cell
will be in right (e.g. 15th of September). Set newFirstDayOfWeek(e.g. Friday).
Expected: The selected range doesn't change. The focused cell doesn't change.
The focused cell has "focused" accessibility state (check Inspect). - Case: Select some dates (e.g. 10-15th of September), move to left, thereby the focused cell
will be in left (e.g. 10th of September). Set newFirstDayOfWeek(e.g. Friday).
Expected: The selected range doesn't change. The focused cell doesn't change.
The focused cell has "focused" accessibility state (check Inspect). - Case:
MinDateis more then the selected range.
Expected: The focused cell changes. The new focused cell has "focused" accessibility state. - Case:
MaxDateis less then the selected range.
Expected: The focused cell changes. The new focused cell has "focused" accessibility state. - Case: A MonthCalendar has 1 calendar.
Expected: Accessibility tree has 1 calendar. - Case: A MonthCalendar has several calendars.
Expected: Accessibility tree has the same count of calendars. - Case:
MinDateis set for a MonthCalendar.
Expected: Dates beforeMinDateare invisible and are not accessible. - Case:
MaxDateis set for a MonthCalendar.
Expected: Dates afterMaxDateare invisible and are not accessible. - Case:
MaxDateandMinDateare set for a MonthCalendar.
They are has a more date range then the display range of the MonthCalendar.
Expected: Accessibility tree has all visible calendars. All dates are accessible. - Case:
MaxDateandMinDateare set for a MonthCalendar.
They are has a less date range then the display range of the MonthCalendar.
Thereby the MonthCalendar has several partially visible calendars
(e.g. the MonthCalendar can contain 6 calendars, but 3 of them are visible dueMinDateandMinDate).
Expected: Accessibility tree has the count of visible calendars only (e.g. 3).
Invisible calendars are not accessible. Invisible dates of partial calendars are not accessible.
- Year view
- Case: Change the Today date (set
TodayDateof a MonthCalendar)
Expected: Nothing happens - Case: Click on a gray month cell (of the next or previous calendars)
Expected: The monthCalendar changes the display range. It accessibility tree rebuilds. - Case: Size of the control is changed that the control changes calendars count
Expected: The accessibility tree is rebuilt. ElementProviderFromPoint returns visible items correctly - Case: A calendar of a MonthCalendar has non-full rows
Expected: Inspect sees only visible items in that row - Case: A calendar of a MonthCalendar has some empty rows
Expected: These rows are not in the accessibility tree - Case: There are no week number and day of week cells in calendars
Expected: There are no any invisible items (week number and day of week cells) in the accessibility tree - Case: Select one month cell (e.g. September), user can't select several cell in this view,
so the selected cell is focused. SetMinDateof the calendar less then the selected cell (e.g. 1st of June).
Expected: The focused cell doesn't change. The focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. September), user can't select several cell in this view,
so the selected cell is focused. SetMinDateof the calendar more then the selected cell (e.g. 1st of December).
Expected: The focused cell changes (e.g. December). The new focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. September), user can't select several cell in this view,
so the selected cell is focused. SetMaxDateof the calendar less then the selected cell (e.g. 1st of June).
Expected: The focused cell changes (e.g. June). The new focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. September), user can't select several cell in this view,
so the selected cell is focused. SetMaxDateof the calendar more then the selected cell (e.g. 1st of December).
Expected: The focused cell doesn't change. The focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. September), user can't select several cell in this view,
so the selected cell is focused. SetMinDateof the calendar with the same month (e.g. 30th of September).
Expected: The focused cell doesn't change. The focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. September), user can't select several cell in this view,
so the selected cell is focused. SetMaxDateof the calendar with the same month (e.g. 1st of September).
Expected: The focused cell doesn't change. The focused cell has "focused" accessibility state (check Inspect). - Case:
MinDateis more then the selected range.
Expected: The focused cell changes. The new focused cell has "focused" accessibility state. - Case:
MaxDateis less then the selected range.
Expected: The focused cell changes. The new focused cell has "focused" accessibility state. - Case: A MonthCalendar has 1 calendar.
Expected: Accessibility tree has 1 calendar. - Case: A MonthCalendar has several calendars.
Expected: Accessibility tree has the same count of calendars. - Case:
MinDateis set for a MonthCalendar.
Expected: Dates beforeMinDateare invisible and are not accessible. - Case:
MaxDateis set for a MonthCalendar.
Expected: Dates afterMaxDateare invisible and are not accessible. - Case:
MaxDateandMinDateare set for a MonthCalendar.
They are has a more date range then the display range of the MonthCalendar.
Expected: Accessibility tree has all visible calendars. All dates are accessible. - Case:
MaxDateandMinDateare set for a MonthCalendar.
They are has a less date range then the display range of the MonthCalendar.
Thereby the MonthCalendar has several partially visible calendars
(e.g. the MonthCalendar can contain 6 calendars, but 3 of them are visible dueMinDateandMinDate).
Expected: Accessibility tree has the count of visible calendars only (e.g. 3).
Invisible calendars are not accessible. Invisible dates of partial calendars are not accessible.
- Decade view
- Case: Change the Today date (set
TodayDateof a MonthCalendar)
Expected: Nothing happens - Case: Click on a gray year cell (of the next or previous calendars)
Expected: The monthCalendar changes the display range. It accessibility tree rebuilds. - Case: Size of the control is changed that the control changes calendars count
Expected: The accessibility tree is rebuilt. ElementProviderFromPoint returns visible items correctly - Case: A calendar of a MonthCalendar has non-full rows
Expected: Inspect sees only visible items in that row - Case: A calendar of a MonthCalendar has some empty rows
Expected: These rows are not in the accessibility tree - Case: There are no week number and day of week cells in calendars
Expected: There are no any invisible items (week number and day of week cells) in the accessibility tree - Case: Select one month cell (e.g. 2020), user can't select several cell in this view,
so the selected cell is focused. SetMinDateof the calendar less then the selected cell (e.g. 1st of June 2019).
Expected: The focused cell doesn't change. The focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. 2020), user can't select several cell in this view,
so the selected cell is focused. SetMinDateof the calendar more then the selected cell (e.g. 1st of December 2021).
Expected: The focused cell changes (e.g. 2021). The new focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. 2020), user can't select several cell in this view,
so the selected cell is focused. SetMaxDateof the calendar less then the selected cell (e.g. 1st of June 2019).
Expected: The focused cell changes (e.g. 2019). The new focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. 2020), user can't select several cell in this view,
so the selected cell is focused. SetMaxDateof the calendar more then the selected cell (e.g. 1st of December 2021).
Expected: The focused cell doesn't change. The focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. 2020), user can't select several cell in this view,
so the selected cell is focused. SetMinDateof the calendar with the same year (e.g. 31th of December 2020).
Expected: The focused cell doesn't change. The focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. 2020), user can't select several cell in this view,
so the selected cell is focused. SetMaxDateof the calendar with the same year (e.g. 1st of January 2020).
Expected: The focused cell doesn't change. The focused cell has "focused" accessibility state (check Inspect). - Case:
MinDateis more then the selected range.
Expected: The focused cell changes. The new focused cell has "focused" accessibility state. - Case:
MaxDateis less then the selected range.
Expected: The focused cell changes. The new focused cell has "focused" accessibility state. - Case: A MonthCalendar has 1 calendar.
Expected: Accessibility tree has 1 calendar. - Case: A MonthCalendar has several calendars.
Expected: Accessibility tree has the same count of calendars. - Case:
MinDateis set for a MonthCalendar.
Expected: Dates beforeMinDateare invisible and are not accessible. - Case:
MaxDateis set for a MonthCalendar.
Expected: Dates afterMaxDateare invisible and are not accessible. - Case:
MaxDateandMinDateare set for a MonthCalendar.
They are has a more date range then the display range of the MonthCalendar.
Expected: Accessibility tree has all visible calendars. All dates are accessible. - Case:
MaxDateandMinDateare set for a MonthCalendar.
They are has a less date range then the display range of the MonthCalendar.
Thereby the MonthCalendar has several partially visible calendars
(e.g. the MonthCalendar can contain 6 calendars, but 3 of them are visible dueMinDateandMinDate).
Expected: Accessibility tree has the count of visible calendars only (e.g. 3).
Invisible calendars are not accessible. Invisible dates of partial calendars are not accessible.
- Century view
- Case: Change the Today date (set
TodayDateof a MonthCalendar)
Expected: Nothing happens - Case: Click on a gray decade cell (of the next or previous calendars)
Expected: The monthCalendar changes the display range. It accessibility tree rebuilds. - Case: Size of the control is changed that the control changes calendars count
Expected: The accessibility tree is rebuilt. ElementProviderFromPoint returns visible items correctly - Case: A calendar of a MonthCalendar has non-full rows
Expected: Inspect sees only visible items in that row - Case: A calendar of a MonthCalendar has some empty rows
Expected: These rows are not in the accessibility tree - Case: There are no week number and day of week cells in calendars
Expected: There are no any invisible items (week number and day of week cells) in the accessibility tree - Case: Select one month cell (e.g. 2020), user can't select several cell in this view,
so the selected cell is focused. SetMinDateof the calendar less then the selected cell (e.g. 1st of June 2019).
Expected: The focused cell doesn't change. The focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. 2020), user can't select several cell in this view,
so the selected cell is focused. SetMinDateof the calendar more then the selected cell (e.g. 1st of December 2021).
Expected: The focused cell changes (e.g. 2021). The new focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. 2020), user can't select several cell in this view,
so the selected cell is focused. SetMaxDateof the calendar less then the selected cell (e.g. 1st of June 2019).
Expected: The focused cell changes (e.g. 2019). The new focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. 2020), user can't select several cell in this view,
so the selected cell is focused. SetMaxDateof the calendar more then the selected cell (e.g. 1st of December 2021).
Expected: The focused cell doesn't change. The focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. 2020), user can't select several cell in this view,
so the selected cell is focused. SetMinDateof the calendar with the same year (e.g. 31th of December 2020).
Expected: The focused cell doesn't change. The focused cell has "focused" accessibility state (check Inspect). - Case: Select one month cell (e.g. 2020), user can't select several cell in this view,
so the selected cell is focused. SetMaxDateof the calendar with the same year (e.g. 1st of January 2020).
Expected: The focused cell doesn't change. The focused cell has "focused" accessibility state (check Inspect). - Case:
MinDateis more then the selected range.
Expected: The focused cell changes. The new focused cell has "focused" accessibility state. - Case:
MaxDateis less then the selected range.
Expected: The focused cell changes. The new focused cell has "focused" accessibility state. - Case: A MonthCalendar has 1 calendar.
Expected: Accessibility tree has 1 calendar. - Case: A MonthCalendar has several calendars.
Expected: Accessibility tree has the same count of calendars. - Case:
MinDateis set for a MonthCalendar.
Expected: Dates beforeMinDateare invisible and are not accessible. - Case:
MaxDateis set for a MonthCalendar.
Expected: Dates afterMaxDateare invisible and are not accessible. - Case:
MaxDateandMinDateare set for a MonthCalendar.
They are has a more date range then the display range of the MonthCalendar.
Expected: Accessibility tree has all visible calendars. All dates are accessible. - Case:
MaxDateandMinDateare set for a MonthCalendar.
They are has a less date range then the display range of the MonthCalendar.
Thereby the MonthCalendar has several partially visible calendars
(e.g. the MonthCalendar can contain 6 calendars, but 3 of them are visible dueMinDateandMinDate).
Expected: Accessibility tree has the count of visible calendars only (e.g. 3).
Invisible calendars are not accessible. Invisible dates of partial calendars are not accessible.
Additional dev points that don't affect users
- Rebuild the accessibility tree, if the calendar
View,MaxDate,MinDate,TodayDate,Sizeare changed
or Next or Previous buttons are clicked (i.e. the display range is changed). - Invoke method of button accessible objects of MonthCalendar simulates mouse moving, clicks and then returns the mouse position back.
Windows doesn't provide API to simulate MonthCalendar buttons clicks via messages.
Analyzers AppManifestAnalyzer.Help (`docs/analyzers/AppManifestAnalyzer.Help.md`)
How to use System.Windows.Forms.Analyzers
System.Windows.Forms.Analyzers analyzers and source generators are shipped inbox with Windows Desktop .NET SDK, and
are automatically referenced for Window Forms .NET applications.
`AppManifestAnalyzer`
AppManifestAnalyzer is automatically invoked when a Windows Forms application (OutputType=Exe or OutputType=WinExe) has a custom app.manifest.
[WFO0003](https://aka.ms/winforms-warnings/wfo0003): Unsupported high DPI configuration.
Windows Forms applications should specify application DPI-awareness via the application configuration orApplication.SetHighDpiMode API.
In NET6.0 the same warning was shipped with ID WFAC010.
| Item | Value |
|---|---|
| Category | ApplicationConfiguration |
| Enabled | True |
| Severity | Warning |
| CodeFix | False |
| Changed in | NET9.0 |
Analyzers ApplicationConfigurationGenerator.Help (`docs/analyzers/ApplicationConfigurationGenerator.Help.md`)
How to use System.Windows.Forms.Analyzers.CSharp
System.Windows.Forms.Analyzers.CSharp analyzers and source generators are shipped inbox with Windows Desktop .NET SDK, and
are automatically referenced for Window Forms .NET applications.
`ApplicationConfigurationGenerator`
ApplicationConfigurationGenerator is automatically invoked when ApplicationConfiguration.Initialize() call is detected,
and it emits the application bootstrap code such as:
public static void Initialize()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.SetHighDpiMode(HighDpiMode.SystemAware);
}For more information on application configuration refer to https://aka.ms/applicationconfiguration.
[WFO0001](https://aka.ms/winforms-warnings/wfo0001): Unsupported project type.
Only projects with OutputType set to "Exe" or "WinExe" are supported, because only applications projects define an application entry point,
where the application bootstrap code must reside.
In NET6.0 the same error was shipped with ID WFAC001.
| Item | Value |
|---|---|
| Category | ApplicationConfiguration |
| Enabled | True |
| Severity | Error |
| CodeFix | False |
| Changed in | NET9.0 |
[WFO0002](https://aka.ms/winforms-warnings/wfo0002): Unsupported property value.
The specified project property cannot be set to the given value.
In NET6.0 the same error was shipped with ID WFAC002.
| Item | Value |
|---|---|
| Category | ApplicationConfiguration |
| Enabled | True |
| Severity | Error |
| CodeFix | False |
| Changed in | NET9.0 |
Analyzers Experimental.Help (`docs/analyzers/Experimental.Help.md`)
List of WinForms Experimental Function Areas
System.Windows.Forms.Analyzers analyzers and source generators are shipped inbox with Windows Desktop .NET SDK, and
are automatically referenced for Window Forms .NET applications.
| Diagnostic ID | Description |
|---|---|
WFO5001 |
Functionality around Dark Mode Colors is experimental in .NET 9. |
WFO5002 |
New ASync APIs for e.g. consuming Windows projected APIs, WebView2 or AI/LLM Libs are experimental in .NET 9. |
Analyzers WinFormsCSharpAnalyzers.Help (`docs/analyzers/WinFormsCSharpAnalyzers.Help.md`)
How to use System.Windows.Forms.Analyzers
System.Windows.Forms.Analyzers analyzers and source generators are shipped inbox with Windows Desktop .NET SDK, and
are automatically referenced for Window Forms .NET applications.
`MissingPropertySerializationConfiguration`
MissingPropertySerializationConfiguration checks for missing DesignerSerializationVisibilityAttribute on properties of classes which are
derived from Control and could potentially serialize design-time data by the designer without the user being aware of it.
[WFO1000](https://aka.ms/winforms-warnings/wfo1000): Missing property serialization configuration.
Properties of classes derived from Control should have DesignerSerializationVisibilityAttribute
set to DesignerSerializationVisibility.Content or DesignerSerializationVisibility.Visible.
| Item | Value |
|---|---|
| Category | WinForms Security |
| Enabled | True |
| Severity | Error |
| CodeFix | False |
| Added in | NET9.0 |
`AvoidPassingFuncReturningTaskWithoutCancellationToken`
AvoidPassingFuncReturningTaskWithoutCancellationToken checks parameters passed to Control.InvokeAsync. It suggests to use a cancellation token when passing a task to these methods.
[WFO2001](https://aka.ms/winforms-warnings/wfo2001): Task is being passed to InvokeAsync without a cancellation token.
Avoid passing a Func<T> to InvokeAsync where T is a Task or ValueTask, unless your intention is for the delegate to simply be kicked off as an unsupervised task. Instead, use Func<CancellationToken, ValueTask> or Func<CancellationToken, ValueTask<T>>, so that the delegate passed to InvokeAsync can be awaited, allowing exceptions to be properly handled.
| Item | Value |
|---|---|
| Category | WinForms Usage |
| Enabled | True |
| Severity | Warning |
| CodeFix | False |
| Added in | NET9.0 |
`ImplementITypedDataObject`
ImplementITypedDataObject checks custom implementations of the managed IDataObject interface and suggests to also implement the ITypedDataObject interface.
[WFO1001](https://aka.ms/winforms-warnings/wfo1001): `IDataObject` type does not implement `ITypedDataObject`.
Types should implement ITypedDataObject to support best practices when interacting with data. Types will not work with typed APIs in Clipboard and other data exchange scenarios if they only implement IDataObject.
| Item | Value |
|---|---|
| Category | WinForms Security |
| Enabled | True |
| Severity | Warning |
| CodeFix | False |
| Added in | NET10.0 |
WinForms Designer guardrails
These rules protect the generated partial declaration in .Designer.cs files. A file is analyzed only
when it contains an instance, non-generic, parameterless void InitializeComponent for a partial type
derived from System.Windows.Forms.Control or System.ComponentModel.Component, and that same type
also has a declaration in a non-Designer file. Framework symbols are resolved semantically: unrelated
types named Control or Component do not qualify.
The boundary is design-time round-tripping, not runtime equivalence. CodeDOM cannot express many
modern language constructs, and the WinForms Designer interprets only a subset of the statements
CodeDOM can represent. Keep initialization unrolled: explicit construction, assignments, supported
calls, and named event hookups. Move runtime-only behavior to the user partial afterInitializeComponent; do not hide designer initialization in a helper call.
[WFO3000](https://aka.ms/winforms-warnings/wfo3000): Avoid unsupported code in `InitializeComponent`.
Move loops (including deconstructing foreach), conditionals, switch constructs, local functions,goto, exception handling, locking, using, and await out of generated initialization.
Use a block-bodied InitializeComponent, not an expression-bodied method. These restrictions do not
apply to the standard generated Dispose override.
[WFO3001](https://aka.ms/winforms-warnings/wfo3001): Keep custom members out of Designer files.
Move properties, nested types, and custom methods out of the generated partial declaration.
Constructors, InitializeComponent, the standard Dispose(bool) override, and explicit interface
implementations are allowed.
[WFO3002](https://aka.ms/winforms-warnings/wfo3002): Keep generated fields at the end of the Designer file.
Keep fields at the end of the Designer partial, except the conventional IContainer components
infrastructure. This ordering preserves the historical generated layout; it is not itself a CodeDOM
expression restriction. Component fields constructed in InitializeComponent must belong to that
partial. Merely reading a user field does not make it designer-owned, and inherited, static, or
unrelated-type fields must not be moved into it.
[WFO3003](https://aka.ms/winforms-warnings/wfo3003): Keep event and delegate declarations out of Designer files.
Move event and delegate declarations to the user code file.
[WFO3004](https://aka.ms/winforms-warnings/wfo3004): Avoid collection expressions in Designer files.
Use syntax supported by Designer serialization instead of C# collection expressions in generated
Designer code, for example new Control[] { button1, button2 } or unrolled Add calls.
[WFO3005](https://aka.ms/winforms-warnings/wfo3005): Avoid `nameof` expressions in `InitializeComponent`.
Use the serialized string value instead of nameof.
[WFO3006](https://aka.ms/winforms-warnings/wfo3006): Avoid conditional expressions in `InitializeComponent`.
Move the condition to the user code file and serialize one deterministic value.
[WFO3007](https://aka.ms/winforms-warnings/wfo3007): Avoid null-coalescing expressions in `InitializeComponent`.
Resolve the fallback outside generated Designer code. This includes both ?? and ??=.
[WFO3008](https://aka.ms/winforms-warnings/wfo3008): Avoid null-conditional expressions in `InitializeComponent`.
Move null-dependent access to the user code file.
[WFO3009](https://aka.ms/winforms-warnings/wfo3009): Avoid interpolated strings in `InitializeComponent`.
Serialize the resulting string value instead of an interpolated expression.
[WFO3010](https://aka.ms/winforms-warnings/wfo3010): Avoid anonymous functions in `InitializeComponent`.
Use a named event handler in the user code file instead of a lambda or anonymous method.
WFO3005βWFO3010 identify constructs that CodeDOM cannot represent. Their separate IDs allow tools and
agents to apply construct-specific guidance.
| Item | Value |
|---|---|
| Category | WinForms Designer |
| Enabled | True |
| Severity | Error (WFO3000, WFO3001, WFO3003, WFO3004, WFO3006βWFO3008, WFO3010); Warning (WFO3002, WFO3005, WFO3009) |
| CodeFix | False |
| Added in | NET11.0 |
`PropertyAllocatesNewInstanceAnalyzer`
[WFO2000](https://aka.ms/winforms-warnings/wfo2000): Avoid constructing fresh reference instances in property getters.
This is a general usage warning, not a Designer-only restriction. It identifies getter return paths
that directly construct a reference instance, without claiming that every access takes that path.
This includes directly returned arrays and anonymous objects.
Value-type construction, cached and initializer-backed properties, indexers, generated code, and
returns inside nested lambdas/local functions are excluded. User-defined conversions are not treated
as transparent returns. The rule does not perform interprocedural factory analysis.
Cache the instance only when stable identity is intended. For deliberate creation, consider a factory
method if API compatibility permits, or document a narrow suppression. There is no automatic fix:
caching can change ownership, disposal, and threading behavior. In particular, fresh instances from
content-serialized properties can discard property-grid edits instead of preserving designer state.
| Item | Value |
|---|---|
| Category | WinForms Usage |
| Enabled | True |
| Severity | Warning |
| CodeFix | False |
| Added in | NET11.0 |
Analyzers WinFormsVisualBasicAnalyzers.Help (`docs/analyzers/WinFormsVisualBasicAnalyzers.Help.md`)
How to use System.Windows.Forms.Analyzers
System.Windows.Forms.Analyzers analyzers and source generators are shipped inbox with Windows Desktop .NET SDK, and
are automatically referenced for Window Forms .NET applications.
`MissingPropertySerializationConfiguration`
MissingPropertySerializationConfiguration checks for missing DesignerSerializationVisibilityAttribute on properties of classes which are
derived from Control and could potentially serialize design-time data by the designer without the user being aware of it.
[WFO1000](https://aka.ms/winforms-warnings/wfo1000): Missing property serialization configuration.
Properties of classes derived from Control should have DesignerSerializationVisibilityAttribute
set to DesignerSerializationVisibility.Content or DesignerSerializationVisibility.Visible.
| Item | Value |
|---|---|
| Category | WinForms Security |
| Enabled | True |
| Severity | Error |
| CodeFix | False |
| Added in | NET9.0 |
`AvoidPassingFuncReturningTaskWithoutCancellationToken`
AvoidPassingFuncReturningTaskWithoutCancellationToken checks parameters passed to Control.InvokeAsync. It suggests to use a cancellation token when passing a task to these methods.
[WFO2001](https://aka.ms/winforms-warnings/wfo2001): Task is being passed to InvokeAsync without a cancellation token.
Avoid passing a Func<T> to InvokeAsync where T is a Task or ValueTask, unless your intention is for the delegate to simply be kicked off as an unsupervised task. Instead, use Func<CancellationToken, ValueTask> or Func<CancellationToken, ValueTask<T>>, so that the delegate passed to InvokeAsync can be awaited, allowing exceptions to be properly handled.
| Item | Value |
|---|---|
| Category | WinForms Usage |
| Enabled | True |
| Severity | Warning |
| CodeFix | False |
| Added in | NET9.0 |
`ImplementITypedDataObject`
ImplementITypedDataObject checks custom implementations of the managed IDataObject interface and suggests to also implement the ITypedDataObject interface.
[WFO1001](https://aka.ms/winforms-warnings/wfo1001): `IDataObject` type does not implement `ITypedDataObject`.
Types should implement ITypedDataObject to support best practices when interacting with data. Types will not work with typed APIs in Clipboard and other data exchange scenarios if they only implement IDataObject.
| Item | Value |
|---|---|
| Category | WinForms Security |
| Enabled | True |
| Severity | Warning |
| CodeFix | False |
| Added in | NET10.0 |
WinForms Designer guardrails
These rules protect the generated partial declaration in .Designer.vb files. A file is analyzed only
when it contains an instance, non-generic, parameterless Sub InitializeComponent for a partial type
derived from System.Windows.Forms.Control or System.ComponentModel.Component, and that same type
also has a declaration in a non-Designer file. Framework symbols are resolved semantically: unrelated
types named Control or Component do not qualify. Identifier matching respects VB casing rules and
optional parameter-list parentheses.
The boundary is design-time round-tripping, not runtime equivalence. CodeDOM cannot express many
modern constructs, and the WinForms Designer interprets only a subset of CodeDOM statements.
Keep initialization unrolled and move runtime-only behavior to the user partial afterInitializeComponent; do not hide designer initialization behind helper calls.
[WFO3000](https://aka.ms/winforms-warnings/wfo3000): Avoid unsupported code in `InitializeComponent`.
Move For, For Each, While, Do, If, Select, GoTo, Try, SyncLock, Using, and Await
out of generated initialization. The standard generated Dispose override is intentionally exempt:
its Try/If/Finally structure is valid infrastructure.
[WFO3001](https://aka.ms/winforms-warnings/wfo3001): Keep custom members out of Designer files.
Move properties, nested types, and custom methods out of the generated partial declaration.
Constructors, InitializeComponent, the standard Dispose(Boolean) override, and explicit interface
method implementations are allowed.
[WFO3002](https://aka.ms/winforms-warnings/wfo3002): Keep generated fields at the end of the Designer file.
Keep fields and WithEvents declarations at the end of the Designer partial, except the conventionalIContainer components infrastructure. This preserves the historical generated layout rather than
enforcing a modern application-code style. Component members constructed in InitializeComponent
belong in that partial; merely reading a user field does not make it designer-owned. Inherited and
unrelated-type members must not be moved.
VB WithEvents declarations bind as property symbols. They are explicitly handled without treating
ordinary properties as generated fields.
[WFO3003](https://aka.ms/winforms-warnings/wfo3003): Keep event and delegate declarations out of Designer files.
Move event and delegate declarations to the user code file.
WFO3004 is C#-only because Visual Basic has no collection-expression syntax.
[WFO3005](https://aka.ms/winforms-warnings/wfo3005): Avoid `NameOf` expressions in `InitializeComponent`.
Use the serialized string value instead of NameOf.
[WFO3006](https://aka.ms/winforms-warnings/wfo3006): Avoid conditional expressions in `InitializeComponent`.
Move the three-argument If condition to the user code file and serialize one deterministic value.
[WFO3007](https://aka.ms/winforms-warnings/wfo3007): Avoid null-coalescing expressions in `InitializeComponent`.
Resolve the two-argument If fallback outside generated Designer code.
[WFO3008](https://aka.ms/winforms-warnings/wfo3008): Avoid null-conditional expressions in `InitializeComponent`.
Move null-dependent access to the user code file.
[WFO3009](https://aka.ms/winforms-warnings/wfo3009): Avoid interpolated strings in `InitializeComponent`.
Serialize the resulting string value instead of an interpolated expression.
[WFO3010](https://aka.ms/winforms-warnings/wfo3010): Avoid anonymous functions in `InitializeComponent`.
Use a named event handler in the user partial instead of a lambda. Use Handles for designer member
events and AddressOf for supported local-component event hookups.
WFO3005βWFO3010 identify constructs that CodeDOM cannot represent. Their separate IDs allow tools and
agents to apply construct-specific guidance.
[WFO3011](https://aka.ms/winforms-warnings/wfo3011): Declare Designer component members `WithEvents`.
A component member constructed by InitializeComponent must use the VB Designer's event model.
For example, keep Friend WithEvents Button1 As Button at the end of the Designer partial and putPrivate Sub Button1_Click(...) Handles Button1.Click in the user partial. The rule does not apply to
the components container, scalar/helper fields, or local variables.
[WFO3012](https://aka.ms/winforms-warnings/wfo3012): Use `Handles` for Designer member events.
Replace AddHandler Button1.Click, AddressOf Button1_Click in InitializeComponent withHandles Button1.Click on the named handler in the user partial, without retaining both hookups.
Root events preserve the receiver with Handles Me.EventName, Handles MyBase.EventName, orHandles MyClass.EventName; inherited WithEvents members also support Handles without
redeclaring them.
This is not a blanket ban on AddHandler. The designer's field-generation setting is namedGenerateMember: when it is False, components can be locals, and supportedAddHandler local.Click, AddressOf Handler wiring remains valid. Arbitrary properties, inherited
plain fields, unrelated receivers, and runtime event wiring outside InitializeComponent are not
rewritten into the member model. CodeDOM itself represents event attachment; the restriction protects
VB Designer ownership and event round-tripping.
WFO3011 and WFO3012 are VB-only. No automatic fix is offered because changing declarations and
subscriptions requires preserving captures, accessibility, and existing Handles clauses.
| Item | Value |
|---|---|
| Category | WinForms Designer |
| Enabled | True |
| Severity | Error (WFO3000, WFO3001, WFO3003, WFO3006βWFO3008, WFO3010βWFO3012); Warning (WFO3002, WFO3005, WFO3009) |
| CodeFix | False |
| Added in | NET11.0 |
`PropertyAllocatesNewInstanceAnalyzer`
[WFO2000](https://aka.ms/winforms-warnings/wfo2000): Avoid constructing fresh reference instances in property getters.
This remains a general usage warning, not a Designer-only restriction. It identifies getter return
paths that directly construct reference instances, not guaranteed allocation on every access.
This includes directly returned arrays and anonymous objects.
Value types, cached and initializer-backed properties, indexers, generated code, nested lambda
returns, and user-defined conversions are excluded.
Cache only when stable identity is intended. Otherwise consider a factory method when API
compatibility permits, or document a narrow suppression. No automatic fix changes ownership,
disposal, or threading semantics. Content-serialized properties returning fresh instances are a
particular design-time hazard because property-grid edits can be applied to discarded instances.
| Item | Value |
|---|---|
| Category | WinForms Usage |
| Enabled | True |
| Severity | Warning |
| CodeFix | False |
| Added in | NET11.0 |
Analyzers Akams (`docs/analyzers/akams.md`)
These mappings are defined in the [redirection manager](https://akalinkmanager.trafficmanager.net/am/redirection/home?options=host:aka.ms)
Design Anchor Layout Changes In Net80 (`docs/design/anchor-layout-changes-in-net80.md`)
Anchor layout changes in .NET 8.0
Description
We have multiple issues reported in WinForms around the anchor layout being problematic on higher DPI scale monitors, irrespective of application DPI mode. This document outlines the changes being made in .NET 8.0 to address these issues, which is aligned with the larger goal to support all supported application DPI modes in WinForms.
Problem in Scope
The position of an anchored control with respect to its parent should be able to be determined at design time and should only need to be changed if there were explicit changes in the control's bounds or when the control is scaled in response to a DPI changed event. Bounds changes that happen as result of the parent control's bounds changing shouldn't alter a control's relative position in the parent's rectangle. However, currently the layout engine computes the anchored control's relative position every time there are changes to the control's bounds or the control's location-related properties.
For example, the following simple code snippet copied from a Control's InitializeComponent method demonstrates the impact of the "over-eager" computations. The number of unnecessary computations can increase quite dramatically for nested layouts.
// The following line triggers layout to compute anchors with the default button size and without a parent control.
this.button7.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
// The following line triggers layout again to compute anchors with the default button size with a new location but still without a parent control.
this.button7.Location = new System.Drawing.Point(9, 47);
// The following line triggers layout once again to compute anchors with the button's new size but still without a parent control.
this.button7.Size = new System.Drawing.Size(134, 23);
// The following line triggers layout to compute anchors with button's current size and the default size for the parent control.
this.Controls.Add(button7);
// The following line triggers layout to compute anchors with the button's current size and a new size for the parent control.
// These sizes still may be changed for the current DPI depending on application's DPI mode.
this.Size = new System.Drawing.Size(828, 146);
// The following line triggers layout to compute anchors with the button's current size and new size for the parent control.
// The current DPI is still not applied.
this.ResumeLayout(false);The above snippet does not represent the complete set of instances where anchor computations are unnecessary and may hold invalid anchor values. It gets even more complicated when nested UserControls are involved.
In addition, the original anchor calculation implementation was designed prior to adding high DPI support to WinForms.
Known issues
Customer reported issues can be largely grouped as follows:
Missing controls
Calculation of control's anchors with default sizes may result in negative anchors and, thus, result in an invalid location for the anchored controls.
Overlapped Controls
Related to the "Missing controls" issue above, if the parent control is scaled to match the current monitor's DPI, and the control's anchor calculations happen out of sync with this scaling, controls may overlap:
Proposed solution
The proposal is that we calculate anchors for a control only if the following conditions are met:
- The control is parented.
- The control's parent's layout is resumed.
The initial anchor calculations would now happen whenever parent's layout is resumed, or when control is parented/resized and parent's layout is not suspended. The anchors will be recalculated whenever there are changes in geometry (Size, Location, etc.) or hierarchy (ParentChanged).
Anchor calculations
The following image illustrates anchors calculation with respect to a control's parent's display rectangle. The "orange" rectangle is the parent's display rectangle, and the "blue" rectangle is the anchored control's (button) bounds.
Leftarrow indicates the X coordinate of the button location with respect to the parent's display rectangle.Toparrow indicates the Y coordinate of the button location with respect to the parent's display rectangle.Rightarrow indicates the distance from right edge of the parent's rectangle where button is placed.Rightarrow indicates the distance from the bottom edge of the parent's rectangle where the button is placed.
In the above image the boundary marked in "orange" is a container hosting the control (boundary marked with "blue"). The control can define its anchor property to explicitly tell the layout engine how its bounds change relative to its parent's bounds.
For example:
this.button14.Anchor = (System.Windows.Forms.AnchorStyles.Bottom
| System.Windows.Forms.AnchorStyles.Left
| System.Windows.Forms.AnchorStyles.Top
| System.Windows.Forms.AnchorStyles.Right );When the control's Anchor property is set, the anchors (that is, left, top, right, bottom values) are computed and stored in an internal struct AnchorInfo. The only time the anchor values can be negative is when the control is placed/position outside/overlapped with the hosting control's bounds.
The following code snippet demonstrates anchor calculations:
private static void ComputeAnchorInfo(IArrangedElement element)
{
AnchorInfo? anchorInfo = GetAnchorInfo(element);
if (anchorInfo is null)
{
anchorInfo = new();
SetAnchorInfo(element, anchorInfo);
}
Rectangle displayRect = element.Container.DisplayRectangle;
Rectangle elementBounds = GetCachedBounds(element);
int x = elementBounds.X;
int y = elementBounds.Y;
anchorInfo.Left = x;
anchorInfo.Top = y;
anchorInfo.Right = displayRect.Width - (x + elementBounds.Width);
anchorInfo.Bottom = displayRect.Height - (y + elementBounds.Height);
}Risk mitigation
The layout engine, in general, is quite complex, and any changes in it carry a very high risk. In order to reduce the risks and to provide backward compatibility, the new anchor calculations are put behind a feature switch System.Windows.Forms.AnchorLayoutV2. The switch is disabled by default for Windows Forms applications.
Developers need to opt-in to get this new feature by setting the switch value to true in the runtimeconfig.json.
Snippet for runtimeconfig.template.json:
{
"configProperties": {
"System.Windows.Forms.AnchorLayoutV2": true
}
}Designer Designer High Dpi Mode (`docs/designer/designer-high-dpi-mode.md`)
Designer HighDpi mode
Visual Studio 2022 and beyond by default launch in PerMonitor DpiAwareness mode but it also supports SystemAware and Unaware DpiAwareness modes when users launch it with custom settings. The WinForms designer does not fully support PerMonitor DpiAwareness mode yet and by default launches in SystemAware mode.
In .NET 6, as part of unifying the information sharing between WinForms designer and WinForms runtime, we added support for specifying the application dpi mode as a build time property on the project and share it for both designtime and at runtime.
Issues
The Winforms designer, when operating in SystemAware/PermonitorV2 mode, serializes layout metrics into the source file based on the Dpi setting of the current display device on which it is being rendered. This may lead to two kinds of problems.
WinForms applications developed on a machine with DPI settings different from the machine where they are executed may encounter layout inconsistencies.
Within a team of multiple developers collaborating on the same project, this situation can lead to unintentional modifications in the serialized source code, even if they are unrelated to the intended changes. As a consequence, it introduces additional overhead in the code review and merging process.
The following example demonstrates an undesired modification in the serialized WinForms source code.
Up until now, developers have been advised to adhere to the instructions given here and restart Visual Studio in DpiUnAware mode to resolve these problems. Nevertheless, this process requires individual developers to do it every time they work on the project, adding inconvenience. Additionally, the entire Visual Studio IDE would be launched as DPI Unaware resulting in a blurry IDE experience.
Solution
In Visual Studio 2022 Version 17.8, we are introducing a new configuration option called ForceDesignerDpiUnaware, distinct from ApplicationHighDpiMode. This option enables developers to enforce DpiUnAware mode for all WinForms designers in the project, regardless of the ApplicationHighDpiMode, which continues to take effect during runtime. The rest of the Visual Studio environment remains in PerMonitor mode and renders crisply.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationHighDpiMode>SystemAware</ApplicationHighDpiMode>
<ForceDesignerDpiUnaware>true</ForceDesignerDpiUnaware>
</PropertyGroup>
</Project>Designer Designer Selection (`docs/designer/designer-selection.md`)
Designer Selection Feature for .NET Framework Projects
Problem
Visual Studio 2022 is a 64-bit process and it cannot load 32-bit assemblies. Due to this technical limitation, Windows Forms .NET Framework in-process designer, which runs in the Visual Studio process, cannot load controls present in 32-bit assemblies.
This has been a major Visual Studio 2022 adoption blocker for customers who have Windows Forms .NET Framework projects which utilize ActiveX/COM controls or other custom controls contained in 32-bit assemblies and which cannot be updated to AnyCPU or to .NET. Until now, such projects could only use Windows Forms designer in Visual Studio 2019 because it is a 32-bit process.
Solution
To address this problem, we have been adding support for .NET Framework projects in Windows Forms out-of-process designer in last few Visual Studio 2022 releases.
This feature is still in an early preview state, and we are collecting developers' feedback on it. The Visual Studio 17.9 Preview 2 release had added the following enhancements:
- Improved Type Resolution for .NET Framework projects.
- ActiveX/COM support for both .NET Framework and .NET projects.
- Designer Selection Feature to track 32-bit assembly load failures in .NET Framework projects.
When a failure is detected, Designer Selection Feature presents following dialog to the user to select appropriate designer for the project:
On selecting Yes, the project will be reloaded and Windows Forms out-of-process designer will be used for the project. When project is targeting x86 platform, the designer will spawn a 32-bit process. When project is targeting AnyCPU or x64 platform, the designer will spawn a 64-bit process. Control assemblies will be loaded in that process.
On selecting No, the project will continue to use in-process designer.
With Yes/No buttons, the designer selection will be remembered for current Visual Studio instance only. To add the designer selection as a project configuration property automatically, enable option Remember for current project. It will add UseWinFormsOutOfProcDesigner property to each project configuration. Windows Forms designer will read this property value to select desired designer automatically the next time project is open in Visual Studio. Here is a sample project configuration after adding this property:
The newly added property is:
<UseWinFormsOutOfProcDesigner>True</UseWinFormsOutOfProcDesigner>This feature will solve the cases of forms having controls that have modern designers, i.e., the ones referencing the new out-of-process designer SDK or some simple controls which do not use complex designer features. However, this feature will not work for forms with controls that use complex designer features and as a result they must be updated to use out-of-process designer extensibility model.
Note that the Designer Selection feature is currently under following preview feature flag which is enabled by default:
This feature flag is present in Visual Studio under Tools -> Options -> Preview Features.
Following InfoBar will show up when out-of-process designer is used for .NET Framework projects:
:warning: Warning
Since the out-of-process designer for .NET Framework projects cannot handle all third-party controls, there is a possibility of data loss on using this feature. Hence, we recommend that you create a backup of your project beforehand.
Also, there is a difference in the way CodeDom parsing is handled between in-process and out-of-process designers. The out-of-process designer will likely make significant updates to the code behind file which might make it incompatible with in-process designer. This issue will be addressed in an upcoming release.
Next Steps
Following are the list of features which we plan to add support for in upcoming Visual Studio releases:
- Disable code style and formatting changes for .NET Framework projects utilizing out-of-process designer (see Modernization of Code Behind in out-of-process designer).
- Add Toolbox support for third-party and custom controls.
- Add setting for
UseWinFormsOutOfProcDesignerproperty in project property pages.
Designer Net Inproc Designer (`docs/designer/net-inproc-designer.md`)
How to use Windows Forms .NET Framework designer for .NET applications
The OOP designer is a port of the original .NET Framework Windows Forms designer that was run inside the Visual Studio's process. And whilst the team is aiming for parity there are still functional gaps between the original designer and the new OOP designer. With that it is possible that you may find yourself in a situtation where the OOP designer is not working as expected, and you'd want to use the .NET Framework designer to carry you on.
This guide will walk you through the steps that enable a substantial use of the .NET Framework designer to design Windows Forms .NET applications. Please note, that due to the changes in the default font and new APIs in Windows Forms .NET, the .NET Framework designer won't be able to provide 100% parity.
1. Create Windows Forms .NET app
Create a new Windows Forms application targeting .NET from Visual Studio or your favorite command line interface.
Create in Visual Studio
VS2022:
- File > Add > New Project... > Windows Forms App (.NET Core), choose C# or Visual Basic
- Specify project name, e.g.
SimpleWinForms
Create from command line
Open your favorite console, create a new folder for your application:
md SimpleWinForms
cd SimpleWinForms
dotnet new winforms -n SimpleWinForms
dotnet new sln
dotnet sln add SimpleWinForms:point_up: TIP: You can have the folder name different from the project's name. Use the option -n (or -name) for that when using dotnet new.
:point_up: TIP: For Visual Basic projects use the option -lang vb when using dotnet new.
After creating the project, you can run the application:
dotnet run --project SimpleWinForms\SimpleWinForms.csproj2. Enable .NET Framework designer support
There are few options available to help you to design UI for your .NET Core project.
Option 1: multi-target your app
Open
SimpleWinForms.slnOpen the
SimpleWinFormsproject file by double clicking on it in Solution Explorer. Change theTargetFrameworkproperty from:diff- <TargetFramework>net6.0-windows</TargetFramework> + <TargetFrameworks>net472;net6.0-windows</TargetFrameworks>Add for any and every form file you have in this
ItemGroup:xml<ItemGroup Condition="'$(TargetFramework)' == 'net472'"> <Compile Update="Form1.cs"> <SubType>Form</SubType> </Compile> <Compile Update="Form1.Designer.cs"> <DependentUpon>Form1.cs</DependentUpon> </Compile> </ItemGroup>After doing these steps this is what you should end up with:

Option 2: Shadow .NET Frameowk project
Open
SimpleWinForms.slnAdd a new Windows Forms App (.NET Framework) project (VS2017: Visual C# > Windows Desktop / VS2019+: C# : Windows : Desktop) to the solution (File > Add > New Project...)

Name the new .NET Framework project as the .NET project, but add ".Designer" to it (e.g.
SimpleWinForms.Designer)Go to the .NET Framework project properties and set the default namespace to the .NET project's namespace

Delete the existing Form files in both projects
Add a new Windows Form in the .NET Framework project's context menu Add > Windows Form...

In the section list, click on Windows Forms, and chose Windows Form from the installed templates

Give the name and click
[Add].:exclamation: IMPORTANT: You need to trigger a form change event for the Designer to create a
resxfile. You can do it by resizing the form for a couple of pixels or changing the form'sTextproperty. Don't forget to save.Now we move the form to the .NET project.
In the Solution Explorer click on the form and press CTRL+X to cut it; and then paste it in to the .NET project (CTRL+V). Check that the main form file, the .designer file and the resource file for the form are all present.Then we link the form back into the .NET Framework project back.
Remember: We can only use the .NET Framework designer, but we want to have only one set of files. So the form files, of course, belong to the .NET project but we want to edit them in the context of the .NET Framework project (thus using the .NET Framework designer).To do this open the context menu on the .NET Framework project in the Solution Explorer, and pick Add > Existing Item
In the File Open Dialog, navigate to the .NET project, select the Form.cs, Form.Designer.cs and Form.resx files and choose Add as Link option.

Compile the solution to see if the file references were set up correctly
As the last but important step, we need to re-nest the linked form files.
If you installed the File Nesting Visual Studio Extension then that is done easily: Select both the Form.Designer.cs and Form.resx file, and from the context menu click File Nesting > Nest Items. In the dialog, pick the main form file (Form.cs), and click OK.
Now, whenever you need to use the Designer on one of the .NET Form or UserControl files, simply open the linked files in the .NET Framework project with the Windows Forms Designer.
More information
If you are porting an existing .NET Framework application to .NET you may wish to read the following blog posts:
- Porting desktop apps to .NET
- .NET Upgrade Assistant
- Using the Windows Forms designer for .NET projects
Designer Troubleshooting (`docs/designer/troubleshooting.md`)
How to troubleshoot the Windows Forms Out-Of-Process designer
Whilst the Windows Forms Out-Of-Process Designer (or the OOP designer) is by far and large sort of a black box to the users, it actually provides some good means to find out more information on its state.
Logging
Once the OOP designer is started, its log is available in the Visual Studio Output window:
The default verbosity is set to Warnings, which includes Warnings and Errors (prior to VS 2022 17.2 was None, i.e. logging disabled). You can change the logging level to terser or more verbose under Tools -> Options -> Windows Forms Designer -> General -> Logging Level, e.g.:
"Designer never loads" problems and possible ways to resolve those
Connection Timeouts
When the OOP designer starts it launches the designer server process (aka DesignToolsServer.exe). If the server process fails to launch or connect to the Visual Studio, you may see the following error:
[02:59:20.7198643] [MyProj]: Timed out waiting for the design tools server process launch to complete.
[02:59:20.7668637] Microsoft.DotNet.DesignTools.Client.ServerException: Timed out waiting for the design tools server process launch to complete.The default connection timeout is set to 2 minutes (prior to VS 2022 17.1p5 was 10 seconds). VS 2022 17.2 introduces the ability to increase the connection timeout, if necessary. You can change it under Tools -> Options -> Windows Forms Designer -> General -> Connection timeout, e.g.:
There can be various reasons why the server process may be taking longer than the allocated time. For example, some users have reported to have solutions with a large number of projects. The designer performs a shadow copying of the necessary files and folders before it can render your form or control, and if you have a slow hard drive, or the project files are located on a network drive, it can have a significant effect on how fast files are copied to the shadow cache folder.
Antivirus is blocking the server process executables
Some customers have reported that the connection timeouts were caused by configurations of their antivirus services. In such cases it is recommended to add exclusion rules for devenv.exe and DesignToolsServer.exe, and this can unblock these executables and can increase the I/O of the shadow cache. For example, to add an exception to the Windows Defender so that it is not slowed down by the system call hooks Defender uses to provide real-time protection can be done with the following PowerShell command:
Add-MpPreference -ExclusionProcess 'devenv.exe'
Add-MpPreference -ExclusionProcess 'DesignToolsServer.exe'`Switch.System.IO.UseLegacyPathHandling` override is enabled
The designer server process uses long paths and requires path normalizations, the behaviours that can be altered by Switch.System.IO.UseLegacyPathHandling appcontext switch. The default value of the switch is false, and if the value is set to true either in devenv.exe.config (which is located somewhere like C:\Program Files\Microsoft Visual Studio\2022\Common7\IDE) or in the registry it will prevent the designer from loading with Argument Exception: Illegal characters in path error.
Infra Automation (`docs/infra/automation.md`)
Automation
Fabric Bot
This repository uses Fabric Bot to automate issue and pull request management. All automation rules are defined in the .github/fabricbot.json file.
At this moment Fabric Bot has not published a JSON schema for the configuration format. In order to make changes to the configuration file, you should use the Fabric Bot portal to load the configuration file via the "Import Configuration" option and make changes using the editor. You need to be signed out from the portal in order for this option to appear.
Serialization Register Custom Type Converter (`docs/serialization/register-custom-type-converter.md`)
Manually Registering a TypeConverter to a Class
A type converter is usually bound to a class by adding the TypeConverterAttribute to the class. When you cannot apply this attribute or want to override it you can programmatically assign a converter instead. This document outlines how to accomplish this.
Prerequisite
Follow the steps to implement your own custom type converter listed here.
Registering Your TypeConverter
Background
A TypeConverter for a given type is found via the TypeDescriptor static method GetConverter(). To provide custom TypeDescriptor behavior you must create and register a custom type descriptor (ICustomTypeDescriptor) via a TypeDescriptionProvider.
Implementing An ICustomTypeDescriptor
Inherit from CustomTypeDescriptor to get default ICustomTypeDescriptor behavior and override GetConverter() to return your custom type converter.
private class CustomConverterDescriptor : CustomTypeDescriptor
{
private TypeConverter _converter;
public CustomConverterDescriptor(ICustomTypeDescriptor parent, TypeConverter converter) : base(parent)
=> _converter = converter;
public override TypeConverter GetConverter() => _converter;
}An ICustomTypeDescriptor is accessed through TypeDescriptionProvider.GetTypeDescriptor(). This method needs to be overridden to provide your newly created ICustomTypeDescriptor. Have another class inherit from TypeDescriptionProvider to preserve the default behavior and override GetTypeDescriptor() to provide CustomConverterDescriptor.
public class CustomConverterProvider : TypeDescriptionProvider
{
private TypeConverter _converter;
public CustomConverterProvider(TypeDescriptionProvider parent, TypeConverter converter) : base(parent)
=> _converter = converter;
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
=> new CustomConverterDescriptor(base.GetTypeDescriptor(objectType, instance), _converter);
// Consider nesting CustomConverterDescriptor here
}Note: The parent TypeDescriptionProvider to be passed to the constructor can be found via TypeDescriptor.GetProvider() and passing in the type that is intended to be associated to your custom type converter. An example of this will follow.
Registration/Deregistration
Once you have CustomConverterProvider and CustomConverterDescriptor as outlined above, finish up registering your custom TypeConverter to a type by calling TypeDescriptor.AddProvider() with parameters CustomConverterProvider and the type you want your custom converter to be associated with.
TypeDescriptionProvider parentProvider = TypeDescriptor.GetProvider(type);
CustomConverterProvider newProvider = new(parentProvider, converter);
TypeDescriptor.AddProvider(newProvider, type);At this point, calling TypeDescriptor.GetConverter() with the same type that was passed to TypeDescriptor.AddProvider() will now return your custom type converter. To restore the previous behavior call TypeDescriptor.RemoveProvider().
TypeDescriptor.RemoveProvider(newProvider, type);Additional Information
TypeDescriptor.GetConverter() Details
This is a static method on TypeDescriptor that will run through its private list of TypeDescriptionProviders and get the provider associated with the type. Once the right provider is found, it will then call TypeDescriptionProvider.GetTypeDescriptor() to get an ICustomTypeDescriptor and call ICustomTypeDescriptor.GetConverter() to grab the TypeConverter.
sequenceDiagram
title TypeDescriptor.GetConverter() Flow
participant TypeDescriptor
participant CustomConverterProvider#58;TypeDescriptionProvider
participant CustomConverterDescriptor#58;CustomTypeDescriptor
participant CustomTypeConverter#58;TypeConverter
note right of CustomConverterProvider#58;TypeDescriptionProvider: Call TypeDescriptor.RemoveProvider() to no longer associate your custom type converter with a given type
TypeDescriptor->>CustomConverterProvider#58;TypeDescriptionProvider: TypeDescriptor.GetConverter()
note over TypeDescriptor,CustomConverterProvider#58;TypeDescriptionProvider: Must call TypeDescriptor.AddProvider() with a given type first
CustomConverterProvider#58;TypeDescriptionProvider->>CustomConverterDescriptor#58;CustomTypeDescriptor: CustomConverterProvider.GetTypeDescriptor()
note over CustomConverterProvider#58;TypeDescriptionProvider,CustomConverterDescriptor#58;CustomTypeDescriptor: Must override GetTypeDescriptor() to return CustomConverterDescriptor first
CustomConverterDescriptor#58;CustomTypeDescriptor->>CustomTypeConverter#58;TypeConverter: CustomConverterDescriptor.GetConverter()
note over CustomConverterDescriptor#58;CustomTypeDescriptor,CustomTypeConverter#58;TypeConverter: Must override GetConverter() to return CustomTypeConverter firstTrimming Feature Switches (`docs/trimming/feature-switches.md`)
WinForms Libraries Feature Switches
Publicly documented feature-switches can be found on the official docs. Additional details on feature switches usage can be found on the runtime repo. Non-public feature switches that impact WinForms libraries can be found in the following table.
Available Feature Switches
| MSBuild Property Name | AppContext Setting | Description |
|---|---|---|
| _ActiveXImplSupport | System.Windows.Forms.ActiveXImpl.IsSupported | WinForms ActiveX support is trimmed when set to false. |
| _MdiWindowDialogSupport | System.Windows.Forms.MdiWindowDialog.IsSupported | WinForms MdiWindowDialog support is trimmed when set to false. |
| _UseComponentModelRegisteredTypes | System.Windows.Forms.Control.UseComponentModelRegisteredTypes | Uses ComponentModel type registration feature when set to true. |
| _WinFormsBindingSupport | System.Windows.Forms.Binding.IsSupported | WinForms binding support is trimmed when set to false. |
| _WinFormsDesignTimeFeaturesSupport | System.Windows.Forms.Control.AreDesignTimeFeaturesSupported | WinForms design time support is trimmed when set to false. |
| _WinFormsImageIndexConverterSupport | System.Windows.Forms.ImageIndexConverter.IsSupported | WinForms ImageIndexConverter support is trimmed when set to false. |
| _WinFormsUITypeEditorSupport | System.Drawing.Design.UITypeEditor.IsSupported | WinForms UITypeEditor support is trimmed when set to false. |
Feature Prompts SuspendRelocationPaintingFlashPreventing Create Github API Proposal Prompt (`docs/Feature-Prompts/SuspendRelocationPaintingFlashPreventing/Create-Github-API-Proposal-Prompt.md`)
Copilot Prompt 1 β Author the WinForms API Suggestion
Your task
Write a complete API suggestion for the dotnet/winforms repository, ready to be filed
as a GitHub issue with the api-suggestion label. The issue covers two related features
for reducing visible intermediate states while WinForms applications update their UI:
- Painting suspension with optional layout suspension across a control tree.
- Deferred top-level form reveal.
Use these issue sections:
## Rationale## API Proposal## API Usage## Alternative Designs## Risks## Will this feature affect UI controls?### Status Checklist
Sub-feature A β painting and layout suspension
Settled API surface
namespace System.Windows.Forms;
public interface ISupportSuspendPainting
{
void BeginSuspendPainting();
void EndSuspendPainting();
}
public enum LayoutSuspendTraversal
{
None = 0,
TopLevelOnly = 1,
Traverse = 2,
}
public sealed class SuspendPaintingScope : IDisposable
{
public SuspendPaintingScope(ISupportSuspendPainting? target);
public void Dispose();
}
public static class ControlMutationExtensions
{
public static SuspendPaintingScope SuspendPainting(
this ISupportSuspendPainting target);
public static SuspendPaintingScope SuspendPainting(
this ISupportSuspendPainting target,
LayoutSuspendTraversal layoutSuspendTraversal);
public static SuspendPaintingScope SuspendPainting(
this ISupportSuspendPainting target,
Func<Control, bool> suspendLayoutContainerFilter);
}ControlimplementsISupportSuspendPaintingexplicitly and exposes protected virtualBeginSuspendPaintingCore/EndSuspendPaintingCorehooks.ListView,ListBox,ComboBox,TreeView, andRichTextBoxroute painting
suspension through their existingBeginUpdate/EndUpdatepaths.Nonesuspends painting only.TopLevelOnlysuspends layout on the targetControl.Traversesuspends layout on the target and all existing descendants.- The predicate is evaluated for the target and every descendant. A
falseresult skips
that node but does not prune traversal. - Selected nodes are suspended even when they currently have no children.
- Layout-aware overloads require the target to derive from
Control. - The scope is a sealed class so it can span
await.
Design considerations
- Snapshot the selected controls when the scope starts.
- Suspend layout root-to-leaf and resume it deepest-first.
- Resume layout before ending painting so recursive invalidation occurs after layout.
- Keep disposal idempotent and preserve nesting through the existing ref counts.
- Explain that controls added after the snapshot are covered by their parent's suspended
layout but do not receive an independently balanced suspension. - Discuss traversal cost for large control trees and exceptions thrown by predicates.
- Include invalid and non-
Controltarget behavior in the proposal.
Sub-feature B β deferred form reveal
Use the current FormRevealMode design from the tracked API proposal:
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; }
}Describe DWM cloaking, dark-mode-aware default resolution, designer serialization,
top-level-window limitations, and conservative fallback behavior on unsupported systems.
Filing instruction
Produce a complete proposal rather than an implementation plan. Clearly distinguish fixed
API shape from implementation choices that remain open for review.
Feature Prompts SuspendRelocationPaintingFlashPreventing SuspendRelocationAndPainting API Feature Prompt (`docs/Feature-Prompts/SuspendRelocationPaintingFlashPreventing/SuspendRelocationAndPainting-API-Feature-Prompt.md`)
Copilot Prompt 2 β Implement the flicker-free UI mutation APIs
Prerequisite
Read the current upstream API suggestion before implementation. Treat its latestAPI Proposal section as the public contract and report any conflict instead of silently
choosing a different shape.
Scope
A β painting suspension with optional layout traversal
Implement
ISupportSuspendPaintingonControlwith ref-countedBeginSuspendPaintingCore/EndSuspendPaintingCorehooks.Route
ListView,ListBox,ComboBox,TreeView, andRichTextBoxthrough their
existingBeginUpdate/EndUpdatemechanisms.Keep
SuspendPaintingScopeas a sealed, idempotentIDisposableclass so it can spanawait.Provide these extension methods:
csharppublic static SuspendPaintingScope SuspendPainting( this ISupportSuspendPainting target); public static SuspendPaintingScope SuspendPainting( this ISupportSuspendPainting target, LayoutSuspendTraversal layoutSuspendTraversal); public static SuspendPaintingScope SuspendPainting( this ISupportSuspendPainting target, Func<Control, bool> suspendLayoutContainerFilter);LayoutSuspendTraversalhasNone,TopLevelOnly, andTraverse.Snapshot selected controls before suspension. Suspend root-to-leaf and resume
deepest-first.The predicate is evaluated for the target and every descendant. A
falseresult skips
suspension for that node without pruning its descendants.Suspend selected controls even when they currently have no children.
Validate layout-aware calls before beginning painting. A non-
Controltarget is invalid.Resume all selected layout scopes before ending painting so the recursive invalidation
occurs after layout.
B β deferred form reveal
- Implement the approved
FormRevealMode,Form.FormRevealMode, andApplicationconfiguration APIs. - Preserve classic behavior when deferred reveal is not active.
- Cloak eligible top-level form handles and uncloak according to the timing strategy in the
proposal. - Keep unsupported DWM and window configurations inert and safe.
Engineering requirements
- Match the current repository language version, nullable annotations, code style, and
interop conventions. - Add XML documentation for every public or protected API.
- Update
PublicAPI.Unshipped.txt. - Keep state lazy where existing
Controlproperty-store patterns apply. - Do not change existing public
BeginUpdate/EndUpdatebehavior.
Tests
- Painting ref-count balance, nesting, idempotent disposal, handle creation, and handle
recreation. None,TopLevelOnly, andTraverselayout selection.- Predicate selection, continued traversal after a rejected node, and empty containers.
- Deepest-first layout resume and recursive invalidation after layout.
- Null, invalid-enum, and non-
Controltarget failures without partial suspension. - Existing native update paths for the selected built-in controls.
- Classic/deferred form reveal behavior and unsupported-OS fallback.
Deliverable
Provide production code, focused tests, updated API tracking, and an updated API proposal.
Call out any behavior that the implementation proves unsafe or unnecessarily costly.
Designer Modernization Of Code Behind In OOP Designer Modernization Of Code Behind In Oop Designer (`docs/designer/modernization-of-code-behind-in-OOP-designer/modernization-of-code-behind-in-oop-designer.md`)
Modernization of code-behind in WinForms OOP designer
Background
The WinForms designer saves visual representations of user controls or forms on the design surface to code-behind file (Form1.Designer.cs or Form1.Designer.vb). Control instances that developer sees on the design surface are represented as a CodeDOM tree (see Using the CodeDOM), that tree is saved to the generated code-behind file in the InitializeComponent method. We call this feature design time serialization.
Previously COM-based CodeModel interface was used to write code into the editor's buffer as well as to generate CodeDOM tree from the source code when designer was loading. This COM object was accessible from the main thread only, which prevented parallelization in source code processing. Also, being more than 20 years old, it did not support some of the newer language features, for example the nameof() expression.
What's different
Starting with Visual Studio 2022 v17.5 Preview 3, the WinForms out-of-process designer uses Roslyn for design time serialization.
In Preview 3, designer serializes more modern code into InitializeComponent. We have worked hard to ensure that you can take projects between this version of Visual Studio and earlier versions. Someday in the future, we may build on the improvements in our serialization to enable performance enhancements and use newer language features. These potential future investments may break the backwards compatibility with earlier VS versions - but we will be sure to inform our developers well ahead of such changes so that you can prepare.
Why change it
- The use of Roslyn unblocks multithreaded serialization and deserialization to solve performance delays in designer load and unload scenarios.
- Code-behind generation now respects
.editorconfigsettings and thus matches code style in the source files. - If implicit usings feature is enabled in project properties, global namespaces are not repeated in type names.
- Further modernization of code-behind is unblocked.
- Newly generated code will work with modern Roslyn analyzers.
How the new code looks like
While InitializeComponent method is meant for the designer use only, and manual modifications to this method are not supported, developers might see some unexpected changes in Form1.Designer.cs and Form1.designer.vb files when comparing versions in the source control tools. This is a one-time code churn. Once the designer file is regenerated in v17.5 Preview 3 or newer and saved, designer will generate code only for controls modified in the design surface.
For example, InitializeComponent method for a form with a button will be modified like this on the first save:
Code generated by VisualStudio v17.4 or earlier:
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(58, 60);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(136, 69);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 25F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(270, 208);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}Code generated by VisualStudio v17.5 Preview 3, or newer:
private void InitializeComponent()
{
button1 = new Button();
SuspendLayout();
//
// button1
//
button1.Location = new Point(58, 60);
button1.Name = "button1";
button1.Size = new Size(136, 69);
button1.TabIndex = 0;
button1.Text = "button1";
button1.UseVisualStyleBackColor = true;
//
// Form1
//
AutoScaleDimensions = new SizeF(10F, 25F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(270, 208);
Controls.Add(button1);
Name = "Form1";
Text = "Form1";
ResumeLayout(false);
}Support for code style settings
The above code was generated for default C# code style settings, thus this access qualifier was removed in the Preview 3 version.
To preserve legacy code generation style in InitializeComponent method, set style preferences to legacy values in .editorconfig file.
[*.designer.cs]
# this. preferences
dotnet_style_qualification_for_event = true
dotnet_style_qualification_for_field = true
dotnet_style_qualification_for_method = true
dotnet_style_qualification_for_property = true
[*.vb]
# Me. preferences
dotnet_style_qualification_for_event = true
dotnet_style_qualification_for_field = true
dotnet_style_qualification_for_method = true
dotnet_style_qualification_for_property = trueSupport for implicit usings property
Namespace names were removed from the type names in Preview 3 version. They are not needed for type resolution because design time serialization looks up the implicit usings property, available in C# starting with .NET 6 for C#, from the project file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>When implicit usings property is disabled in the C# project file, namespace names are generated.
Similar changes are present in Visual Basic
Code generated by VisualStudio v17.4 or earlier:
<System.Diagnostics.DebuggerStepThrough()>
Private Sub InitializeComponent()
Me.Button1 = New System.Windows.Forms.Button()
Me.SuspendLayout()
'
'Button1
'
Me.Button1.Location = New System.Drawing.Point(58, 60)
Me.Button1.Name = "Button1"
Me.Button1.Size = New System.Drawing.Size(136, 69)
Me.Button1.TabIndex = 0
Me.Button1.Text = "Button1"
Me.Button1.UseVisualStyleBackColor = True
'
'Form1
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(10.0!, 25.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(270, 208)
Me.Controls.Add(Me.Button1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
End SubCode generated by VisualStudio v17.5 Preview 3, or newer:
<System.Diagnostics.DebuggerStepThrough()>
Private Sub InitializeComponent()
Button1 = New Button()
SuspendLayout()
'
' Button1
'
Button1.Location = New Point(58, 60)
Button1.Name = "Button1"
Button1.Size = New Size(136, 69)
Button1.TabIndex = 0
Button1.Text = "Button1"
Button1.UseVisualStyleBackColor = True
'
' Form1
'
AutoScaleDimensions = New SizeF(10.0F, 25.0F)
AutoScaleMode = AutoScaleMode.Font
ClientSize = New Size(270, 208)
Controls.Add(Button1)
Name = "Form1"
Text = "Form1"
ResumeLayout(False)
End SubSummary
New code-behind in WinForm apps looks differently because it follows code style settings defined for the project. Under the covers, the design time serialization was redesigned in order to move from EnvDTE.CodeModel to Roslyn. We are planning to build upon this foundation in the future Visual Studio releases. Code-behind generated by the older releases will load into v17.5 Preview 3 or later releases and will work as expected. Code-behind generated by v17.5 Preview 3 can be loaded in the previous releases of the Visual Studio, but we are considering generating modern code that might not be supported by the previous versions of deserializer.
Src System Windows Forms TaskDialog Issue AccessViolation NavigationInButtonClicked (`docs/src/System/Windows/Forms/TaskDialog/Issue_AccessViolation_NavigationInButtonClicked.md`)
Windows Task Dialog Issue
Access Violation when receiving [`TDN_NAVIGATED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-navigated) within [`TDN_BUTTON_CLICKED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-button-clicked)
An Access violation can occur when receiving a navigation notification (TDN_NAVIGATED)
within a TDN_BUTTON_CLICKED
notification (e.g. due to running the message loop) and then returning S_OK.
- Run the code.
- Click on the "My Custom Button" button.
- Notice that the dialog navigates and an inner dialog shows.
- Close the inner dialog by clicking the OK button.
- Issue: In most cases, an access violation occurs now (if not, try again a
few times). - Notice the problem also occurs when you (after the navigation) first close
the original dialog, and then close the inner dialog. - The problem can be avoided by returning
S_FALSEfrom theTDN_BUTTON_CLICKED
notification.
/* Detailed source-code truncated for AI context efficiency. */Src System Windows Forms TaskDialog Issue AccessViolation NavigationInRadioButtonClicked (`docs/src/System/Windows/Forms/TaskDialog/Issue_AccessViolation_NavigationInRadioButtonClicked.md`)
Windows Task Dialog Issue
Access Violation when receiving [`TDN_NAVIGATED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-navigated) within [`TDN_RADIO_BUTTON_CLICKED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-radio-button-clicked)
An Access Violation occurs when receiving a navigation notification
(TDN_NAVIGATED)
within a TDN_RADIO_BUTTON_CLICKED
notification (e.g. due to running the message loop).
- Run the code and select one of the radio buttons.
- Notice the dialog has navigated and an inner dialog is opened.
- Close the inner dialog.
- Issue: Notice the application crashes with an access violation.
/* Detailed source-code truncated for AI context efficiency. */Src System Windows Forms TaskDialog Issue ButtonClickHandlerCalledTwice (`docs/src/System/Windows/Forms/TaskDialog/Issue_ButtonClickHandlerCalledTwice.md`)
Windows Task Dialog Issue
[`TDN_BUTTON_CLICKED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-button-clicked) notification sent twice when pressing a button with its access key
When you "click" a button by pressing its access key (mnemonic) and the dialog
is still open when the message loop continues, the TDN_BUTTON_CLICKED
notification is sent twice instead of once.
- Run the code.
- Click one of the buttons with the mouse, or press space or enter, and notice
that the counter increments by 1. - Issue: Press Alt+M or Alt+N and
notice the counter increments by 2.
/* Detailed source-code truncated for AI context efficiency. */Src System Windows Forms TaskDialog Issue RadioButton InfiniteLoop (`docs/src/System/Windows/Forms/TaskDialog/Issue_RadioButton_InfiniteLoop.md`)
Windows Task Dialog Issue
Infinite loop with radio buttons
An infinite loop occurs when sending aTDM_CLICK_RADIO_BUTTON message within aTDN_RADIO_BUTTON_CLICKED notification.
- Run the code and select the second radio button.
- Issue: Notice that the GUI doesn't respond any more. When debugging, you can see that
the callback is flooded withTDN_RADIO_BUTTON_CLICKEDnotifications even though we
don't send any more messages to the dialog.
/* Detailed source-code truncated for AI context efficiency. */Src System Windows Forms TaskDialog Issue RadioButton WeirdBehavior (`docs/src/System/Windows/Forms/TaskDialog/Issue_RadioButton_WeirdBehavior.md`)
Windows Task Dialog Issue
Weird behavior with radio buttons
Weird behavior occurs when running the message loop within anTDN_RADIO_BUTTON_CLICKED
notification.
Run the code.
Notice that within the
TDN_TIMERnotification, aTDM_CLICK_RADIO_BUTTONis
sent to select the second radio button which causes an inner dialog to be shown within
theTDN_RADIO_BUTTON_CLICKED
notification, but the radio button is only selected after
the inner dialog is closed (and therefore the notification handler returns).
While that seems like a strange/undesired behavior, it is actually the documented behavior:"The specified radio button ID is sent to the TaskDialogCallbackProc callback function
as part of a TDN_RADIO_BUTTON_CLICKED notification code. After the callback function
returns, the radio button will be selected."(For the .NET implementation that has the
TaskDialogRadioButton.CheckedChangedevent
that is expected to show already the new state, this can be worked-around by ignoringTDN_RADIO_BUTTON_CLICKEDnotifications caused by the code itself, and instead raising
the events after sending theTDM_CLICK_RADIO_BUTTONmessage.)
However, this special behavior might be related to the following issue (and the other
code samples regarding radio buttons):When you click one of the radio buttons and then immediately close the inner dialog,
everything works.Now try the following: Click the first radio button (but do NOT close the inner dialog),
then click the second radio button.Now close the most inner dialog (showing call count 2), and then the other inner dialog.
Issue: Notice that the dialog changed the selection of the radio button and shows the inner
dialog again. When you close it, even more inner dialogs open.During that time, when you focus the original dialog, it will switch the selected radio
button again.
/* Detailed source-code truncated for AI context efficiency. */--- METRICS ---
- Files Extracted: 60
- Estimated Token Budget: ~92554 tokens
- Canonical Reference: https://codewiki.google/github.com/dotnet/winforms