winforms Documentation

Windows Forms is a .NET UI framework for building Windows desktop applications.

4,864 stars C# C# 1 file Β· ~92,633 tokens #desktop#hacktoberfest#help-wanted#windows-forms#winforms
RAW Doc

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.

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.

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:

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


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:

cmd
mark-shipped.cmd

Overview (`eng/common/README.md`)

Don't touch this folder

text
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:

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.

  1. Install the required template with dotnet tool (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-CSharp
  2. To confirm that the template is correctly installed run the following command and look for your template:

    text
    > dotnet new -u
  3. Create an app from your template:

    text
    > dotnet new winforms -n testapp1
  4. Verify the app behaves as expected.
    If necessary, tweak the template and create new apps.

  5. Once you are happy with the template, you can uninstall it to revert to the original by running the command given in dotnet new -u output; 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.

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:

text
.\build.cmd -pack /p:_GenerateManifest=true

To debug the script run the following command:

text
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.


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:

  1. Use https://aka.ms/tlbimp to generate an assembly for oleacc.dll
cmd
tlbimp c:\windows\system32\oleacc.dll /out:Accessibility.dll
  1. Use ildasm to disassemble that assembly.
cmd
ildasm Accessibility.dll /out=Accessibility.il
  1. Replace the desktop assembly references with their corresponding .NETCore assemblies (since .NETCore doesn't provide a version of tlbimp.exe yet)

    • replace [mscorlib]System.Runtime.InteropServices with [INTEROP_ASSEMBLY]System.Runtime.InteropServices
    • replace [mscorlib] with [CORE_ASSEMBLY]
    • review the diff
  2. 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-neutral
AnalyzerTestCase 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

  1. Reserve a WFO ID in
    src\System.Windows.Forms.Analyzers\src\System\Windows\Forms\Analyzers\Diagnostics\DiagnosticIDs.cs.
  2. Add the localized title and message to src\System.Windows.Forms.Analyzers\src\Resources\SR.resx,
    then build so the XLF files are synchronized.
  3. Add a descriptor to the shared or language-specific descriptor class.
  4. Implement the analyzer under the applicable C# or Visual Basic src\Analyzers tree. Put common
    semantic detection in the shared analyzer project; keep syntax APIs language-specific.
  5. Track the rule in AnalyzerReleases.Unshipped.md and document it in the applicable
    docs\analyzers\WinFormsCSharpAnalyzers.Help.md or
    docs\analyzers\WinFormsVisualBasicAnalyzers.Help.md file.
  6. 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 a
TestData 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, or
System.ComponentModel.Component. Include negative cases with same-named types in unrelated
namespaces. For VB, use realistic WithEvents/Handles fixtures and distinguish member events from
GenerateMember=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:

csharp
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 add
CurrentReferences.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:

powershell
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 -nologo

Before 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:

  1. Right-click the project in Solution Explorer and select Properties.
  2. Go to the Debug then Open debug launch profiles UI.
  3. Check the box Enable native code debugging.
  4. Save and rebuild the project.

This setting is saved in a user-specific .csproj.user file.

Option B – Editing launchSettings.json:

  1. Open the file Properties\launchSettings.json.
  2. Under the "profiles" section, locate the profile matching your project name.
  3. Add or update the property "nativeDebugging": true.

Example:

json
{
  "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:

  1. Go to Debug > Options from the Visual Studio toolbar.
  2. In the left pane, select Debugging > Symbols.
  3. Under Symbol file (.pdb) locations, click the βž• (plus) button.
  4. Enter the following URL:
text
https://symweb
  1. 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:

  1. In Visual Studio, locate the Debug dropdown on the top toolbar (next to the green Start button).
  2. Click the small downward arrow next to the active configuration. You will see the currently selected framework (typically something like Framework (net11.0-windows)).
  3. Click the displayed framework name to open a submenu showing all target frameworks for this project.
  4. Select net481 from the list.
  5. Build and run the project using any of the usual commands:
    • Build: Ctrl + Shift + B
    • Start Without Debugging: Ctrl + F5
    • Start With Debugging: F5

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

  1. If you have not already done so, clone or fork the repo at https://github.com/dotnet/winforms

  2. Checkout a new branch for your work with the following command:

    text
    git checkout -b <your-new-branch-name>
  3. Run a build and execute integration tests with the following command:

    text
    .\build -integrationTest /m:1
  4. If 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.

  1. Browse to .\src\System.Windows.Forms\tests\IntegrationTests\MauiTests. All Maui tests live under this folder, each in their own folder.

  2. Create a copy of the existing MauiButtonTest folder in the same directory

    1. Rename the new folder to something more appropriate for the test you are porting (MauiComboBoxTest, for example)
    2. Browse into the new folder and rename the .csproj to the name you chose in step 3 (MauiComboBoxTest.csproj, for example)
    3. Delete the existing MauiButtonTest.cs file
  3. Find the source code for the test you are porting and copy it into the new folder you created in the previous step

  4. Clean up the Maui test source:

    1. Rename the file appropriately (MauiComboBoxTest.cs, for example)

    2. Add using System.Windows.Forms.IntegrationTests.Common; to the using directives.

    3. Change the namespace to System.Windows.Forms.IntegrationTests.MauiTests

    4. Rename the class appropriately (MauiComboBoxTest, for example)

    5. Add the following line as the first command in the constructor:

      text
      this.BringToForeground();
    6. Add the following line as the first command in Main():

      text
      Thread.CurrentThread.SetCulture("en-US");
    7. Make sure each scenario method has the [Scenario(true)] attribute

    8. Remove the entire [Scenarios] section at the bottom of the file

    9. Make sure each scenario method’s name describes what the test is actually doing.

      For example, the button scenario methods are named as follows:

      text
      Click_Fires_OnClick()
       Hotkey_Fires_OnClick()
       Hotkey_DoesNotFire_OnClick()
    10. 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:

      1. Use implicit types (var) whenever possible
      2. All fields should have access modifiers
      3. Prefer built-in types instead of classes (β€œstring” is preferred to β€œString”)
      4. Only use β€œthis” when necessary
      5. Etc...

Update the WinForms solution

  1. Open .\Winforms.sln in Visual Studio
  2. In the solution explorer, browse to tests\integration\maui
  3. Right-click on the maui folder and click on Add -> Existing Project...
  4. Add the csproj you created in the previous section

Run the maui test manually

  1. Open a command prompt and browse to the root of your repo.

  2. Build the source code (including the test you just ported):

    text
    .\build-local.ps1
  3. Browse to .\artifacts\bin\<your-test-name>\Debug\net6.0

  4. Double click on <your-test-name>.exe

  5. Check the results.log in the same directory to make sure everything passed

  6. 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

  1. Browse to .\src\System.Windows.Forms\tests\IntegrationTests\System.Windows.Forms.Maui.IntegrationTests

  2. Copy WinformsMauiButtonTest.cs to a new file in the same directory

  3. Rename the new file according to the Maui test you added in the earlier section (WinformsMauiComboBoxTest.cs, for example)

  4. Rename the class to the same thing (WinformsMauiComboBoxTest, for example)

  5. 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

  6. Change the name of the single test method to match the name of your project (MauiComboBoxTest, for example)

  7. Open a command prompt and browse to the root of your repo.

  8. Run the following command:

    text
    .\build -integrationTest /m:1
  9. Verify 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

Run the maui test through the CI build

  1. Create a pull request from your new branch (or fork) to the winforms main at https://github.com/dotnet/winforms/compare
  2. Once the PR is created (and every time new files are pushed to the PR), the build will kick off
  3. You can see the result of the build by scrolling down to the checks section and clicking on Show All Checks
  4. If the build is not successful, try to investigate the failure
    1. Click on Details next to the dotnet-winforms CI check
    2. 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:

  1. Right-click the project in Solution Explorer and select Properties.
  2. Go to the Debug then Open debug launch profiles UI.
  3. Check the box Enable native code debugging.
  4. Save and rebuild the project.

This setting is saved in a user-specific .csproj.user file.

Option B – Editing launchSettings.json:

  1. Open the file Properties\launchSettings.json.
  2. Under the "profiles" section, locate the profile matching your project name.
  3. Add or update the property "nativeDebugging": true.

Example:

json
{
  "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:

  1. Go to Debug > Options from the Visual Studio toolbar.
  2. In the left pane, select Debugging > Symbols.
  3. Under Symbol file (.pdb) locations, click the βž• (plus) button.
  4. Enter the following URL:
text
https://symweb
  1. 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:

  1. In Visual Studio, locate the Debug dropdown on the top toolbar (next to the green Start button).
  2. Click the small downward arrow next to the active configuration. You will see the currently selected framework (typically something like Framework (net11.0-windows)).
  3. Click the displayed framework name to open a submenu showing all target frameworks for this project.
  4. Select net481 from the list.
  5. Build and run the project using any of the usual commands:
    • Build: Ctrl + Shift + B
    • Start Without Debugging: Ctrl + F5
    • Start With Debugging: F5

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 the PlatformNotSupportedException to 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:

cs
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:

  1. .NET 7.0 SDK (recommended)

  2. .NET 8.0 daily build (latest changes, could be less stable)

Creating new applications

You can create a new WinForms application with dotnet new command, using the following commands:

cmd
dotnet new winforms -o MyWinFormsApp
cd MyWinFormsApp
dotnet run

Designing 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, and Application.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;
  • ToggleFullScreen behavior 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 so
AutoSize = 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. Its
DisplayRectangle 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.cmd from the repository root. This builds the Winforms.sln using the default config (Debug|Any CPU).
  • To specify a build configuration, add -configuration followed 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

  1. .NET 6.0 and above branches need VisualStudio 2022 to build.
  2. Run .\restore.cmd from the repository root.
  3. Run .\start-vs.cmd from the repository root. This will prepend the repo-local .NET SDK to the path, and open Winforms.sln in Visual Studio.
  4. You should now be able to build as you normally would.

Building from Visual Studio Code

  1. (Optional) Run .\restore.cmd from the repository root.
  2. Run .\start-code.cmd from the repository root. This will prepend the repo-local .NET SDK to the path, and open Winforms.sln in Visual Studio Code.
  3. 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 artifacts folder.
  • Binaries are under artifacts\bin.
    • For example, System.Windows.Forms.dll can be found under artifacts\bin\System.Windows.Forms\Debug\net6.0.
  • Logs are found under artifacts\log.
  • Packages are found under artifacts\packages.

Running apps from VS

  1. Right click on the project you wish to run and select 'Set as startup project'.
  2. F5.

Running apps from command line

  1. Build the solution: .\build.cmd
  2. 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
  3. (Alternatively) Navigate to .\artifacts\bin\AccessibilityTests\Debug\net6.0 and 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.
  • 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.resx file 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.cmd command. You can also build just the modified project by running dotnet build from 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".

  1. Use four spaces of indentation (never use tabs). XML blocks should get a single space indent (this includes XML comments).
  2. 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).
  3. Avoid more than one empty line at any time. For example, do not have two blank lines between members of a type.
  4. 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

  1. 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.
  2. 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.
  3. When breaking arguments to a method, all arguments should be indented on individual lines at one indent in.
  4. When breaking logical statements, all logical operators (&&, ||, etc.) at a given parenthetical scope should be indented on individual lines at one indent in.
  5. When breaking ternary operators (?:), they should be broken into two singly indented lines, starting with ? and :.

Code Samples

More complicated line breaks:

c#
if (_trackColumn != previousColumnIndex
    && !(previousColumnIndex == -1 && hti._col == _trackColumn)
    && (dataGridViewColumnNext is null || _trackColumn != dataGridViewColumnNext.Index))
c#
if (Focused
    && !IsCurrentCellInEditMode
    && (EditMode == DataGridViewEditMode.EditOnEnter
        || (EditMode != DataGridViewEditMode.EditProgrammatically && CurrentCellInternal.EditType is null)))

Why => isn't at the beginning of a line:

c#
// 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

  1. Use Allman style braces, where each brace begins on a new line.
  2. if statements must use code blocks with the exception of single line parameter validation at the beginning of a method.
  3. using statements 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 multiple using statements in a row, they should share a code block to reduce nesting.
  4. fixed statements must use code blocks. When there are multiple using statements in a row, they should share a code block to reduce nesting.
  5. unsafe should 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

  1. 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.
  2. Avoid comments labeling the end of a block (never if {} else {} // else).
  3. Methods and properties should have XML comments, not \\ comments. public API comments should align with the documentation on https://learn.microsoft.com.
  4. 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.)
  5. 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.
  6. Avoid \* *\ comments.

General Naming

  1. Avoid abbreviations and type names for variable or property names (e.g. Point firstPoint not pt1).
  2. Names should describe their use, not their type (e.g. Rectangle bounds over Rectangle rectangle).
  3. Use nameof(...) instead of "..." whenever possible and relevant.

General Visibility

  1. Always specify the visibility, even if it's the default (e.g. private string _foo not string _foo). Visibility should be the first modifier (e.g. public abstract not abstract public).

Namespace Usings

  1. Namespace usings should be specified at the top of the file, before namespace declarations, and should be sorted alphabetically, with the exception of System.* namespaces, which are to be placed on top of all others.
  2. Global usings go should in a file called globalusings.cs.

Namespaces

  1. Use file-scoped namespaces.
  2. Files should be in sub folders that match the namespaces. Additional organizational folders can be created within namespace folders when necessary for clarity.

Types

  1. All types should be in their own files. This includes nested types.
  2. All internal and private classes should be sealed when they are not derived from.
  3. Use Pascal Casing to name all types. The only exception is for interop types, which should match the native casing.
  4. Consider primary constructors for "simple" structs and classes where there is only one constructor with no logic other than field assignment.

Fields

  1. Type constants, statics, and fields should be specified at the top within the type declaration.
  2. Use _camelCase for internal and private fields and use readonly where possible. Prefix instance fields with _, static fields with s_ and thread static fields with t_.
  3. Use PascalCasing to name all constants. The only exception is for interop code where the constant value should match the naming in the native code.
  4. public or internal fields should not be used (use properties).
  5. protected properties should be preferred over fields.

Properties

  1. Use PascalCasing to name all properties.
  2. Use auto-implemented properties when possible.
  3. Prefer methods over properties for expensive code or code with significant side effects.
  4. Use expression body definitions (=>) for single line getters and setters.

Methods

  1. Use PascalCasing to name all methods.
  2. Constructors should precede all other methods and properties.
  3. Use expression body definitions (=>) for single line methods.
  4. 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.
  5. Use static methods when instance fields are not accessed, and consider passing fields in to make methods static to clarify what a method modifies.
  6. 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.
  7. 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.
  8. Exit (return) early from methods to reduce nesting.

Variables

  1. We never use var for built-in types, e.g. don't use var count = 2, instead use int count = 2. var should only be used when the type name inhibits code readability.
  2. Target-typed new() should be used to remove redundancy, not var. For example: FileStream stream = new(...);, not var stream = new FileStream(...);. Using target-typed new() for succinctness is allowed where the type is reasonably obvious, e.g. Point[] points = [new(1, 2), new(5, 6)];
  3. Collection expressions are preferred, e.g. Point[] points = [new(1, 2)]; over Point[] points = new Point[] { new(1, 2) };

General

  1. Use language keywords instead of BCL types (e.g. int, string, float instead of Int32, String, Single, etc.) for both type references as well as method calls (e.g. int.Parse instead of Int32.Parse).
  2. Always use interpolated strings when composing / formatting strings.
  3. Use raw string literals for multi-line strings.

Conditions

  1. Prefer ternary operators (?:) over if .. else when there is only one line in each clause.
  2. Prefer pattern matching in conditions.
  3. Prefer null-coalescing operators (?? and ??=).
  4. Prefer switch expressions over switch statements where possible.

Nullability

  1. Prefer pattern matching to check variable type and null state (e.g. if (parameter is Form form), if (manager.GetService() is { } container), etc.).
  2. 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 throw ArgumentException when an associated argument is known or InvalidOperationException otherwise. Do not let code fall into NullReferenceException.

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 BinaryFormatter for clipboard and drag and drop scenarios if at all possible.
  • Use the recommended built-in types (listed below) and SetData APIs.
  • For custom types, use the new SetDataAsJson<T>() APIs or serialize manually as string or byte[] data.
  • Always use TryGetData<T>() to get data (not GetData()).
  • Read legacy data using the NrbfDecoder.

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, TimeSpan and DateTime.
  • Arrays or List<T> of the above primitive types. For maximum resilience, avoid string[] and List<string> as they can contain nulls.
  • System.Drawing types: Bitmap, PointF, RectangleF, Point, Rectangle, SizeF, Size, and Color.

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 a resolver callback 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):

csharp
// 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:

csharp
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.

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:

  1. Reference the System.Runtime.Serialization.Formatters package: 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.

  2. Set the runtime switch to enable unsafe serialization: In your project file, set the property EnableUnsafeBinaryFormatterSerialization to true. Without this switch, calls to binary serialization throw.

    xml
    <PropertyGroup>
      <EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
    </PropertyGroup>
  3. Set the WinForms‑specific switch: WinForms adds an app‑context switch Windows.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization. Without this switch, WinForms will not fall back to BinaryFormatter even if the general serialization switch is enabled. Set this flag in your runtimeconfig.json file:

    json
    {
        "configProperties": {
            "Windows.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization": true
        }
    }
  4. Use the resolver overload to control which types are allowed: Even with BinaryFormatter enabled, you must specify a resolver when calling TryGetData<T> so you only deserialize known types. The existing, deprecated, GetData APIs will continue to work, but provide no additional constraints. A sample type resolver follows:

    csharp
    static 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:

csharp
if (dataObject.TryGetData("LegacyFormat", out SerializationRecord? record))
{
    // Process the serialization record
}

You can also retrieve raw data as a MemoryStream for custom processing.


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:

xml
<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:

  1. Create your .NET WinForms 3.1 (C# only), 5 or 6 app (both VB and C#).

  2. Before the designer by opening a Form or a UserControl, edit the .csproj
    file.

  3. 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>
  1. 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):

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.

  1. Build the repository.
  2. Debug the change, as needed.
  3. Test the change, to validate quality.

More Information


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:

  1. Explore this repositories issues, and then
  2. 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:

  1. Issue has no Assignee, unless someone is working on the issue at the moment.
  2. Use help wanted as much as possible, ideally with a quick note about next steps / complexity of the issue.
  3. Set milestone to Future, unless you can 95%-commit you can fund the issue in specific milestone.
  4. Each issue has exactly one "issue type" label (bug, enhancement, api-suggestion, test-bug, test-enhancement, question, documentation, etc.).
  5. Don't be afraid to say no, or close issues β€” just explain why and be polite.
  6. 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

  1. 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.
  2. 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).

  3. 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.
  4. 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.

  1. 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.
  2. Push PRs forward, don't let them go stale (response every 5+ days, ideally no PRs older than 2 weeks).

  3. 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.
  4. 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

  1. 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.
  2. Add new constants to src\Common\src\Obsoletions.cs, following the existing conventions:
    • A ...Message const using the same description added to the table below.
    • A ...DiagnosticId const for the WFDEV### ID.
  3. If adding the <Obsolete> attribute to the Microsoft.VisualBasic.Forms assembly, edit the src\Microsoft.VisualBasic.Forms\src\Obsoletions.vb file.
  4. Annotate src files by referring to the constants defined in Obsoletions.cs:
    • Specify the UrlFormat = Obsoletions.SharedUrlFormat.
    • Example:
      c#
      [Obsolete(
          Obsoletions.DomainUpDownAccessibleObjectMessage,
          DiagnosticId = Obsoletions.DomainUpDownAccessibleObjectDiagnosticId,
          UrlFormat = Obsoletions.SharedUrlFormat)]
    • If the Obsoletions type is not available in the C# project, link it into the project:
      xml
      <Compile Include="..\..\Common\src\Obsoletions.cs" Link="Common\Obsoletions.cs" />
  5. Create the new aka.ms link
    • Point it to the repo's .md file in the docs folder that describes your analyzer until the learn site docs are completed in the redirection manager
    • Set the security group to the team alias
  6. Apply the :book: documentation: breaking label to the PR that introduces the obsoletion.
  7. Document the breaking change
    • In the breaking-change issue filed in dotnet/docs, specifically mention that this breaking change is an obsoletion with a WFDEV diagnostic ID.
    • Create another issue to add documentation to the learn website in the dotnet/docs-desktop repo
    • The documentation team will produce a PR that adds the warning or error documentation to the learn site 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.ms name in the redirection manager
    • Connect with @gewarren, @adegeo, or @merriemcgaw with any questions.

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

  1. 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.md and AnalyzerReleases.Unshipped.md files.
    • Starting in NET9.0, we are using the WFO#### format for code analyzer diagnostic IDs.
  2. Create a new Descriptor in the analyzer code by calling DiagnosticDescriptorHelper.Create
  3. 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.

  1. Add the diagnostic ID to the table below.
  2. 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)]
  3. 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 through
