Ever been stuck working in VB.NET 2005 or 2008 with a project created in VB.NET 2003? Aside from language differences, you’re stuck with forms that except for resources are in one file. Not only is this ugly, but it makes the IDE slower, since it redraws when it detects any changes to a file that has designer info.
So I started hunting about for a quick solution to convert this over (why isn’t this automatic MS?) and found a great solution by Duncan Smart, on his blog here. I’ve modified it to work with VB and tested and ran it in VB 2008. Duncan’s didn’t seem to work right on VB code when iterating through the class members, but there does seem to be little predictability when it comes to using the macro system. I also added some extra error checking and a restore-on-error feature.
Here’s the code for the macro. Instructions for usage are in the comments on the top. Sorry for the sometimes sloppy style–I wrote it quick and dirty.
Imports System Imports EnvDTE Imports EnvDTE80 Imports EnvDTE90 Imports System.Diagnostics Public Module ExtractWinFormsDesignerFile ' ------------------------------------------------------------------------- ' Extract WinForms Designer File Visual Studio 2005/2008 Macro ' ------------------------------------------------------------------------- ' Extracts the InitializeComponent() and Dispose() methods and control ' field delarations from a .NET 1.x VS 2003 project into a VS 2005/8 ' style .NET 2.0 partial class in a *.Designer.VB file. (This tested to work ' with VB only.) ' ' To use: ' * Copy the methods below into a Visual Studio Macro Module (use ' ALT+F11 to show the Macro editor) ' * Select a Windows Form in the Solution Explorer ' * Run the macro by showing the Macro Explorer (ALT+F8) and double ' clicking the 'ExtractWinFormsDesignerFile' macro. ' ' Duncan Smart, InfoBasis, 2007 ' From: http://blog.dotsmart.net/2008/05/20/converting-visual-studio-2003-winforms-to-visual-studio-20052008-partial-classes/ ' Modified and updated by Nathan Jones, 2010 ' See: https://www.nathanpjones.com/ ' ------------------------------------------------------------------------- Sub ExtractWinFormsDesignerFile() Dim item As ProjectItem = DTE.SelectedItems.Item(1).ProjectItem Dim sourceFileName As String = item.FileNames(1) Dim sourceDir As String = System.IO.Path.GetDirectoryName(sourceFileName) Dim origCode As String Dim bareName As String = System.IO.Path.GetFileNameWithoutExtension(sourceFileName) Dim newDesignerFile As String = sourceDir & "\" & bareName & ".Designer.vb" If IO.File.Exists(newDesignerFile) Then MsgBox("Designer file already exists!") Exit Sub Else If MsgBox("You are about to extract designer code from:" + vbCrLf + _ vbCrLf + _ " " + sourceFileName + vbCrLf + _ vbCrLf + _ "To the following file:" + vbCrLf + _ vbCrLf + _ " " + newDesignerFile + vbCrLf + _ vbCrLf + _ "MAKE SURE TO BACK UP FIRST!", _ MsgBoxStyle.OkCancel Or MsgBoxStyle.DefaultButton2 Or MsgBoxStyle.Question, _ "Confirm") = MsgBoxResult.Cancel Then Exit Sub End If origCode = System.IO.File.ReadAllText(sourceFileName) Dim codeClass As CodeClass Dim namespaceName As String codeClass = findClass(item.FileCodeModel.CodeElements) namespaceName = "" If codeClass.Namespace IsNot Nothing Then namespaceName = codeClass.Namespace.FullName End If Dim initComponentText As String Dim disposeText As String Dim fieldDecls() As String Try initComponentText = extractMember(codeClass.Members.Item("InitializeComponent")) Catch MsgBox("Error extracting InitializeComponent!") Exit Sub End Try Try disposeText = extractMember(codeClass.Members.Item("Dispose")) Catch MsgBox("Error extracting Dispose! Will restore source file.") System.IO.File.WriteAllText(sourceFileName, origCode) Exit Sub End Try Try fieldDecls = extractWinFormsFields(codeClass, initComponentText) Catch MsgBox("Error extracting field declares! Will restore source file.") System.IO.File.WriteAllText(sourceFileName, origCode) Exit Sub End Try Dim finalCode As String finalCode = IIf(namespaceName <> "", "Namespace " + namespaceName + vbCrLf + vbCrLf, "") + _ "<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _" + vbCrLf + _ "Partial Class " + codeClass.Name + vbCrLf + _ " Inherits System.Windows.Forms.Form" + vbCrLf + _ vbCrLf + _ disposeText + vbCrLf + _ vbCrLf + _ fieldDecls(0) + vbCrLf + _ vbCrLf + _ initComponentText + vbCrLf + _ fieldDecls(1) + vbCrLf + _ "End Class" + vbCrLf + _ IIf(namespaceName <> "", vbCrLf + "End Namespace" + vbCrLf, "") ' Now write the new designer file System.IO.File.WriteAllText(newDesignerFile, finalCode) Dim newProjItem As ProjectItem = item.ProjectItems.AddFromFile(newDesignerFile) Try newProjItem.Open() Catch End Try Try DTE.ExecuteCommand("Edit.FormatDocument") Catch End Try MsgBox("Code separated successfully!" + vbCrLf + _ "You may have residual comments or code regions in the source " + vbCrLf + _ "class that should be deleted or moved to the new Designer.vb class.", _ MsgBoxStyle.Information, _ "Complete") End Sub Function findClass(ByVal items As System.Collections.IEnumerable) As CodeClass For Each codeEl As CodeElement In items If codeEl.Kind = vsCMElement.vsCMElementClass Then Return codeEl ElseIf codeEl.Children.Count > 0 Then Dim cls As CodeClass = findClass(codeEl.Children) If cls IsNot Nothing Then Return findClass(codeEl.Children) End If End If Next Return Nothing End Function Function extractWinFormsFields(ByVal codeClass As CodeClass, ByVal initComponentsCode As String) As String() Dim member As CodeElement Dim fieldsCode As String Dim components As String Dim initComponentsCodeForComp As String = initComponentsCode.ToLower fieldsCode = "" For i As Integer = codeClass.Members.Count To 1 Step -1 member = codeClass.Members.Item(i) If member.Kind = vsCMElement.vsCMElementVariable Then Dim field As CodeVariable = member If field.Type.TypeKind <> vsCMTypeRef.vsCMTypeRefArray Then If field.Name.ToLower = "components" Then ' We'll insert this separately components = extractMember(field) ElseIf initComponentsCodeForComp Like ("* Me." + field.Name + " = New *").ToLower Then fieldsCode = extractMember(field) + vbCrLf + _ fieldsCode End If End If End If Next Return New String() {components, fieldsCode} End Function Function extractMember(ByVal memberElement As CodeElement) As String Dim memberStart As EditPoint = memberElement.GetStartPoint().CreateEditPoint() Dim memberText As String = String.Empty memberText += memberStart.GetText(memberElement.GetEndPoint()) memberStart.Delete(memberElement.GetEndPoint()) Return memberText End Function End Module
Thanks for the macro converter in VB.
There is a small bug in your code – change MsgBoxResult.No to MsgBoxResult.Cancel in the following lines:
…
MsgBoxStyle.OkCancel Or MsgBoxStyle.DefaultButton2 Or MsgBoxStyle.Question, _
“Confirm”) = MsgBoxResult.Cancel Then Exit Sub
Good catch! I went ahead and fixed it.
Thanks for the marco, works great, just what I’ve been looking for!
Nathan,
I like what you’ve done.
Here’s a version that moves the existing “Inherits” line to the new file instead of assuming that the base type is “Form” and creating a (hopefully) duplicate copy there…
Option Strict On
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports EnvDTE90a
Imports EnvDTE100
Imports System.Diagnostics
Public Module MoveDesignerCodeToPartialClass
‘ ————————————————————————-
‘ Extract WinForms Designer File Visual Studio 2005/2008 Macro
‘ ————————————————————————-
‘ Extracts the InitializeComponent() and Dispose() methods and control
‘ field delarations from a .NET 1.x VS 2003 project into a VS 2005/8
‘ style .NET 2.0 partial class in a *.Designer.cs file.
‘
‘ To use:
‘ * Copy the methods below into a Visual Studio Macro Module (use
‘ ALT+F11 to show the Macro editor)
‘ * Select a Windows Form in the Solution Explorer
‘ * Run the macro by showing the Macro Explorer (ALT+F8) and double
‘ clicking the ‘ExtractWinFormsDesignerFile’ macro.
‘
‘ Duncan Smart, InfoBasis, 2007
‘ Adam Best, 2009
‘ Jeff Shelby, 2010
‘ ————————————————————————-
Public Sub ExtractWinFormsDesignerFile()
Dim item As ProjectItem = DTE.SelectedItems.Item(1).ProjectItem
Dim fileName As String = item.FileNames(1)
Dim dir As String = System.IO.Path.GetDirectoryName(fileName)
Dim bareName As String = System.IO.Path.GetFileNameWithoutExtension(fileName)
Dim newItemPath As String = System.IO.Path.Combine(dir, bareName & “.Designer.vb”)
‘If the designer file already exists,
‘don’t create another one.
For Each childItem As ProjectItem In item.ProjectItems
If (childItem.FileNames(1) = newItemPath) Then
MsgBox(“This form already has a Designer file. It will not be converted.”)
Exit Sub
End If
Next
Dim codeClass As CodeClass = findClass(item.FileCodeModel.CodeElements)
‘If we didn’t find a non-partial
‘code class, there’s nothing to do.
If IsNothing(codeClass) Then
Exit Sub
End If
Dim initComponentText As String = extractMember(codeClass.Members.Item(“InitializeComponent”))
Dim disposeText As String = extractMember(codeClass.Members.Item(“Dispose”))
Dim fieldDecls As String = extractWinFormsFields(codeClass)
Dim PartialClassFileContents As New System.Text.StringBuilder
With PartialClassFileContents
If (Not IsNothing(codeClass.Namespace)) Then
.Append(“Namespace ” & codeClass.Namespace.FullName())
End If
.Append(” _” & vbCrLf)
.Append(“Partial Class ” & codeClass.Name & vbCrLf)
.Append(vbCrLf)
.Append(disposeText & vbCrLf)
.Append(fieldDecls & vbCrLf)
.Append(initComponentText & vbCrLf)
.Append(“End Class” & vbCrLf)
If (Not IsNothing(codeClass.Namespace)) Then
.Append(“End Namespace”)
End If
End With
System.IO.File.WriteAllText(newItemPath, PartialClassFileContents.ToString)
Dim newProjItem As ProjectItem = item.ProjectItems.AddFromFile(newItemPath)
‘There’s only one element in the file, which is the partial class we just created.
Dim newCodeClass As CodeClass = DirectCast(newProjItem.FileCodeModel.CodeElements.Item(1), CodeClass)
‘Move the “Inherits” statement from the original file to the new one.
newCodeClass.AddBase(extractBase(codeClass))
‘Reformat the new file.
newProjItem.Open(Constants.vsViewKindCode)
newProjItem.Document.Activate()
DTE.ExecuteCommand(“Edit.FormatDocument”)
End Sub
Private Function findClass(ByVal items As System.Collections.IEnumerable) As CodeClass
‘The designer generated code was originally placed in the same code behind
‘file as the custom code because partial classes were not supported. Knowing
‘this, we will assume that “partial class” has not been used to split this
‘form into multiple code files. If that assumption is correct, the first
‘class we run into will be the one that contains the designer generated code.
For Each codeEl As CodeElement In items
If (codeEl.Kind = vsCMElement.vsCMElementClass) Then
‘Convert the original class declaration to a partial class.
Dim start As EditPoint = codeEl.GetStartPoint().CreateEditPoint()
Dim endPoint As TextPoint = codeEl.GetStartPoint(vsCMPart.vsCMPartBody)
Dim text As String = start.GetText(endPoint)
If text.Contains(“partial class”) Then
‘Bad news. Our assumption was violated so
‘we probably don’t want to carry forward.
MsgBox(“The code is already in “”partial class”” form so it will not be converted.”)
Return Nothing
End If
start.Delete(endPoint)
Dim newText As String = text.Replace(“class”, “partial class”)
start.Insert(newText)
Return DirectCast(codeEl, CodeClass)
ElseIf (codeEl.Children.Count > 0) Then
Dim cls As CodeClass = findClass(codeEl.Children)
If Not IsNothing(cls) Then
Return cls
End If
End If
Next
Return Nothing
End Function
Private Function extractBase(ByVal codeClass As CodeClass) As String
‘Due to a “feature” of the FullName implementation for VB
‘this will not work if the base class is a generic type.
‘There is a way around this using the VBGenericExtender
‘(see http://social.msdn.microsoft.com/Forums/en/vsx/thread/59dd091b-a11f-4d27-9e77-df43b664b2b1)
‘however:
‘a) Having a generic base type seems unlikely in this case
‘b) Using VBGenericExtender wouldn’t work for C# code which
‘ makes it harder to convert this code back and forth.
Dim BaseName As String = codeClass.Bases.Item(1).FullName
codeClass.RemoveBase(BaseName)
Return BaseName
End Function
Private Function extractWinFormsFields(ByVal codeClass As CodeClass) As String
Dim fieldsCode As New System.Text.StringBuilder
‘We can’t just iterate over the members of the Members
‘collection because we will be changing its membership
‘inside the loop.
Dim MemberCount As Integer = codeClass.Members.Count
For iMember As Integer = MemberCount To 1 Step -1
Dim member As CodeElement = codeClass.Members.Item(iMember)
If (member.Kind = vsCMElement.vsCMElementVariable) Then
Dim field As CodeVariable = DirectCast(member, CodeVariable)
If (field.Type.TypeKind vsCMTypeRef.vsCMTypeRefArray) Then
Dim fieldType As CodeType = field.Type.CodeType
Dim isControl As Boolean = _
fieldType.Namespace.FullName.StartsWith(“System.Windows.Forms”) _
OrElse fieldType.IsDerivedFrom(“System.Windows.Forms.Control”) _
OrElse fieldType.IsDerivedFrom(“System.ComponentModel.IContainer”)
If isControl Then
fieldsCode.AppendLine(extractMember(member))
End If
End If
End If
Next
Return fieldsCode.ToString()
End Function
Private Function extractMember(ByVal memberElement As CodeElement) As String
Dim memberStart As EditPoint = memberElement.GetStartPoint().CreateEditPoint()
Dim memberText As String = memberStart.GetText(memberElement.GetEndPoint())
memberStart.Delete(memberElement.GetEndPoint())
Return memberText
End Function
End Module
Excellent post!
I also wanted to contribute. Here is a version that can convert also User Controls, by virtue of actually querying the base class of the selected item:
(Technically, what it does is to insert into the designer whatever base class the original form/usercontrol/designable item has)
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics
Public Module ExtractWinFormsDesignerFile
‘ ————————————————————————-
‘ Extract WinForms Designer File Visual Studio 2005/2008 Macro
‘ ————————————————————————-
‘ Extracts the InitializeComponent() and Dispose() methods and control
‘ field delarations from a .NET 1.x VS 2003 project into a VS 2005/8
‘ style .NET 2.0 partial class in a *.Designer.VB file. (This tested to work
‘ with VB only.)
‘
‘ To use:
‘ * Copy the methods below into a Visual Studio Macro Module (use
‘ ALT+F11 to show the Macro editor)
‘ * Select a Windows Form in the Solution Explorer
‘ * Run the macro by showing the Macro Explorer (ALT+F8) and double
‘ clicking the ‘ExtractWinFormsDesignerFile’ macro.
‘
‘ Duncan Smart, InfoBasis, 2007
‘ From: http://blog.dotsmart.net/2008/05/20/converting-visual-studio-2003-winforms-to-visual-studio-20052008-partial-classes/
‘ Modified and updated by Nathan Jones, 2010
‘ See: https://www.nathanpjones.com/
‘ ————————————————————————-
Sub ExtractWinFormsDesignerFile()
Dim item As ProjectItem = DTE.SelectedItems.Item(1).ProjectItem
Dim sourceFileName As String = item.FileNames(1)
Dim sourceDir As String = System.IO.Path.GetDirectoryName(sourceFileName)
Dim origCode As String
Dim bareName As String = System.IO.Path.GetFileNameWithoutExtension(sourceFileName)
Dim newDesignerFile As String = sourceDir & “\” & bareName & “.Designer.vb”
If IO.File.Exists(newDesignerFile) Then
MsgBox(“Designer file already exists!”)
Exit Sub
Else
If MsgBox(“You are about to extract designer code from:” + vbCrLf + _
vbCrLf + _
” ” + sourceFileName + vbCrLf + _
vbCrLf + _
“To the following file:” + vbCrLf + _
vbCrLf + _
” ” + newDesignerFile + vbCrLf + _
vbCrLf + _
“MAKE SURE TO BACK UP FIRST!”, _
MsgBoxStyle.OkCancel Or MsgBoxStyle.DefaultButton2 Or MsgBoxStyle.Question, _
“Confirm”) = MsgBoxResult.Cancel Then Exit Sub
End If
origCode = System.IO.File.ReadAllText(sourceFileName)
Dim codeClass As CodeClass
Dim namespaceName As String
Dim baseClass As CodeClass
Dim baseClassName As String
codeClass = findClass(item.FileCodeModel.CodeElements)
namespaceName = “”
baseClassName = “”
If codeClass.Namespace IsNot Nothing Then
namespaceName = codeClass.Namespace.FullName
If codeClass.Bases.Count > 0 Then
baseClass = codeClass.Bases.Item(1)
baseClassName = baseClass.FullName
End If
End If
If baseClassName = “” Then
MsgBox(“No base class defined. Cannot continue!”)
End If
Dim initComponentText As String
Dim disposeText As String
Dim fieldDecls() As String
Try
initComponentText = extractMember(codeClass.Members.Item(“InitializeComponent”))
Catch
MsgBox(“Error extracting InitializeComponent!”)
Exit Sub
End Try
Try
disposeText = extractMember(codeClass.Members.Item(“Dispose”))
Catch
MsgBox(“Error extracting Dispose! Will restore source file.”)
System.IO.File.WriteAllText(sourceFileName, origCode)
Exit Sub
End Try
Try
fieldDecls = extractWinFormsFields(codeClass, initComponentText)
Catch
MsgBox(“Error extracting field declares! Will restore source file.”)
System.IO.File.WriteAllText(sourceFileName, origCode)
Exit Sub
End Try
Dim finalCode As String
finalCode = IIf(namespaceName “”, “Namespace ” + namespaceName + vbCrLf + vbCrLf, “”) + _
” _” + vbCrLf + _
“Partial Class ” + codeClass.Name + vbCrLf + _
” Inherits ” + baseClassName + vbCrLf + _
vbCrLf + _
disposeText + vbCrLf + _
vbCrLf + _
fieldDecls(0) + vbCrLf + _
vbCrLf + _
initComponentText + vbCrLf + _
fieldDecls(1) + vbCrLf + _
“End Class” + vbCrLf + _
IIf(namespaceName “”, vbCrLf + “End Namespace” + vbCrLf, “”)
‘ Now write the new designer file
System.IO.File.WriteAllText(newDesignerFile, finalCode)
Dim newProjItem As ProjectItem = item.ProjectItems.AddFromFile(newDesignerFile)
Try
newProjItem.Open()
Catch
End Try
Try
DTE.ExecuteCommand(“Edit.FormatDocument”)
Catch
End Try
MsgBox(“Code separated successfully!” + vbCrLf + _
“You may have residual comments or code regions in the source ” + vbCrLf + _
“class that should be deleted or moved to the new Designer.vb class.”, _
MsgBoxStyle.Information, _
“Complete”)
End Sub
Function findClass(ByVal items As System.Collections.IEnumerable) As CodeClass
For Each codeEl As CodeElement In items
If codeEl.Kind = vsCMElement.vsCMElementClass Then
Return codeEl
ElseIf codeEl.Children.Count > 0 Then
Dim cls As CodeClass = findClass(codeEl.Children)
If cls IsNot Nothing Then
Return findClass(codeEl.Children)
End If
End If
Next
Return Nothing
End Function
Function extractWinFormsFields(ByVal codeClass As CodeClass, ByVal initComponentsCode As String) As String()
Dim member As CodeElement
Dim fieldsCode As String
Dim components As String
Dim initComponentsCodeForComp As String = initComponentsCode.ToLower
fieldsCode = “”
For i As Integer = codeClass.Members.Count To 1 Step -1
member = codeClass.Members.Item(i)
If member.Kind = vsCMElement.vsCMElementVariable Then
Dim field As CodeVariable = member
If field.Type.TypeKind vsCMTypeRef.vsCMTypeRefArray Then
If field.Name.ToLower = “components” Then
‘ We’ll insert this separately
components = extractMember(field)
ElseIf initComponentsCodeForComp Like (“* Me.” + field.Name + ” = New *”).ToLower Then
fieldsCode = extractMember(field) + vbCrLf + _
fieldsCode
End If
End If
End If
Next
Return New String() {components, fieldsCode}
End Function
Function extractMember(ByVal memberElement As CodeElement) As String
Dim memberStart As EditPoint = memberElement.GetStartPoint().CreateEditPoint()
Dim memberText As String = String.Empty
memberText += memberStart.GetText(memberElement.GetEndPoint())
memberStart.Delete(memberElement.GetEndPoint())
Return memberText
End Function
End Module
Hello, I used the code provided by the author of this article but my controls did not convert over. All of user controls are gone so I’m left with an empty form. Can anyone help with this?
I’ve only used this for a Form. I’d recommend digging into it to get it to work the way you want and then sharing in the comments here.
Pingback: Converting Visual Studio 2003 WinForms to Visual Studio 2005/2008/2010/2012 partial classes (via: Duncan Smart’s Weblog) « The Wiert Corner – irregular stream of stuff
Nathan,
Thanks for your Macro. Since Macros have been eliminated in Visual Studio 2012, I used your Macro and the subsequent adaptions posted here to create a Visual Studio Add-In. I wrote an article about the Add-In and submitted it to The Code Project. The article can be found here: http://www.codeproject.com/Articles/528990/A-Visual-Studio-add-in-to-move-Windows-Forms-Desig
Dear Nathan,
Thank you for the great article and code. I have used your code submitted on CodeProject. It works like a charm and saved days of effort for me. Great work.
Regards,
Nishant Kainth