Converting VB.NET 2003 WinForms to 2005/2008 Partial Classes

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.

9 thoughts on “Converting VB.NET 2003 WinForms to 2005/2008 Partial Classes

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

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

  3. 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: http://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

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

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

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

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">