You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

199 lines
8.4 KiB

  1. # Modernization of code-behind in WinForms OOP designer
  2. ## Background
  3. 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](https://learn.microsoft.com/dotnet/framework/reflection-and-codedom/using-the-codedom)), that tree is saved to the generated code-behind file in the `InitializeComponent` method. We call this feature design time serialization.
  4. Previously COM-based [CodeModel interface](https://learn.microsoft.com/dotnet/api/envdte.codemodel?view=visualstudiosdk-2022) 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.
  5. ## What's different
  6. Starting with [Visual Studio 2022 v17.5 Preview 3](https://visualstudio.microsoft.com/vs/preview/), the WinForms out-of-process designer uses [Roslyn](https://github.com/dotnet/roslyn) for design time serialization.
  7. 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.
  8. ## Why change it
  9. * The use of Roslyn unblocks multithreaded serialization and deserialization to solve performance delays in designer load and unload scenarios.
  10. * Code-behind generation now respects `.editorconfig` settings and thus matches code style in the source files.
  11. * If implicit usings feature is enabled in project properties, global namespaces are not repeated in type names.
  12. * Further modernization of code-behind is unblocked.
  13. * Newly generated code will work with modern Roslyn analyzers.
  14. ## How the new code looks like
  15. 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.
  16. For example, `InitializeComponent` method for a form with a button will be modified like this on the first save:
  17. ![Comparison of Form1.Designer.cs as generated by Visual Studio 2022 v17.4 or earlier against the same file generated by v17.5 Preview 3](./images/Form1-Designer-C%23-comparison.png)
  18. Code generated by VisualStudio v17.4 or earlier:
  19. ```cs
  20. private void InitializeComponent()
  21. {
  22. this.button1 = new System.Windows.Forms.Button();
  23. this.SuspendLayout();
  24. //
  25. // button1
  26. //
  27. this.button1.Location = new System.Drawing.Point(58, 60);
  28. this.button1.Name = "button1";
  29. this.button1.Size = new System.Drawing.Size(136, 69);
  30. this.button1.TabIndex = 0;
  31. this.button1.Text = "button1";
  32. this.button1.UseVisualStyleBackColor = true;
  33. //
  34. // Form1
  35. //
  36. this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 25F);
  37. this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
  38. this.ClientSize = new System.Drawing.Size(270, 208);
  39. this.Controls.Add(this.button1);
  40. this.Name = "Form1";
  41. this.Text = "Form1";
  42. this.ResumeLayout(false);
  43. }
  44. ```
  45. Code generated by VisualStudio v17.5 Preview 3, or newer:
  46. ```cs
  47. private void InitializeComponent()
  48. {
  49. button1 = new Button();
  50. SuspendLayout();
  51. //
  52. // button1
  53. //
  54. button1.Location = new Point(58, 60);
  55. button1.Name = "button1";
  56. button1.Size = new Size(136, 69);
  57. button1.TabIndex = 0;
  58. button1.Text = "button1";
  59. button1.UseVisualStyleBackColor = true;
  60. //
  61. // Form1
  62. //
  63. AutoScaleDimensions = new SizeF(10F, 25F);
  64. AutoScaleMode = AutoScaleMode.Font;
  65. ClientSize = new Size(270, 208);
  66. Controls.Add(button1);
  67. Name = "Form1";
  68. Text = "Form1";
  69. ResumeLayout(false);
  70. }
  71. ```
  72. ### Support for code style settings
  73. The above code was generated for default C# code style settings, thus `this` access qualifier was removed in the Preview 3 version.
  74. To preserve legacy code generation style in `InitializeComponent` method, set style preferences to legacy values in [`.editorconfig` file](https://learn.microsoft.com/visualstudio/ide/create-portable-custom-editor-options).
  75. ```ini
  76. [*.designer.cs]
  77. # this. preferences
  78. dotnet_style_qualification_for_event = true
  79. dotnet_style_qualification_for_field = true
  80. dotnet_style_qualification_for_method = true
  81. dotnet_style_qualification_for_property = true
  82. [*.vb]
  83. # Me. preferences
  84. dotnet_style_qualification_for_event = true
  85. dotnet_style_qualification_for_field = true
  86. dotnet_style_qualification_for_method = true
  87. dotnet_style_qualification_for_property = true
  88. ```
  89. ### Support for implicit usings property
  90. 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](https://learn.microsoft.com/dotnet/core/tutorials/top-level-templates#implicit-using-directives) property, available in C# starting with .NET 6 for C#, from the project file:
  91. ```xml
  92. <Project Sdk="Microsoft.NET.Sdk">
  93. <PropertyGroup>
  94. <OutputType>WinExe</OutputType>
  95. <TargetFramework>net8.0-windows</TargetFramework>
  96. <Nullable>enable</Nullable>
  97. <UseWindowsForms>true</UseWindowsForms>
  98. <ImplicitUsings>enable</ImplicitUsings>
  99. </PropertyGroup>
  100. </Project>
  101. ```
  102. When implicit usings property is disabled in the C# project file, namespace names are generated.
  103. ### Similar changes are present in Visual Basic
  104. ![Comparison of Form1.Designer.vb as generated by Visual Studio 2022 v17.4 or earlier against the same file generated by v17.5 Preview 3](./images/Form1-Designer-VB-comparison.png)
  105. Code generated by VisualStudio v17.4 or earlier:
  106. ```vb
  107. <System.Diagnostics.DebuggerStepThrough()>
  108. Private Sub InitializeComponent()
  109. Me.Button1 = New System.Windows.Forms.Button()
  110. Me.SuspendLayout()
  111. '
  112. 'Button1
  113. '
  114. Me.Button1.Location = New System.Drawing.Point(58, 60)
  115. Me.Button1.Name = "Button1"
  116. Me.Button1.Size = New System.Drawing.Size(136, 69)
  117. Me.Button1.TabIndex = 0
  118. Me.Button1.Text = "Button1"
  119. Me.Button1.UseVisualStyleBackColor = True
  120. '
  121. 'Form1
  122. '
  123. Me.AutoScaleDimensions = New System.Drawing.SizeF(10.0!, 25.0!)
  124. Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
  125. Me.ClientSize = New System.Drawing.Size(270, 208)
  126. Me.Controls.Add(Me.Button1)
  127. Me.Name = "Form1"
  128. Me.Text = "Form1"
  129. Me.ResumeLayout(False)
  130. End Sub
  131. ```
  132. Code generated by VisualStudio v17.5 Preview 3, or newer:
  133. ```vb
  134. <System.Diagnostics.DebuggerStepThrough()>
  135. Private Sub InitializeComponent()
  136. Button1 = New Button()
  137. SuspendLayout()
  138. '
  139. ' Button1
  140. '
  141. Button1.Location = New Point(58, 60)
  142. Button1.Name = "Button1"
  143. Button1.Size = New Size(136, 69)
  144. Button1.TabIndex = 0
  145. Button1.Text = "Button1"
  146. Button1.UseVisualStyleBackColor = True
  147. '
  148. ' Form1
  149. '
  150. AutoScaleDimensions = New SizeF(10.0F, 25.0F)
  151. AutoScaleMode = AutoScaleMode.Font
  152. ClientSize = New Size(270, 208)
  153. Controls.Add(Button1)
  154. Name = "Form1"
  155. Text = "Form1"
  156. ResumeLayout(False)
  157. End Sub
  158. ```
  159. ## Summary
  160. 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.