EffectiveVisualStylesMode.

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:

csharp
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:

csharp
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.

text
/* 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 using
BorderStyle.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:

csharp
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:

csharp
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:

csharp
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:

Prepare your project for porting

  1. 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.
  2. Start from a working solution. Ensure the solution opens, builds, and runs without any issues.

  3. Replace packages.config with PackageReference. If your project uses
    NuGet packages, you will need to add the same NuGet packages to the new .NET
    project. .NET projects support only PackageReference for adding
    NuGet packages. To move your NuGet references from packages.config to your
    project file, right-click on packages.config -> Migrate packages.config
    to PackageReference...
    .

    You can learn more about this migration in our docs.

  4. Migrate to the SDK-style .csproj file. The new SDK-style .csproj format 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.

  5. Configure assembly file generation. Most existing projects include an AssemblyInfo.cs file 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 two AssemblyInfo.cs files and your build will fail. There are two ways to resolve this problem. You can either:

    • Disable AssemblyInfo.cs generation on build by setting the property:

      xml
      <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
    • Move the static values from AssemblyInfo.cs to properties in the new .csproj file.

    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

  1. Try porting your project using try-convert tool, which we built to help migrations.
    NB: The tool may not work for all possible projects, in that case please read on.

Manual porting

  1. Add .NET Windows Forms project. Add a new .NET 5.0 Windows Forms project to the solution.

  2. **Add **. Copy the elements from the .csproj file of the original project to the new project's .csproj file.
    Note: The new project format does not use the Name and ProjectGuid elements, so you can safely delete those.

  3. Restore/Build. At this point, it's a good idea to restore/build to make sure all dependencies are properly configured.

  4. Link files. Link all files from your existing .NET Framework WinForms project to the .NET 5.0 WinForms project by adding following to the .csproj file.

    xml
    <ItemGroup>
        <Compile Include="..\<Your .NET Framework Project Name>\**\*.cs" />
        <EmbeddedResource Include="..\<Your .NET Framework Project Name>\**\*.resx" />
    </ItemGroup>
  5. 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>
  6. Run new project. Set your new .NET project as StartUp Project and run it. Make sure everything works.

  7. 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.

cmd
dotnet add package Microsoft.Windows.Compatibility

Migrating 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 ServiceReference created by Visual Studio, you may get the following error:
cs
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)

  1. Merge any outstanding PRs, if necessary

  2. Ensure the HEAD of the main branch builds successfully and all tests pass

  3. Publish any unpublished API - run .\eng\ApiCompatibility\mark-shipped.cmd

  4. Commit and wait for a successful build

  5. (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.

  1. Disable loc on "release/X.Y-qwe" branch (e.g.: https://github.com/dotnet/winforms/pull/6753)

  2. Switch back to the main branch

  3. Update the Service Fabric bot with the new release label in .\github\fabricbot.json. Refer to .\docs\infra\automation.md, if have questions.

  4. Update the branding (e.g.: https://github.com/dotnet/winforms/commit/719b2112778ffd1e7ded559ea2736b4011428117)

  5. Commit and wait for a successful build.

  6. Create new milestone for the new release before updating following bot service.

  7. Move work items to this new milestone if they were on previous milestone and close the previous milestone.

  8. Update necessary reports with the progress

  9. 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)

  1. Merge internal servicing branch (i.e. internal/internal/release/*) into the public branch, if necessary.

  2. Update the branding (e.g.: https://github.com/dotnet/winforms/commit/480bd703c5af93b9e23dc7435c6029d685be7097)

  3. Commit and wait for a successful build.

  4. Ensure the HEAD of the servicing branch builds successfully and all tests pass

  5. Update dotnet/installer with the released version (e.g.: https://github.com/dotnet/installer/commit/34ed90f436809631e254b9e15628cbcad7307629)

  6. Create a new milestone.

  7. Close the current milestone on the GitHub, ensuring all open issues/PRs are either closed or bumped to the following milestone

  8. 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.

xml
<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.

xml
<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.

xml
<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 AppContextSwitchOverrides and System.Windows.Forms.ApplicationConfigurationSection of app.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:

json
{
  "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:

json
{
  "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:

json
{
  "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:

text
/* 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:

text
/* 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 native IDataObject to managed interface
    • Composition.ManagedToNativeAdapter.cs - Adapts managed IDataObject to native COM
    • DataObjectCore.cs - Core data object implementation
    • ClipboardCore.cs - Core clipboard operations
    • BinaryFormatUtilities.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:

  1. Packaging in dotnet/winforms:

    • The Microsoft.Private.Winforms transport package is built from this repository
    • This package bundles System.Private.Windows.Core and other shared assemblies
    • See: pkg/Microsoft.Private.Winforms/README.md
  2. Ingestion by WPF:

    • The dotnet/wpf repository consumes the Microsoft.Private.Winforms NuGet 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
  3. Windows Desktop Shared Framework:

    • Both WinForms and WPF assemblies are bundled into the Microsoft.WindowsDesktop.App shared framework
    • The dotnet/windowsdesktop repository orchestrates this bundling
    • Applications targeting Windows Desktop get a single unified set of assemblies at runtime

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:

csharp
// 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:

csharp
// 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_OPEN and other clipboard-related error constants

Advantages of Sharing

  1. Consistency: Clipboard and OLE behavior is identical across WinForms and WPF applications
  2. Single fix benefits both: Bug fixes in shared code automatically improve both stacks
  3. Shared test coverage: Tests in dotnet/winforms validate behavior for WPF as well
  4. Reduced duplication: Avoids maintaining two separate implementations of complex OLE/COM interop
  5. Better quality: Concentrated development and review effort on a single implementation

Liabilities and Risks

  1. Coupling: Changes in dotnet/winforms can break WPF, and vice versa (though WPF changes rarely touch these components)
  2. Regressions affect both stacks: A bug introduced in shared code will impact both WinForms and WPF
  3. Validation requirements: Changes must be tested in both WinForms and WPF scenarios
  4. 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 the Ole/ directory
  • If in doubt, check if the code is in System.Private.Windows.Core or referenced in pkg/Microsoft.Private.Winforms/

Before making changes:

  1. Consider WPF impact: Ask yourself: "Could this change affect WPF clipboard, drag-and-drop, or data transfer?"
  2. Search for related WPF issues: Check the dotnet/wpf issue tracker for similar problems
  3. Review existing tests: Look at test coverage in src/System.Private.Windows.Core.Tests/ and src/System.Windows.Forms.Tests/ for clipboard/OLE scenarios

When making changes:

  1. Prefer small, targeted fixes: Especially for release branches (e.g., release/10.0), minimize scope to reduce risk
  2. Larger refactorings belong in main: Structural changes and broad refactorings should target the main branch
  3. Add or update tests: Ensure your changes have test coverage, including error handling and edge cases
  4. 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
  5. 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
  • 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 ComScope disposal 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

The following issues track ongoing work and awareness in this area:

Additional Resources

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:

  1. Use a custom *DataAttribute (inheriting from the appropriate *DataAttributeBase class) and set DisableDiscoveryEnumeration = true in 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

  2. 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 -> Output and select Tests from the Show 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.
  • $(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:

  1. Add your local <repoRoot>\.dotnet folder 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

  2. 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 😊

Testing (`docs/testing.md`)

Windows Forms Testing

This document describes our approach to testing.

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:

console
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:

console
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 via dotnet test --filter <filter expression> command by switching to the desired test project directory first.

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 Button class 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.ButtonAccessibleObject class in System.Windows.Forms.dll, I would look for a Button.ButtonAccessibleObjectTests.cs under src\System.Windows.Forms\tests.
  • 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 Button class should be in ButtonTests.cs.
    • For example, tests for the Button.ButtonAccessibleObject class should be in Button.ButtonAccessibleObjectTests.cs.
  • Test names should start with the class they are testing.
    • For example, all tests for the Button class should start with "Button".
  • 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_AutoSizeModeGetSet or MyButton_Click_should_throw_ArgumentNullException.

Decoration

  • All tests that deal with UI controls or types that require synchronization context must be decorated with WinFormsFact or WinFormsTheory attributes.
  • Other tests can be decorated with StaFact, StaTheory, Fact or Theory

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:

  1. theory test must be correctly decorated

  2. theories must avoid creating UI controls. E.g. instead of writing:

    cs
    public 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:

    cs
    public 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 };
        ...
    }
  3. 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.:

    cs
    public 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 use TestAccessor pattern 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, StaTheory or Theory attributes 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 ArgumentException for 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:

cs
[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:

console
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

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 Button class in System.Windows.Forms.dll, I would look for a Button.cs under src\System.Windows.Forms\tests
  • 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 Sequential collection are executed sequentially.
  • Unit tests that involve the following situations should be included in the Sequential collection:

    • 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

All accessible objects have "ControlType" (a part of modern UIA) and "LegacyIAccessible.Role"
(further "Role" - a part of outdated MSAA) accessibility properties.

inspect-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.

accessibleobject-role-implementation

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

accessibleobject-getpropertyvalue-implementation

.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:

cs
Control control = new Control();
// control.AccessibleRole = AccessibleRole.List; - Role is not set

or equals AccessibleRole.Default:

cs
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 ToolStrip items that didn't change
    their accessible objects "ControlType" values depending on Role in .NET Framework 4.7.2
    but in in .NET 6.0 they can do it correctly:

    1. ListView - "ControlType" was always List
    2. ToolStripMenuItem - "ControlType" was always Menu item
    3. ToolStripSplitButton - "ControlType" was always Button
    4. ToolStripDropDownButton - "ControlType" was always Button
  • RichTextBox: at this moment, we don't provide a managed accessible object for it.
    It doesn't support setting a custom AccessibleRole and
    changing "ControlType" respectively (in .NET Framework 4.7.2 too).
    It always has "Role" as Document and "ControlType" as Document.

  • TreeView: at this moment, we don't provide a managed accessible object for it.
    It doesn't support setting a custom AccessibleRole and
    changing "ControlType" respectively (in .NET Framework 4.7.2 too).
    It always has "Role" value as Outline and "ControlType" value as Tree.

  • DateTimePicker: its accessible object has non-standard
    "LocalizedControlType" accessibility property value by default.
    But it changes if to change AccessibleRole property value of the control.

  • Form: default "Role" value is Client but default "ControlType" value
    is Window. This is an exception and does not correspond to the list above.
    In the rest, Form with a custom AccessibleRole value works as expected.

  • MonthCalendar: if AccessibleName property value is null or empty
    default "ControlType" of its accessible object is Calendar,
    if AccessibleName is a some text - default "ControlType" is Table.

  • ToolStripLabel: if IsLink property value equals false
    its accessible object default "ControlType" value is Text,
    and "ControlType" is Hyperlink if IsLink is true.

  • ToolStripStatusLabel: if IsLink property value equals false
    its accessible object default "ControlType" value is Text,
    and "ControlType" is Hyperlink if IsLink is true.

  • ToolStripNumericUpDown: this item is used in PrintPreviewDialog control.

  • ToolStripOverflowButton: needs to use ToolStrip.OverflowButton
    property to get this item and set a custom AccessibleRole value.

  • DataGridViewEditingPanel: default "Role" is Client.
    Needs to use DataGridView.EditingPanel to get the control and set
    a custom value for its AccessibleRole.


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.

  1. Details view

listview-inspect-details-view-tree

  1. LargeIcon view

listview-inspect-largeicon-view-tree

  1. List view

listview-inspect-list-view-tree

  1. SmallIcon view

listview-inspect-smallicon-view-tree

  1. Tile view

listview-inspect-tile-view-tree

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:

  1. An application has visual styles are enabled.
  2. A ListView is not in a virtual mode.
  3. The ShowGroups property has true value
  4. A ListView has one or more groups
  5. A ListView is not in a List view
cs
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));
  1. Details view

listview-withgroup-inspect-details-view-tree

  1. LargeIcon view

listview-withgroup-inspect-largeicon-view-tree

  1. List view

listview-withgroup-inspect-list-view-tree

  1. SmallIcon view

listview-withgroup-inspect-smallicon-view-tree

  1. Tile view

listview-withgroup-inspect-tile-view-tree

Accessibility tree for a ListView with disabled visual styles

ListViewGroups are not visible when visual styles are disabled.

cs
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));
  1. Details view

listview-disabledstyles-inspect-details-view-tree

  1. LargeIcon view

listview-disabledstyles-inspect-largeicon-view-tree

  1. List view

listview-disabledstyles-inspect-list-view-tree

  1. SmallIcon view

listview-disabledstyles-inspect-smallicon-view-tree

  1. Tile view

listview-disabledstyles-inspect-tile-view-tree

Accessibility tree for a ListView with disabled ListViewGroups

ListViewGroups are not visible when ShowGroups property is set to false.

cs
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));
  1. Details view

listview-showgroupdisabled-inspect-details-view-tree

  1. LargeIcon view

listview-showgroupdisabled-inspect-largeicon-view-tree

  1. List view

listview-showgroupdisabled-inspect-list-view-tree

  1. SmallIcon view

listview-showgroupdisabled-inspect-smallicon-view-tree

  1. Tile view

listview-showgroupdisabled-inspect-tile-view-tree

Accessibility tree for a ListView in virtual mode

ListViewGroups are not visible when ListView is in virtual mode.

cs
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);
  1. Details view

listview-virtualmode-inspect-details-view-tree

  1. LargeIcon view

listview-virtualmode-inspect-largeicon-view-tree

  1. List view

listview-virtualmode-inspect-list-view-tree

  1. SmallIcon view

listview-virtualmode-inspect-smallicon-view-tree

  1. 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.

cs
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));
  1. Details view

listview-withoutgroup-inspect-details-view-tree

  1. LargeIcon view

listview-withoutgroup-inspect-largeicon-view-tree

  1. List view

listview-withoutgroup-inspect-list-view-tree

  1. SmallIcon view

listview-withoutgroup-inspect-smallicon-view-tree

  1. Tile view

listview-withoutgroup-inspect-tile-view-tree

ListViewSubItem

Accessibility tree for a ListViewItem

A ListViewSubItem is display if the following conditions are met:

  1. A ListView is in a List or Details views.
  2. A ListView has one or more ListViewSubItems
  3. A ListView has one or more Columns
  4. The ListViewItem has a TileSize enough to display ListViewSubItems (only for Tile view)
cs
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);
  1. Details view

listviewsubitem-inspect-details-view-tree

  1. LargeIcon view

listviewsubitem-inspect-largeicon-view-tree
This view does not support ListViewSubItems

  1. List view

listviewsubitem-inspect-list-view-tree
This view does not support ListViewSubItems

  1. SmallIcon view

listviewsubitem-inspect-smallicon-view-tree
This view does not support ListViewSubItems

  1. Tile view

listviewsubitem-inspect-tile-view-tree

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.

cs
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));

listviewsubitem-inspect-details-view-tree

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.

cs
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));

listviewsubitem-twocolumns-inspect-details-view-tree

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.

cs
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));

listviewsubitem-twosubitems-inspect-details-view-tree

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) and
ListViewSubItem.FragmentNavigate(NavigateDirection.PreviousSibling) methods, also never return a 0-indexed ListViewSubItem

All ListViewSubItem is display if the following conditions are met:

  1. A ListView has one or more ListViewSubItems
  2. A ListView has one or more Columns
  3. The ListViewItem has a TileSize enough to display ListViewSubItems
cs
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);

listviewsubitem-inspect-tile-view-tree

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.

cs
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);

listviewsubitem-twocolumns-inspect-tile-view-tree

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.

cs
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);

listviewsubitem-threeitems-inspect-tile-view-tree

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.

cs
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);

listviewsubitem-smallsize-inspect-tile-view-tree

ListViewSubItems are not displayed when visual styles are disabled for an application. As a result, ListViewSubItems are also hidden in the Accessibility tree

cs
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);

listviewsubitem-disabledvisualstyles-inspect-tile-view-tree


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.

  1. Month view

monthcalendar-inspect-month-view-tree

  1. Year view

monthcalendar-inspect-year-view-tree

  1. Decade view

monthcalendar-inspect-decade-view-tree

  1. Century view

monthcalendar-inspect-century-view-tree

Accessibility properties

  1. 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
  • Name is 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:

  • Name is 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. Description is 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 = always false
  • 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 = always false
  • 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
  1. 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
  • Name is 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:

  • Name is 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 is 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
  • Description is 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
  1. 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
  • Name is 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:

  • Name is 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 is 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
  • Description is 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
  1. 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
  • Name is 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:

  • Name is 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 is 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
  • Description is 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.

  1. 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
  1. 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
  1. 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
  1. 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 Screenshotmonthcalendar-gray-dates-accessible-from-point
  • Returns a calendar accessible object, if its Bounds contains mouse coordinates, and if there is no any cell in this point Screenshotmonthcalendar-calendar-accessible-from-point
  • Returns a MonthCalendar accessible object, if MaxDate and MinDate are set, and several months are missing there (they are invisible) and if mouse is hovering some invisible calendar Screenshotmonthcalendar-control-accessible-from-point
  • 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) Screenshotmonthcalendar-first-weeknumber-accessible-from-point
  • Doesn't return week number cells accessible objects of the last calendar, if MaxDate is set, and mouse is hovering them Screenshotmonthcalendar-last-weeknumbers-accessible-from-point
  • Returns a correct accessible object, that is visible, if MaxDate, MinDate, FirstDayOfWeek are changed or the display range is changed via the "Next"/"Previous" buttons

Narrator

  1. 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, FirstDayOfWeek are changed or the display range is changed via the "Next"/"Previous" buttons
  1. 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, FirstDayOfWeek are changed or the display range is changed via the "Next"/"Previous" buttons
  1. 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, FirstDayOfWeek are changed or the display range is changed via the "Next"/"Previous" buttons
  1. 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, FirstDayOfWeek are changed or the display range is changed via the "Next"/"Previous" buttons

AccessibilityInsights

  1. 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
  1. 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
  1. 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
  1. 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

  1. Month view
  • Case: Change the Today date (set TodayDate of 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). Set MinDate of 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). Set MinDate of 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). Set MaxDate of 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). Set MaxDate of 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). Set MinDate of 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). Set MinDate of 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). Set MaxDate of 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). Set MaxDate of 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 new FirstDayOfWeek (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 new FirstDayOfWeek (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: MinDate is more then the selected range.
    Expected: The focused cell changes. The new focused cell has "focused" accessibility state.
  • Case: MaxDate is 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: MinDate is set for a MonthCalendar.
    Expected: Dates before MinDate are invisible and are not accessible.
  • Case: MaxDate is set for a MonthCalendar.
    Expected: Dates after MaxDate are invisible and are not accessible.
  • Case: MaxDate and MinDate are 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: MaxDate and MinDate are 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 due MinDate and MinDate).
    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.
  1. Year view
  • Case: Change the Today date (set TodayDate of 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. Set MinDate of 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. Set MinDate of 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. Set MaxDate of 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. Set MaxDate of 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. Set MinDate of 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. Set MaxDate of 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: MinDate is more then the selected range.
    Expected: The focused cell changes. The new focused cell has "focused" accessibility state.
  • Case: MaxDate is 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: MinDate is set for a MonthCalendar.
    Expected: Dates before MinDate are invisible and are not accessible.
  • Case: MaxDate is set for a MonthCalendar.
    Expected: Dates after MaxDate are invisible and are not accessible.
  • Case: MaxDate and MinDate are 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: MaxDate and MinDate are 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 due MinDate and MinDate).
    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.
  1. Decade view
  • Case: Change the Today date (set TodayDate of 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. Set MinDate of 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. Set MinDate of 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. Set MaxDate of 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. Set MaxDate of 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. Set MinDate of 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. Set MaxDate of 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: MinDate is more then the selected range.
    Expected: The focused cell changes. The new focused cell has "focused" accessibility state.
  • Case: MaxDate is 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: MinDate is set for a MonthCalendar.
    Expected: Dates before MinDate are invisible and are not accessible.
  • Case: MaxDate is set for a MonthCalendar.
    Expected: Dates after MaxDate are invisible and are not accessible.
  • Case: MaxDate and MinDate are 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: MaxDate and MinDate are 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 due MinDate and MinDate).
    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.
  1. Century view
  • Case: Change the Today date (set TodayDate of 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. Set MinDate of 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. Set MinDate of 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. Set MaxDate of 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. Set MaxDate of 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. Set MinDate of 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. Set MaxDate of 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: MinDate is more then the selected range.
    Expected: The focused cell changes. The new focused cell has "focused" accessibility state.
  • Case: MaxDate is 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: MinDate is set for a MonthCalendar.
    Expected: Dates before MinDate are invisible and are not accessible.
  • Case: MaxDate is set for a MonthCalendar.
    Expected: Dates after MaxDate are invisible and are not accessible.
  • Case: MaxDate and MinDate are 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: MaxDate and MinDate are 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 due MinDate and MinDate).
    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, Size are 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 or
Application.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:

cs
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 after
InitializeComponent; 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 after
InitializeComponent; 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 conventional
IContainer 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 put
Private 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 with
Handles 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, or
Handles 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 named
GenerateMember: when it is False, components can be locals, and supported
AddHandler 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)

Short URL Full URL
https://aka.ms/WfoGenManifest https://github.com/dotnet/winforms/blob/main/docs/analyzers/AppManifestAnalyzer.Help.md
https://aka.ms/WfoExperimental https://github.com/dotnet/winforms/blob/main/docs/analyzers/Experimental.Help.md
https://aka.ms/WfoCsAppConfig https://github.com/dotnet/winforms/blob/main/docs/analyzers/ApplicationConfigurationGenerator.Help.md
https://aka.ms/WfoCsAnalyzers https://github.com/dotnet/winforms/blob/main/docs/analyzers/WinFormsCSharpAnalyzers.Help.md
https://aka.ms/WfoVbAnalyzers https://github.com/dotnet/winforms/blob/main/docs/analyzers/WinFormsVisualBasicAnalyzers.Help.md

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.

cs
// 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.

  • Left arrow indicates the X coordinate of the button location with respect to the parent's display rectangle.
  • Top arrow indicates the Y coordinate of the button location with respect to the parent's display rectangle.
  • Right arrow indicates the distance from right edge of the parent's rectangle where button is placed.
  • Right arrow 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:

cs
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:

cs
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:

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.

  1. WinForms applications developed on a machine with DPI settings different from the machine where they are executed may encounter layout inconsistencies.

  2. 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.

xml
<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:

  1. Improved Type Resolution for .NET Framework projects.
  2. ActiveX/COM support for both .NET Framework and .NET projects.
  3. 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:

xml
<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:

  1. 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).
  2. Add Toolbox support for third-party and custom controls.
  3. Add setting for UseWinFormsOutOfProcDesigner property 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:

  1. File > Add > New Project... > Windows Forms App (.NET Core), choose C# or Visual Basic
  2. Specify project name, e.g. SimpleWinForms

Create from command line

Open your favorite console, create a new folder for your application:

cmd
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:

cmd
dotnet run --project SimpleWinForms\SimpleWinForms.csproj

2. 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

  1. Open SimpleWinForms.sln

  2. Open the SimpleWinForms project file by double clicking on it in Solution Explorer. Change the TargetFramework property from:

    diff
    -    <TargetFramework>net6.0-windows</TargetFramework>
    +    <TargetFrameworks>net472;net6.0-windows</TargetFrameworks>
  3. 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:

    edit-project-file

Option 2: Shadow .NET Frameowk project

  1. Open SimpleWinForms.sln

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

    add-netfx-project

  3. Name the new .NET Framework project as the .NET project, but add ".Designer" to it (e.g. SimpleWinForms.Designer)

  4. Go to the .NET Framework project properties and set the default namespace to the .NET project's namespace

    edit-namespace

  5. Delete the existing Form files in both projects

  6. Add a new Windows Form in the .NET Framework project's context menu Add > Windows Form...

    add-new-form

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

    add-new-form-dialog

  8. Give the name and click [Add].

    :exclamation: IMPORTANT: You need to trigger a form change event for the Designer to create a resx file. You can do it by resizing the form for a couple of pixels or changing the form's Text property. Don't forget to save.

  9. 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.

  10. 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.

    add-as-link

  11. Compile the solution to see if the file references were set up correctly

  12. 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:


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:

text
[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:

powershell
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.

c#
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.

c#
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.

c#
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().

c#
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.

mermaid
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 first

Trimming 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:

  1. Painting suspension with optional layout suspension across a control tree.
  2. 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

csharp
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);
}
  • Control implements ISupportSuspendPainting explicitly and exposes protected virtual
    BeginSuspendPaintingCore / EndSuspendPaintingCore hooks.
  • ListView, ListBox, ComboBox, TreeView, and RichTextBox route painting
    suspension through their existing BeginUpdate / EndUpdate paths.
  • None suspends painting only.
  • TopLevelOnly suspends layout on the target Control.
  • Traverse suspends layout on the target and all existing descendants.
  • The predicate is evaluated for the target and every descendant. A false result 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-Control target behavior in the proposal.

Sub-feature B β€” deferred form reveal

Use the current FormRevealMode design from the tracked API proposal:

csharp
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 latest
API 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 ISupportSuspendPainting on Control with ref-counted
    BeginSuspendPaintingCore / EndSuspendPaintingCore hooks.

  • Route ListView, ListBox, ComboBox, TreeView, and RichTextBox through their
    existing BeginUpdate / EndUpdate mechanisms.

  • Keep SuspendPaintingScope as a sealed, idempotent IDisposable class so it can span
    await.

  • Provide these extension methods:

    csharp
    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);
  • LayoutSuspendTraversal has None, TopLevelOnly, and Traverse.

  • Snapshot selected controls before suspension. Suspend root-to-leaf and resume
    deepest-first.

  • The predicate is evaluated for the target and every descendant. A false result 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-Control target 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, and
    Application configuration 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 Control property-store patterns apply.
  • Do not change existing public BeginUpdate / EndUpdate behavior.

Tests

  • Painting ref-count balance, nesting, idempotent disposal, handle creation, and handle
    recreation.
  • None, TopLevelOnly, and Traverse layout 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-Control target 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 .editorconfig settings 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:

cs
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:

cs
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.

ini
[*.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 = true

Support 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:

xml
<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:

vb
<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 Sub

Code generated by VisualStudio v17.5 Preview 3, or newer:

vb
<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 Sub

Summary

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_FALSE from the TDN_BUTTON_CLICKED
    notification.
text
/* 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.
text
/* 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.
text
/* 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 a
TDM_CLICK_RADIO_BUTTON message within a
TDN_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 with TDN_RADIO_BUTTON_CLICKED notifications even though we
    don't send any more messages to the dialog.
text
/* 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 an
TDN_RADIO_BUTTON_CLICKED
notification.

  • Run the code.

  • Notice that within the
    TDN_TIMER notification, a
    TDM_CLICK_RADIO_BUTTON is
    sent to select the second radio button which causes an inner dialog to be shown within
    the TDN_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.CheckedChanged event
    that is expected to show already the new state, this can be worked-around by ignoring
    TDN_RADIO_BUTTON_CLICKED notifications caused by the code itself, and instead raising
    the events after sending the TDM_CLICK_RADIO_BUTTON message.)
    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.

text
/* Detailed source-code truncated for AI context efficiency. */

--- METRICS ---