Introduction
This article provides instructions for installing and using a Visual Studio 2012
add-in to move Visual Studio 2003 Windows Forms Designer
generated code from the Public Class file to a Partial Class file. In addition, I tell the story of how I created the
add-in.
When Visual Studio 2005 was released, the Visual Studio 2003 Windows Forms Designer generated code was removed from the
.vb file and placed
in a separate .Designer.vb file. Visual Studio 2012 still accepts the Windows Forms Designer generated code in the
.vb file for an existing Project. As part
of my Visual Studio 2012 learning process, I wanted to separate the Windows Forms Designer generated code into a Partial Class
.Designer.vb file.
The Macro
I found several versions of a Visual Studio Macro for doing this at http://www.nathanpjones.com/wp/2010/02/converting-vb-net-2003-winforms-to-20052008-partial-classes.
Unfortunately, I found that the Macros feature had been removed from Visual Studio 2012. After seeing the original author’s comment to “dig into it to get
it to work the way you want", I decided to turn it into a Visual Studio Add-In. Something new to learn!
I want to express my thanks to the original Macro developer
and to those that subsequently submitted their own amendments. That code gave
me a jumpstart on this project. The original Macro traversed the Visual Studio
automation object model of the selected Windows Form .vb file to locate
variable declarations that were initialized by code within the
Sub InitializeComponent
procedure and to locate both the Sub InitializeComponent
and
Sub Dispose
procedures. These along with a Partial Class clause are used to
create a new .Designer.vb file. A subsequent version of the Macro, contributed
by another programmer, selected variables derived from
System.Windows.Forms.Control
or System.ComponentModel.IContainer
.
Background
Use this Visual Studio add-in to move Windows Form Designer generated code from a Public Class .vb file to a Partial Class
.Designer.vb file.
The ConvertVBFiles VB.NET Project associated with his article is compiled to process all Windows Forms within a selected project.
A conditional compilation option is provided to allow you to compile your version to only convert the currently selected Windows Form.
Be sure to create a backup of your Visual Studio Solution/Project before attempting to use this Visual Studio Add-In.
Using the code
A compiled version of ConvertVBFiles.dll is included in the ZIP file. Skip steps 2 and 3 if you want to use the
ConvertVBFiles.dll as-is with the conditional
compilation declaration #CONVERT_ALL_FILES_IN_SELECTED_PROJECT = True
.
- Extract the ConvertVBFiles Project source code into C:\Users\<USERNAME>\Documents\Visual Studio 2012\Projects.
- Edit the value of
#Const CONVERT_ALL_FILES_IN_SELECTED_PROJECT = True
to True or False.
- True processes all Windows Forms within a Project.
- False processes only the selected Windows Form.
- Compile your version of ConvertVBFiles.DLL.
- Extract the ConvertVBFile.AddIn file into C:\Users\<USERNAME>\Documents\Visual Studio 2012\Addins.
- Edit the ConvertVBFile.AddIn file replacing <USERNAME> with your username.
="1.0" ="UTF-16" ="no"
<Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility">
<HostApplication>
<Name>Microsoft Visual Studio</Name>
<Version>11.0</Version>
</HostApplication>
<Addin>
<FriendlyName>Convert VB 'Windows Form Designer generated code' Region to a separate *.Designer.vb file</FriendlyName>
<Description>Convert VB 'Windows Form Designer generated code' Region to a separate *.Designer.vb file</Description>
<Assembly>c:\users\<USERNAME>\documents\visual studio 2012\Projects\ConvertVBFiles\ConvertVBFiles\bin\ConvertVBFiles.dll</Assembly>
<FullClassName>ConvertVBFiles.ConvertVBFiles</FullClassName>
<LoadBehavior>0</LoadBehavior>
<CommandPreload>1</CommandPreload>
<CommandLineSafe>0</CommandLineSafe>
</Addin>
</Extensibility>
If you compiled your version with CONVERT_ALL_FILES_IN_SELECTED_PROJECT
set to
True
, with your Solution open, select a Project and then click Tools,
Convert to Designer Partial Class. The Add-In will iterate through all of the .VB forms, converting those that contain Windows Form Designer generated code
and then display a Message Box detailing what it did.
If you compiled your version with CONVERT_ALL_FILES_IN_SELECTED_PROJECT
set to
False
, with your Solution open, select Windows Form file within
a Project and then click Tools, Convert to Designer Partial Class. The Add-In will convert that file and display a Message Box detailing what it did.
Removal
To remove the ConvertVBFiles add-in from Visual Studio, rename the ConvertVBFiles.AddIn extension in the C:\Users\<USERNAME>\Documents\Visual Studio 2012\Addins directory
or delete the ConvertVBFile.Addin file.
Points of Interest
Conveniently, Visual Studio 2012 provides an Extensibility Project Type in the New Project dialog. I used this to start my new Add-In project.
I pasted the Macro source into a new Sub ExtractWinFormsDesignerFile
procedure in my new project and called it from the generated Sub Exec
procedure.
I cleaned up a few syntax issues, added some Debug.WriteLine
statements to help me learn the process flow, added a few breakpoints and proceeded to start testing.
I found a few cases where the object model yielded Nothing values for an item. I added checks using
IsNothing()
to ignore these items.
The version of the Macro code that I used moved some items that should not have been moved. For example, if I had declared a variable derived
from something in System.Windows.Form
outside of the Windows Form Designer generated code region, it got moved and it shouldn’t have been moved.
An example is a Property declaration for a variable of type ListView.SelectedIndexCollection
.
At this point, I had selected variable declarations using the following nested
If
statements.
If (member.Kind = vsCMElement.vsCMElementVariable) Then
Dim objDeclaration As CodeVariable = DirectCast(member, CodeVariable)
Dim objFieldType As CodeType = objDeclaration.Type.CodeType
If Not IsNothing(objFieldType) Then
If Not IsNothing(objFieldType.Namespace) Then
Dim isControl As Boolean = objFieldType.Namespace.FullName.StartsWith("System.Windows.Forms")
Debug.WriteLine("Namespace=" & objFieldType.Namespace.FullName)
If isControl OrElse objFieldType.IsDerivedFrom("System.ComponentModel.IContainer") Then
Debug.WriteLine("Added " & objFieldType.Namespace.FullName)
strWindowsFormsControlsDeclarations.AppendLine(extractMember(member))
intItemsConvertedCount += 1
End If
End If
End If
End If
A Human Could Tell Which Code to Move
I decided to take advantage of the fact that the object model exposes the absolute character offset of each Sub procedure and each variable declaration.
I used the offsets to determine if a variable declaration was within the Windows Form Designer generated code region. This strategy would take advantage of the order
in which the Visual Studio 2003 Windows Form Designer wrote out the generated code.
This is the order in which the Visual Studio 2003 Windows Form Designer creates the generated code:
Public Class MainForm
Inherits System.Windows.Forms.Form
#Region " Windows Form Designer generated code "
Public Sub New()
…
End Sub
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
…
End Sub
…
… Windows Form Designer generated variable declarations here …
…
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
…
End Sub
Protected Overrides Sub Finalize()
…
End Sub
I added a check that the absolute character offset of a variable declaration was less than the absolute character offset of the
Sub InitializeComponent
procedure and
greater than the character offset of either Sub New
or Sub Dispose
. If so, the variable declaration is moved to the new
.Designer.vb file.
If Dispose or New are not present, I use zero as their offset value.
Doing this creates the small possibility that someone may have declared a System.Windows.Form
-derived variable declaration before the
Windows Form Designer generated code region. If they did, that variable declaration will be moved also but could easily be moved back with cut and paste.
If (member.Kind = vsCMElement.vsCMElementVariable) Then
Dim objDeclaration As CodeVariable = DirectCast(member, CodeVariable)
Dim objFieldType As CodeType = objDeclaration.Type.CodeType
If Not IsNothing(objFieldType) Then
If Not IsNothing(objFieldType.Namespace) Then
Dim isControl As Boolean = objFieldType.Namespace.FullName.StartsWith("System.Windows.Forms")
Dim intOffset As Integer = objDeclaration.StartPoint.AbsoluteCharOffset
Debug.WriteLine("Namespace=" & objFieldType.Namespace.FullName)
If isControl OrElse objFieldType.IsDerivedFrom("System.ComponentModel.IContainer") Then
If intOffset_InitializeComponent > intOffset AndAlso (intOffset_Dispose < _
intOffset OrElse intOffset_New < intOffset) Then
Debug.WriteLine("Added " & objFieldType.Namespace.FullName)
strWindowsFormsControlsDeclarations.AppendLine(extractMember(member))
intItemsConvertedCount += 1
End If
End If
End If
End If
End If
A Friend WithEvents
During testing, I found a few forms where I had declared some System.Windows.Forms
-derived variables before the Windows Form generated code region. I made the following change to the Boolean statement that initializes the
IsControl
variable. The check of the Access property ensures that Friend and
WithEvents
are present. This further reduces the probability that a variable declaration from outside of the Windows Form Designer generated code region will be erroneously moved to the new
.Designer.vb file.
Before
Dim isControl As Boolean = objFieldType.Namespace.FullName.StartsWith("System.Windows.Forms")
After
Dim isControl As Boolean = objFieldType.Namespace.FullName.StartsWith("System.Windows.Forms") AndAlso _
CBool(objDeclaration.Access And (vsCMAccess.vsCMAccessWithEvents + vsCMAccess.vsCMAccessProject))
Visual Studio Tools Menu
I was experiencing an occasional problem where the Add-In would not put its menu item within the Visual Studio Tools menu.
I think this was caused by running in Debug Mode, creating a second instance of Visual Studio (DEVENV.EXE) and not checking the checkboxes in
Tools, Manage Add-Ins. I found that if I checked the check boxes, the DLL could not be replaced when I recompiled during testing.
I changed one line of code within the Extensibility Project Type-provided code to allow the DLL another opportunity to create the Tools menu item.
Within the Sub OnConnection
procedure, I made the following change to cause the menu item add code to execute after startup.
Before
If connectMode = ext_ConnectMode.ext_cm_UISetup Then
After
If connectMode = ext_ConnectMode.ext_cm_UISetup OrElse connectMode = ext_ConnectMode.ext_cm_AfterStartup Then
Productivity Improvement
I continued to do testing and cleanup. My final enhancement was to use the conditional compilation feature to allow compiling a version
of the Add-In that would iterate through all of the .vb files in a selected Project within the Solution.
Convert only the selected Windows Form within a Project
#Const CONVERT_ALL_FILES_IN_SELECTED_PROJECT = False
Convert all Windows Forms within the selected Project
#Const CONVERT_ALL_FILES_IN_SELECTED_PROJECT = True
PrintDocument
A few days after writing the initial version of this article, I found another type of Windows Form Designer generated variable declaration in one of my programs. To move this item from the .vb file to the .Designer.vb file, I changed the IsControl
initialization to the following.
Dim isControl As Boolean = (objFieldType.Namespace.FullName.StartsWith("System.Windows.Forms") OrElse _
objFieldType.Namespace.FullName.StartsWith("System.Drawing.Printing")) AndAlso _
CBool(objDeclaration.Access And (vsCMAccess.vsCMAccessWithEvents + vsCMAccess.vsCMAccessProject))
History
- 01-15-2013: Initial version.
- 01-19-2013: Added check to handle PrintDocument declaration (Namespace: System.Drawing.Printing).