Windows Developer Center > Windows Forms Forums > Windows Forms Designer > Loading VB.NET Code Into A Designer
Ask a questionAsk a question
 

AnswerLoading VB.NET Code Into A Designer

  • Sunday, June 10, 2007 11:11 PMAnonymous5400 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Does anybody know how to load VB.NET/C# code into a designer?  I've been trying to do so for weeks with no success.  I know that Visual Studio and SharpDevelop do it, but I can't seem to replicate the functionality in my own app.

    Thanks very much for all your help!

Answers

  • Sunday, June 17, 2007 6:02 AMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    Ok, sorry it took so long... been busy with other things.

     

    Here's a sample design form, designer loader, parser, and ITypeResolutionService. The parser uses SharpDevelop's parser, so you'll need a copy of ICSharpCode.NRefactory.dll. You can get it by downloading the package at http://www.sharpdevelop.net/OpenSource/SD/Download/GetFile.aspx?What=Setup&Release=Serralongue

     

    Here's a breakdown of it all.

     

    1. Form1 has an empty tabpage control and a single menu item that allows you to choose a source VB or C# file.

    2. Pick a file and a new design surface is created. The surface using DesginerLoader to load the file.

    3. DesignerLoader loads the file you selected, as well as any others associated with it (eg. Form1.vb and Form1.Designer.vb).

    4. The two (or more, see the comments) files are merged into a temporary file and parsed via SharpDevParser.

    5. The result is displayed in a new tab page.

     

    And that's about it. Here's the code:

     

    Form1.vb

     

    Code Snippet

    Imports System.IO

    Imports System.ComponentModel

    Imports System.ComponentModel.Design

    Imports Microsoft.VisualBasic

    Imports System.ComponentModel.Design.Serialization

    Public Class Form1

       Dim serviceContainer As ServiceContainer

       Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)

          serviceContainer = New ServiceContainer

          serviceContainer.AddService(GetType(ITypeResolutionService), New TypeResolutionService())

          MyBase.OnLoad(e)

       End Sub

       Private Sub OpenToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles

          OpenToolStripMenuItem.Click

          Dim formFile As String

          Dim openDialog As OpenFileDialog = New OpenFileDialog()

          With openDialog

             .AddExtension = True

             .CheckFileExists = True

             .CheckPathExists = True

             .DefaultExt = "vb"

             .FileName = ""

             .Filter = "VB Files|*.vb|C# Files|*.cs"

             .FilterIndex = 0

             .InitialDirectory = Application.StartupPath

             If .ShowDialog() = Windows.Forms.DialogResult.OK Then

                OpenForm(.FileName)

             End If

          End With

       End Sub

       Private Sub OpenForm(ByVal filePath As String)

          Dim ds As DesignSurface

          Dim loader As DesignerLoader

          Dim tabPage As TabPage

          Dim view As Control

     

          If File.Exists(filePath) Then

             tabPage = New TabPage(Path.GetFileName(filePath))

             ds = New DesignSurface(serviceContainer)

            

             loader = New DesignerLoader(filePath)

             ds.BeginLoad(loader)

     

             view = ds.View

             view.Dock = DockStyle.Fill

             tabPage.Controls.Add(view)

             tcDesigners.TabPages.Add(tabPage)

             tcDesigners.SelectedTab = tabPage

          Else

             MsgBox(filePath & " not found.", MsgBoxStyle.Exclamation, "Open Form")

          End If

       End Sub

    End Class

     

    Form1.Designer.vb:

    Code Snippet

    <Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _

    Partial Class Form1

       Inherits System.Windows.Forms.Form

       'Form overrides dispose to clean up the component list.

       <System.Diagnostics.DebuggerNonUserCode()> _

       Protected Overrides Sub Dispose(ByVal disposing As Boolean)

          Try

             If disposing AndAlso components IsNot Nothing Then

                components.Dispose()

             End If

          Finally

             MyBase.Dispose(disposing)

          End Try

       End Sub

     

       'Required by the Windows Form Designer

       Private components As System.ComponentModel.IContainer

     

       'NOTE: The following procedure is required by the Windows Form Designer

       'It can be modified using the Windows Form Designer.

       'Do not modify it using the code editor.

       <System.Diagnostics.DebuggerStepThrough()> _

       Private Sub InitializeComponent()

          Dim resources As System.ComponentModel.ComponentResourceManager = New       System.ComponentModel.ComponentResourceManager(GetType(Form1))

          Me.MenuStrip1 = New System.Windows.Forms.MenuStrip

          Me.FileToolStripMenuItem = New System.Windows.Forms.ToolStripMenuItem

          Me.OpenToolStripMenuItem = New System.Windows.Forms.ToolStripMenuItem

          Me.tcDesigners = New System.Windows.Forms.TabControl

          Me.MenuStrip1.SuspendLayout()

          Me.SuspendLayout()

          '

          'MenuStrip1

          '

          Me.MenuStrip1.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.FileToolStripMenuItem})

          Me.MenuStrip1.Location = New System.Drawing.Point(0, 0)

          Me.MenuStrip1.Name = "MenuStrip1"

          Me.MenuStrip1.RenderMode = System.Windows.Forms.ToolStripRenderMode.Professional

          Me.MenuStrip1.Size = New System.Drawing.Size(789, 24)

          Me.MenuStrip1.TabIndex = 0

          Me.MenuStrip1.Text = "MenuStrip1"

          '

          'FileToolStripMenuItem

          '

          Me.FileToolStripMenuItem.DropDownItems.AddRange(New System.Windows.Forms.ToolStripItem()       {Me.OpenToolStripMenuItem})

          Me.FileToolStripMenuItem.Name = "FileToolStripMenuItem"

          Me.FileToolStripMenuItem.Size = New System.Drawing.Size(35, 20)

          Me.FileToolStripMenuItem.Text = "&File"

          '

          'OpenToolStripMenuItem

          '

          Me.OpenToolStripMenuItem.Image = CType(resources.GetObject("OpenToolStripMenuItem.Image"), System.Drawing.Image)

          Me.OpenToolStripMenuItem.ImageTransparentColor = System.Drawing.Color.Magenta

          Me.OpenToolStripMenuItem.Name = "OpenToolStripMenuItem"

          Me.OpenToolStripMenuItem.ShortcutKeys = CType((System.Windows.Forms.Keys.Control Or System.Windows.Forms.Keys.O), System.Windows.Forms.Keys)

          Me.OpenToolStripMenuItem.Size = New System.Drawing.Size(152, 22)

          Me.OpenToolStripMenuItem.Text = "&Open"

          '

          'tcDesigners

          '

          Me.tcDesigners.Dock = System.Windows.Forms.DockStyle.Fill

          Me.tcDesigners.Location = New System.Drawing.Point(0, 24)

          Me.tcDesigners.Name = "tcDesigners"

          Me.tcDesigners.SelectedIndex = 0

          Me.tcDesigners.Size = New System.Drawing.Size(789, 473)

          Me.tcDesigners.TabIndex = 1

          '

          'Form1

          '

          Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)

          Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font

          Me.ClientSize = New System.Drawing.Size(789, 497)

          Me.Controls.Add(Me.tcDesigners)

          Me.Controls.Add(Me.MenuStrip1)

          Me.MainMenuStrip = Me.MenuStrip1

          Me.Name = "Form1"

          Me.Text = "Form1"

          Me.MenuStrip1.ResumeLayout(False)

          Me.MenuStrip1.PerformLayout()

          Me.ResumeLayout(False)

          Me.PerformLayout()

       End Sub

       Friend WithEvents MenuStrip1 As System.Windows.Forms.MenuStrip

       Friend WithEvents FileToolStripMenuItem As System.Windows.Forms.ToolStripMenuItem

       Friend WithEvents OpenToolStripMenuItem As System.Windows.Forms.ToolStripMenuItem

       Friend WithEvents tcDesigners As System.Windows.Forms.TabControl

    End Class

     

    DesignerLoader.vb

    Code Snippet

    Imports ICSharpCode.NRefactory

    Imports system.CodeDom

    Imports System.CodeDom.Compiler

    Imports System.ComponentModel.Design

    Imports System.ComponentModel.Design.Serialization

    Imports System.IO

    Imports System.Text

    Public Class DesignerLoader

       Inherits CodeDomDesignerLoader

       ' Enum of supported language file extensions. These entries match corresponding SharpDevelop SupportedLanguage

       ' values.

       Private Enum SupportedLanguages

          CS

          VB

       End Enum

     

       ' Imports / using statements for supported languages.

       Dim importTokens As String() = {"using", "Imports"}

     

       ' Statement termination characters for supported languages.

       Dim statementTerminators As String() = {";", ""}

     

       Dim sourceFile As String

       Dim provider As CodeDomProvider

       Dim errors As String

     

    #Region " Construction / Initialization "

       ''' <summary>

       ''' Duh

       ''' </summary>

       ''' <param name="fileName">A code file to open.</param>

       ''' <remarks>If the file has matching associates, they will be loaded as well. See LoadReaders()

       ''' for details.</remarks>

       Public Sub New(ByVal fileName As String)

          Init(fileName)

       End Sub

     

       ''' <summary>

       ''' Saves the requested source files and gets a CodeDomProvider appropriate for the file type.

       ''' </summary>

       ''' <param name="fileName">Name of the file to load.</param>

       ''' <remarks></remarks>

       Private Sub Init(ByVal fileName As String)

          sourceFile = fileName

          provider = GetProvider(fileName)

       End Sub

    #End Region

    #Region " CodeDomDesignerLoader implementation / overrides "

       ''' <summary>

       ''' Returns a reference to our CodeDomProvider, which is specific to the type of file passed in

       ''' at initialization.

       ''' </summary>

       ''' <value></value>

       ''' <returns></returns>

       ''' <remarks></remarks>

       Protected Overrides ReadOnly Property CodeDomProvider() As System.CodeDom.Compiler.CodeDomProvider

          Get

             Return provider

          End Get

       End Property

       ''' <summary>

       ''' Parses the specified source file (and associated designer / partial files) with the help of SharpDevelop.

       ''' </summary>

       ''' <returns>A CodeCompileUnit representing the class(es) included in the source file.</returns>

       ''' <remarks></remarks>

       Protected Overrides Function Parse() As System.CodeDom.CodeCompileUnit

          Dim parser As SharpDevParser

          Dim mergeFileName As String = ""

          Dim reader As TextReader = Nothing

          Dim ccu As CodeCompileUnit = Nothing

          Dim readers As List(Of TextReader) = LoadReaders(sourceFile)

     

          If readers.Count > 0 Then

             mergeFileName = MergeFiles(readers)

             If mergeFileName.Length > 0 Then

                reader = File.OpenText(mergeFileName)

             End If

          End If

     

          If reader IsNot Nothing Then

                Try

                   parser = New SharpDevParser()

                   ccu = parser.Parse(GetLanguageIndex(provider), reader, GetService(GetType(ITypeResolutionService)))

                Catch ex As Exception

                Finally

                   reader.Close()

                   Try

                      File.Delete(mergeFileName)

                   Catch ex As Exception

                   End Try

                End Try

          End If

     

          CloseReaders(readers)

          Return ccu

       End Function

     

       ''' <summary>

       ''' Returns an instance of a Type Resultion Service. We don't provide one ourselves... just return whatever the

       ''' LoaderHost gives us.

       ''' </summary>

       ''' <value>Whatever the LoaderHost gives us via GetService().</value>

       ''' <returns>Uhhh, I just said that... Whatever the LoaderHost gives us via GetService().</returns>

       ''' <remarks></remarks>

       Protected Overrides ReadOnly Property TypeResolutionService() As    System.ComponentModel.Design.ITypeResolutionService

          Get

             Return LoaderHost.GetService(GetType(TypeResolutionService))

          End Get

       End Property

     

       ''' <summary>

       ''' Not supported in this implementation.

       ''' </summary>

       ''' <param name="unit">CodeCompileUnit to write to file.</param>

       ''' <remarks></remarks>

       Protected Overrides Sub Write(ByVal unit As System.CodeDom.CodeCompileUnit)

          '

       End Sub

    #End Region

    #Region " Nitty gritty "

       ''' <summary>

       ''' Attempts to load all partial class definitions given a source code file.

       ''' </summary>

       ''' <param name="sourceFile">One of the source files in a set of related files</param>

       ''' <returns>A list of TextReaders, each loaded with a file assumed to belong to the same

       ''' class definition. This list will be empty (count = 0) if an error occurs or the specified

       ''' file is not found.</returns>

       ''' <remarks>Partial classes are supported but will only be located if all class files reside

       ''' in the same folder and follow the same naming pattern as the file specified. The naming pattern

       ''' looked for is {some name}{optional ".*"}.{language extension}

       '''

       ''' Eg. If Form1.Designer.vb is specified, then its code-behind file Form1.vb would also be loaded,

       ''' as would a file named Form1.partial2.vb, etc.

       ''' </remarks>

       Private Function LoadReaders(ByVal sourceFile As String) As List(Of TextReader)

          Dim basePath As String = Path.GetDirectoryName(sourceFile)

          Dim fileExtension As String = Path.GetExtension(sourceFile)

          Dim baseName As String = GetBaseFileName(sourceFile)

          Dim files() As String = Directory.GetFiles(basePath, baseName & "*" & fileExtension)

          Dim validFiles As List(Of String) = New List(Of String)

          Dim readerList As List(Of TextReader) = New List(Of TextReader)

          Dim regEx As RegularExpressions.Regex = New RegularExpressions.Regex("(" & baseName & ")(\..+)*(" & fileExtension & ")")

     

          Try

             For Each file As String In files

                If regEx.IsMatch(file) Then

                   validFiles.Add(file)

                End If

             Next

             For Each name As String In validFiles

                readerList.Add(File.OpenText(name))

             Next

          Catch ex As Exception

             Trace.WriteLine("Exception opening source files - " & ex.Message)

             CloseReaders(readerList)

             readerList.Clear()

          End Try

          Return readerList

       End Function

     

       ''' <summary>

       ''' Gets the first part of a source code file name (ie. everything before the first ".").

       ''' </summary>

       ''' <param name="fullPath">Full path to the file to evaluate.</param>

       ''' <returns>Base file name.</returns>

       ''' <remarks>Give a file named "Form1.Designer.vb", this function would return "Form1".</remarks>

       Private Function GetBaseFileName(ByVal fullPath As String) As String

          Dim baseName As String = Path.GetFileNameWithoutExtension(fullPath)

          Dim theDot As Integer = baseName.IndexOf(".")

     

          If theDot > 0 Then

             baseName = baseName.Substring(0, theDot)

          End If

          Return baseName

       End Function

     

       ''' <summary>

       ''' Merges related code files into a temporary file.

       ''' </summary>

       ''' <param name="fileReaders">List of open TextReaders, each loaded with a related code file.</param>

       ''' <returns>Path to the merged file.</returns>

       ''' <remarks>The resulting file will have all Import / using statements from all files moved to the top.

       ''' Parsers like it that way.

       ''' </remarks>

       Private Function MergeFiles(ByVal fileReaders As List(Of TextReader)) As String

          Dim sourceCode As String = ""

          Dim importList As List(Of String)

          Dim importToken As String

          Dim statementTerminator As String

          Dim mergeFile As String = ""

          Dim language As Integer = GetLanguageIndex(provider)

     

          If language < 0 Then Return ""

     

          Try

             importList = New List(Of String)

             importToken = importTokens(language)

             statementTerminator = statementTerminators(language)

     

             For Each reader As TextReader In fileReaders

                sourceCode += PullCodeExtractImports(reader, importToken, statementTerminator, importList)

             Next

     

             importList.Sort()

             For index As Integer = 0 To importList.Count - 1

                importList(index) = importToken & " " & importList(index)

             Next

     

             sourceCode = String.Join(statementTerminator & vbCrLf, importList.ToArray) & vbCrLf & sourceCode

             If sourceCode.Length > 0 Then

                mergeFile = Path.GetTempFileName()

                File.AppendAllText(mergeFile, sourceCode)

             End If

     

          Catch ex As Exception

             Trace.WriteLine(ex.ToString)

             If mergeFile.Length > 0 Then

                Try

                   File.Delete(mergeFile)

                Catch ex2 As Exception

                End Try

             End If

             mergeFile = ""

          End Try

          Return mergeFile

       End Function

     

       ''' <summary>

       ''' Pulls the code from a text file, excluding Import / using statements.

       ''' </summary>

       ''' <param name="reader">Reader opened with the source file to read.</param>

       ''' <param name="importToken">Language-specific token for the Imports / using statement.</param>

       ''' <param name="statementTerminator">Language-specific code statement terminator. ";" in C#, empty string in VB.</param>

       ''' <param name="importList">Reference to a list of import statements. Imports found in the current file will be added

       ''' to this list.</param>

       ''' <returns>The resulting code, as a string, minus any import statements.</returns>

       ''' <remarks></remarks>

       Private Function PullCodeExtractImports(ByVal reader As TextReader, ByVal importToken As String, ByVal    statementTerminator As String, ByRef importList As List(Of String)) As String

          Dim line As String = Nothing

          Dim resultString As String = ""

          Dim strippedLine As String

     

          line = reader.ReadLine

          Do Until line Is Nothing

             strippedLine = line.Trim.TrimEnd(New Char() {statementTerminator})

     

             If strippedLine.ToLower.StartsWith(importToken.ToLower) AndAlso _

                importList.IndexOf(strippedLine.Substring(importToken.Length + 1)) < 0 Then

                importList.Add(strippedLine.Substring(importToken.Length + 1))

             Else

                resultString += line & vbCrLf

             End If

     

             line = reader.ReadLine

          Loop

          Return resultString

       End Function

     

       ''' <summary>

       ''' Returns a SupportedLanguages value given a language-specific CodeDomProvider.

       ''' </summary>

       ''' <param name="provider">The CodeDomProvider for the language to query.</param>

       ''' <returns>One of the SupportedLanguages values, -1 if CodeDomProvider is not supported by this

       ''' class</returns>

       ''' <remarks></remarks>

       Private Function GetLanguageIndex(ByVal provider As CodeDomProvider) As Integer

          Try

             Return CInt(System.Enum.Parse(GetType(SupportedLanguages), provider.FileExtension, True))

          Catch ex As Exception

             Return -1

          End Try

       End Function

     

       ''' <summary>

       ''' Closes a bunch of text readers.

       ''' </summary>

       ''' <param name="readers">List of open TextReaders to close.</param>

       ''' <remarks></remarks>

       Private Sub CloseReaders(ByVal readers As List(Of TextReader))

          For Each reader As TextReader In readers

             Try

                reader.Close()

             Catch ex As Exception

             End Try

          Next

       End Sub

     

       ''' <summary>

       ''' Returns a CodeDomProvider appropriate for the file specified, based on its file extension.

       ''' </summary>

       ''' <param name="fileName">Code file whose provider is to be loaded.</param>

       ''' <returns>A CodeDomProvider appropriate for the file specified if successful,

       ''' Nothing otherwise.</returns>

       ''' <remarks></remarks>

       Private Function GetProvider(ByVal fileName As String) As CodeDomProvider

          Try

             Dim language As String = CodeDomProvider.GetLanguageFromExtension(Path.GetExtension(fileName))

             Return CodeDomProvider.CreateProvider(language)

          Catch ex As Exception

             Trace.WriteLine(ex.ToString)

             Return Nothing

          End Try

       End Function

    #End Region

    End Class

     

    SharpDevParser.vb

    Code Snippet

    Imports system.CodeDom

    Imports System.CodeDom.Compiler

    Imports System.ComponentModel

    Imports System.ComponentModel.Design

    Imports System.ComponentModel.Design.Serialization

    Imports System.Windows.Forms.Design

    Imports System.IO

    Imports ICSharpCode.NRefactory

    Public Class SharpDevParser

       Dim _errorOutput As String = ""

       Dim _errorCount As Integer = 0

     

       ''' <summary>

       ''' Uses the SharpDevelop parser to parse the source file.

       ''' </summary>

       ''' <returns>A CodeCompileUnit representing the source file, Nothing if parsing fails.</returns>

       ''' <remarks>If the parse fails, ErrorOutput will contain the parser's ErrorOutput string and

       ''' ErrorCount will indicate the number of errors encountered.</remarks>

       Public Function Parse(ByVal language As SupportedLanguage, ByVal sourceReader As TextReader, ByVal typeResolutionService As ITypeResolutionService) As CodeCompileUnit

     

          Dim parser As ICSharpCode.NRefactory.IParser = ParserFactory.CreateParser(language, sourceReader)

          Dim visitor As Visitors.CodeDomVisitor

     

          ResetErrors()

     

          parser.ParseMethodBodies = True

          parser.Parse()

     

          _errorCount = parser.Errors.Count

          _errorOutput = parser.Errors.ErrorOutput

     

          If ErrorCount = 0 Then

             visitor = New Visitors.CodeDomVisitor()

             visitor.EnvironmentInformationProvider = New EnvironmentInformationProvider(typeResolutionService)

             visitor.VisitCompilationUnit(parser.CompilationUnit, Nothing)

             Return visitor.codeCompileUnit

          Else

             Return Nothing

          End If

       End Function

     

       ''' <summary>

       ''' Resets ErrorOutput and ErrorCount to default values.

       ''' </summary>

       ''' <remarks></remarks>

       Private Sub ResetErrors()

          _errorCount = 0

          _errorOutput = ""

       End Sub

     

       ''' <summary>

       ''' Returns a string representing errors encountered during a parse.

       ''' </summary>

       ''' <value></value>

       ''' <returns></returns>

       ''' <remarks></remarks>

       Public ReadOnly Property ErrorOutput() As String

          Get

             Return _errorOutput

          End Get

       End Property

     

       ''' <summary>

       ''' Returns the number of errors encountered during a parse.

       ''' </summary>

       ''' <value></value>

       ''' <returns></returns>

       ''' <remarks></remarks>

       Public ReadOnly Property ErrorCount() As Integer

          Get

             Return _errorCount

          End Get

       End Property

    End Class

     

    ''' <summary>

    ''' Implementation of IEnvironmentInformationProvider.

    ''' </summary>

    ''' <remarks>IEnvironmentInformationProvider is used by a Visitor to determine whether a Type contains

    ''' a field with the specific name. This is necessary because the parser can't tell, from reading the code,

    ''' whether a member is a field or a property. An Enum's values, for instance, cannot be differentiated

    ''' from a class type's properties.

    '''

    ''' Failure to make this determination can result in failure to load the member. In the absence of an

    ''' IEnvironmentInformationProvider the parser will assume a property/</remarks>

    Friend Class EnvironmentInformationProvider

       Implements IEnvironmentInformationProvider

       Dim resolutionService As ITypeResolutionService

     

       ''' <summary>

       ''' Constructor.

       ''' </summary>

       ''' <param name="typeResolutionService">A Type Resolution Service instance. This is used to resolve the type

       ''' requested.</param>

       ''' <remarks></remarks>

       Public Sub New(ByVal typeResolutionService As ITypeResolutionService)

          resolutionService = typeResolutionService

       End Sub

     

       ''' <summary>

       ''' Determines if a type has a field with a given name.

       ''' </summary>

       ''' <param name="fullTypeName">Name of the type to query.</param>

       ''' <param name="fieldName">Name of the field to look for.</param>

       ''' <returns>True if the type has a field by the given name, False if an error occurs or the type

       ''' doesn't contain a corresponding field.</returns>

       ''' <remarks></remarks>

       Public Function HasField(ByVal fullTypeName As String, ByVal fieldName As String) As Boolean Implements    ICSharpCode.NRefactory.IEnvironmentInformationProvider.HasField

          Dim theType As Type = Type.GetType(fullTypeName)

     

          If theType Is Nothing AndAlso resolutionService IsNot Nothing Then

             theType = resolutionService.GetType(fullTypeName)

          End If

          If theType Is Nothing Then

             Return False

          Else

             Return Not theType.GetField(fieldName) Is Nothing

          End If

       End Function

    End Class

     

    And finally, you need a decent ITypeResolutionService. This is a modified version of SharpDevelop's.

    TypeResolutionService.vb

    Code Snippet

    Imports System

    Imports System.ComponentModel.Design

    Imports System.Reflection

    Imports System.IO

    Imports Microsoft.Win32

    ''' <summary>

    ''' Modified port of SharpDev's TypeResolutionService. Resolves types during a designer load.

    ''' </summary>

    ''' <remarks></remarks>

    Public Class TypeResolutionService

       Implements ITypeResolutionService

       <System.Runtime.InteropServices.DllImport("kernel32.dll")> _

       Private Shared Function MoveFileEx(ByVal lpExistingFileName As String, ByVal lpNewFileName As String, ByVal dwFlags As Integer) As Boolean

       End Function

     

       Shared ReadOnly _designerAssemblies As List(Of Assembly) = New List(Of Assembly)

       Shared ReadOnly assemblyDict As Dictionary(Of String, Assembly) = New Dictionary(Of String, Assembly)

       '''<summary>

       ''' List of assemblies used by the form designer. This static list is not an optimal solution,

       ''' but better than using AppDomain.CurrentDomain.GetAssemblies(). See SD2-630.

       ''' </summary>

       Public Shared ReadOnly Property DesignerAssemblies() As List(Of Assembly)

          Get

             Return _designerAssemblies

          End Get

       End Property

       Shared Sub New()

          ClearMixedAssembliesTemporaryFiles()

          ' Preload commonly referenced assemblies.

          DesignerAssemblies.Add(GetType(Object).Assembly) ' MSCorlib

          DesignerAssemblies.Add(GetType(Uri).Assembly) ' System

          DesignerAssemblies.Add(GetType(System.Drawing.Point).Assembly) ' System.Drawing

          DesignerAssemblies.Add(GetType(System.Windows.Forms.AutoScaleMode).Assembly) ' System.Windows.Forms

          DesignerAssemblies.Add(GetType(System.Windows.Forms.Design.AnchorEditor).Assembly) ' System.Windows.Forms.Design

          RegisterVSDesignerWorkaround()

          AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf AssemblyResolveEventHandler

       End Sub

     

       Const MOVEFILE_DELAY_UNTIL_REBOOT As Integer = &H4

       Shared Sub MarkFileToDeleteOnReboot(ByVal fileName As String)

          MoveFileEx(fileName, Nothing, MOVEFILE_DELAY_UNTIL_REBOOT)

       End Sub

       Shared Sub ClearMixedAssembliesTemporaryFiles()

          Dim files As String() = Directory.GetFiles(Path.GetTempPath(), "*.sd_forms_designer_mixed_assembly.dll")

          For Each filename As String In files

             Try

                File.Delete(filename)

             Catch

             End Try

          Next

       End Sub

       Dim formSourceFileName As String

       Public Sub New()

       End Sub

       Public Sub New(ByVal formSourceFileName As String)

          Me.formSourceFileName = formSourceFileName

       End Sub

       Shared Function GetHash(ByVal fileName As String) As String

          Return Path.GetFileName(fileName).ToLowerInvariant() + File.GetLastWriteTimeUtc(fileName).Ticks.ToString()

       End Function

       ''' <summary>

       ''' Loads the file in none-locking mode. Returns null on failure.

       '''</summary>

       Public Shared Function LoadAssembly(ByVal fileName As String) As Assembly

          If Not File.Exists(fileName) Then

             Return Nothing

          End If

          ' FIX for SD2-716, remove when designer gets its own AppDomain

          For Each asm As Assembly In AppDomain.CurrentDomain.GetAssemblies()

             Try

                If String.Equals(asm.Location, fileName, StringComparison.InvariantCultureIgnoreCase) Then

                   RegisterAssembly(asm)

                   Return asm

                End If

             Catch e As NotSupportedException

                ' Fixes forum-12823

             End Try

          Next

          Dim hash As String = GetHash(fileName)

          SyncLock assemblyDict

             Dim asm As Assembly

             If assemblyDict.TryGetValue(hash, asm) Then

                Return asm

             End If

             Try

                asm = Assembly.Load(File.ReadAllBytes(fileName))

             Catch e As BadImageFormatException

                If e.Message.Contains("HRESULT: 0x8013141D") Then

                   Dim tempPath As String = Path.GetTempFileName()

                   File.Delete(tempPath)

                   tempPath += ".sd_forms_designer_netmodule_assembly.dll"

                   Try

                      'convert netmodule to assembly

                      Dim p As Process = New Process()

                      p.StartInfo.UseShellExecute = False

                      p.StartInfo.FileName = Path.GetDirectoryName(GetType(Object).Module.FullyQualifiedName) +    Path.DirectorySeparatorChar + "al.exe"

                      p.StartInfo.Arguments = """" & fileName & " /out:""" + tempPath + """"

                      p.StartInfo.CreateNoWindow = True

                      p.Start()

                      p.WaitForExit()

                      If p.ExitCode = 0 And File.Exists(tempPath) Then

                         Dim asm_data As Byte() = File.ReadAllBytes(tempPath)

                         asm = Assembly.Load(asm_data)

                         asm.LoadModule(Path.GetFileName(fileName), File.ReadAllBytes(fileName))

                      End If

                   Catch ex As Exception

                      MsgBox(ex.ToString, MsgBoxStyle.Critical, "Error calling linker for netmodule")

                   End Try

                   Try

                      File.Delete(tempPath)

                   Catch

                   End Try

                Else

                   Throw ' don't ignore other load errors

                End If

             Catch e As FileLoadException

                If e.Message.Contains("HRESULT: 0x80131402") Then

                   'this is C++/CLI Mixed assembly which can only be loaded from disk, not in-memory

                   Dim tempPath As String = Path.GetTempFileName()

                   File.Delete(tempPath)

                   tempPath += ".sd_forms_designer_mixed_assembly.dll"

                   File.Copy(fileName, tempPath)

                   asm = Assembly.LoadFile(tempPath)

                   MarkFileToDeleteOnReboot(tempPath)

                Else

                   Throw ' don't ignore other load errors

                End If

             End Try

             SyncLock DesignerAssemblies

                If Not DesignerAssemblies.Contains(asm) Then

                   DesignerAssemblies.Insert(0, asm)

                End If

             End SyncLock

             assemblyDict(hash) = asm

             Return asm

          End SyncLock

       End Function

       Public Shared Sub RegisterAssembly(ByVal asm As Assembly)

          Dim file As String = asm.Location

          If file.Length > 0 Then

             SyncLock assemblyDict

                assemblyDict(GetHash(file)) = asm

             End SyncLock

          End If

          SyncLock DesignerAssemblies

             If Not DesignerAssemblies.Contains(asm) Then

                DesignerAssemblies.Insert(0, asm)

             End If

          End SyncLock

       End Sub

       Public Function GetAssembly(ByVal name As AssemblyName) As Assembly Implements    System.ComponentModel.Design.ITypeResolutionService.GetAssembly

          Return LoadAssembly(name, False)

       End Function

       Public Function GetAssembly(ByVal name As AssemblyName, ByVal throwOnError As Boolean) As Assembly Implements System.ComponentModel.Design.ITypeResolutionService.GetAssembly

          Return LoadAssembly(name, throwOnError)

       End Function

       Shared Function LoadAssembly(ByVal name As AssemblyName, ByVal throwOnError As Boolean) As Assembly

          Try

             Dim asm As Assembly = Assembly.Load(name)

             RegisterAssembly(asm)

             Return asm

          Catch e As System.IO.FileLoadException

             If throwOnError Then

                Throw

             End If

             Return Nothing

          End Try

       End Function

       Public Function GetPathOfAssembly(ByVal name As AssemblyName) As String Implements System.ComponentModel.Design.ITypeResolutionService.GetPathOfAssembly

          Dim Assembly As Assembly = GetAssembly(name)

          If Assembly IsNot Nothing Then

             Return Assembly.Location

          End If

          Return Nothing

       End Function

       Public Overloads Function [GetType](ByVal name As String) As Type Implements    System.ComponentModel.Design.ITypeResolutionService.GetType

          Return [GetType](name, False, False)

       End Function

       Public Overloads Function [GetType](ByVal name As String, ByVal throwOnError As Boolean) As Type Implements System.ComponentModel.Design.ITypeResolutionService.GetType

          Return [GetType](name, throwOnError, False)

       End Function

       Public Overloads Function [GetType](ByVal name As String, ByVal throwOnError As Boolean, ByVal ignoreCase As Boolean) As Type Implements System.ComponentModel.Design.ITypeResolutionService.GetType

          If name Is Nothing OrElse name.Length = 0 Then

             Return Nothing

          End If

          If IgnoreType(name) Then

             Return Nothing

          End If

          Try

             Dim type As Type = System.Type.GetType(name, False, ignoreCase)

             ' type lookup for typename, assembly, xyz style lookups

             If type Is Nothing Then

                Dim idx As Integer = name.IndexOf(",")

                If idx > 0 Then

                   Dim splitName As String() = name.Split(",")

                   Dim typeName As String = splitName(0)

                   Dim assemblyName As String = splitName(1).Substring(1)

                   Dim assembly As Assembly = Nothing

                   Try

                      assembly = assembly.Load(assemblyName)

                   Catch e As Exception

                   End Try

                   If assembly IsNot Nothing Then

                      SyncLock DesignerAssemblies

                         If Not DesignerAssemblies.Contains(assembly) Then

                            DesignerAssemblies.Add(assembly)

                         End If

                      End SyncLock

                      type = assembly.GetType(typeName, False, ignoreCase)

                   Else

                      type = type.GetType(typeName, False, ignoreCase)

                   End If

                End If

             End If

             If type Is Nothing Then

                SyncLock DesignerAssemblies

                   For Each asm As Assembly In DesignerAssemblies

                      Dim t As Type = asm.GetType(name, False)

                      If t IsNot Nothing Then

                         Return t

                      End If

                   Next

                End SyncLock

             End If

             If type Is Nothing Then

                type = SearchLoadedAssemblies(name, ignoreCase)

             End If

             If throwOnError And type Is Nothing Then

                Throw New TypeLoadException(name + " not found by TypeResolutionService")

             End If

             Return type

          Catch e As Exception

          End Try

          Return Nothing

       End Function

       Public Sub ReferenceAssembly(ByVal name As AssemblyName) Implements System.ComponentModel.Design.ITypeResolutionService.ReferenceAssembly

          Trace.WriteLine(name)

       End Sub

    #Region " VSDesigner workarounds "

       '''<summary>

       ''' HACK - Ignore any requests for types from the Microsoft.VSDesigner

       ''' assembly. There are smart tag problems if data adapter

       ''' designers are used from this assembly.

       ''' </summary>

       Function IgnoreType(ByVal name As String) As Boolean

          Dim idx As Integer = name.IndexOf(",")

          If idx > 0 Then

             Dim splitName As String() = name.Split(",")

             Dim assemblyName As String = splitName(1).Substring(1)

             If assemblyName = "Microsoft.VSDesigner" Then

                Return True

             End If

          End If

          Return False

       End Function

       Shared vsDesignerIdeDir As String

       Shared Sub RegisterVSDesignerWorkaround()

          If vsDesignerIdeDir Is Nothing Then

             vsDesignerIdeDir = ""

             Dim key As RegistryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\Microsoft\VisualStudio\8.0\Setup\VS")

             If key IsNot Nothing Then

                vsDesignerIdeDir = key.GetValue("VS7CommonDir").ToString

                If vsDesignerIdeDir.Length > 0 Then

                   vsDesignerIdeDir = Path.Combine(vsDesignerIdeDir, "IDE")

                   AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf DomainResolveHandler

                End If

             End If

          End If

       End Sub

       Shared Function DomainResolveHandler(ByVal sender As Object, ByVal args As ResolveEventArgs) As Assembly

          Dim shortName As String = args.Name

          If shortName.IndexOf(",") >= 0 Then

             shortName = shortName.Substring(0, shortName.IndexOf(","))

          End If

          If shortName.StartsWith("Microsoft.") And File.Exists(Path.Combine(vsDesignerIdeDir, shortName & ".dll")) Then

             Return Assembly.LoadFrom(Path.Combine(vsDesignerIdeDir, shortName + ".dll"))

          End If

          Return Nothing

       End Function

    #End Region

       Shared Function SearchLoadedAssemblies(ByVal name As String, ByVal ignoreCase As Boolean) As Type

          Dim lastAssembly As Assembly = Nothing

          Dim foundType As Type = Nothing

          For Each asm As Assembly In AppDomain.CurrentDomain.GetAssemblies

             foundType = asm.GetType(name, False, ignoreCase)

             If foundType IsNot Nothing Then

                lastAssembly = asm

                Exit For

             End If

          Next

          If lastAssembly IsNot Nothing Then

             TypeResolutionService.DesignerAssemblies.Add(lastAssembly)

          End If

          Return foundType

       End Function

       Shared Function AssemblyResolveEventHandler(ByVal sender As Object, ByVal args As ResolveEventArgs) As Assembly

          Dim lastAssembly As Assembly = Nothing

          For Each asm As Assembly In TypeResolutionService.DesignerAssemblies

             If asm.FullName = args.Name Then

                Return asm

             End If

          Next

          For Each asm As Assembly In AppDomain.CurrentDomain.GetAssemblies

             If asm.FullName = args.Name Then

                lastAssembly = asm

             End If

          Next

          If lastAssembly IsNot Nothing Then

             TypeResolutionService.DesignerAssemblies.Add(lastAssembly)

          End If

       Return lastAssembly

       End Function

    End Class

     

All Replies

  • Monday, June 11, 2007 12:12 AMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    You mean like switch to code view in a custom designer?
  • Monday, June 11, 2007 4:54 PMAnonymous5400 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Kind of.  I already have an application with a working designer, toolbox, and property window.  I also have a "View Code" button, which loads the code into a textbox, just like it should.  However, I want an "Open" menu item that, if a form is opened, can load it into designer view, not just load text into a textbox.  If that does/n't make sense, let me know Smile.  Thanks!
  • Monday, June 11, 2007 5:23 PMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Yeah I think I know what you mean. You could create a RichTextBox control attached to its own root designer and load a new surface with that control as the root. Kinda like this guy does with his CodeDomDesignerLoader.

     

    I think that's what you mean?

     

    Marc

  • Tuesday, June 12, 2007 9:30 PMAnonymous5400 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Actually, the CodeDomDesignerLoader in that sample is what I'm trying to base my code off of!  Could you elaborate on what you were saying about the RichTextBox?  Fully explained, what I want is this:
    1. User clicks File menu, then Open.
    2. User selects VB.NET code file (for example, Form1.vb)
    3. Program creates new form designer, and makes the form in the design view have Form1.vb's contents displayed as a form with buttons, listboxes, etc., NOT as text.

    Thank you again for helping!
  • Wednesday, June 13, 2007 12:07 AMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Ohhhhhhh you wanna deserialize a form! Ok gotcha. Can't write a sample right this minute but gimme a couple hours.

    Marc.

  • Wednesday, June 13, 2007 3:41 AMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Welll allllllrighty then. Not many freebies for parsing a source file apparently. This is gonna take a while..
  • Wednesday, June 13, 2007 3:46 AMAnonymous5400 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    That's exactly what I've found Smile.  I don't care - if you can find something with a trial version that works, I'll buy the full product!  I'm not as worried about the cost of this project as I am about getting it working properly Smile.

    Hope you find something you can work with - you seem really good in this department!
  • Wednesday, June 13, 2007 6:37 AMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Oh by freebies I mean built-in functionality Smile The VBCodeProvider (and CSharpCodeProvider) handle code generation and compilation but don't seem to do parsing. There are internal classes used by VS that do parsing but they're a little confusing (and, well, internal.)

     

    In the end you have to reproduce what the CodeGen.GetCodeCompileUnit() method does in that sample host app. Difference is that you want to read the file and determine what type of code element each statement represents (class declaration, import, namespace, etc.)  instead of building them manually. Once that's done you can get a serializer to deserialize the statements.

     

    It's late and my thinking's a little fuzzy... I know I've seen classes in the framework that help do this somewhere. I'll look into it further tomorrow.

    Marc.

     

  • Thursday, June 14, 2007 3:18 AMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Oh.

    My.

    God.

     

    This hurts.

     

    Microsoft, would it have killed ya to provide a parser implementation with your language providers? 

     

    I've narrowed it down to 4 approaches: 

    1. Compiling the code via CodeDomProvider.CompileAssemblyFromSource, then iterating through the resulting types to find a class compatible with a root component;
    2. Implementing something of a lexxer to read through the file and figure out what type of statement each line represents;
    3. Using SharpDevelop's parser / lexxer implementations, which are available under the GPL but may not be compatible with your implementation; and
    4. Providing a source file with your application, forcing the users to select only that file, and hard-coding the form instance

     

    Since I'm guessing option 4 is out, here's the rundown on the others.

     

    1. Using this option, you have to make sure the source file has the appropriate imports statements. Something we take for granted, like the statement MsgBox("I need a parser!"), relies on having Microsoft.VisualBasic or Microsoft.CSharp (depending on the language) listed as imports. There are others as well. These are implied when you compile via the IDE but not when using the language provider's compiler.

      Another issue is the fact that designable components are often paired with the ol' classname.designer.extension file which has the designer generated partial class.

      You can code around these issues to address most cases but you'll no doubt run into some exceptions. I'm working on a sample using this method.

    2. This would allow you to build a CodeStatementCollection.. uhh... collection, and instantiate the class using CodeDomSerializer. It's obviously more complicated but I think this is the "proper" way to do it. I might work on this one at some point.
    3. Self explanatory. NRefactory seems to be parsing workhorse (ICSharpCode.NRefactory.dll to be precise.)

    Actually there is another one - this guy wrote a C# parser back in '02. You could play with that and see about upgrading it to .Net 2.0, as well as including VB support. I'm thinking I'll be doing this in the near future.

     

    Anyway, that's what I've discovered. I'll post a sample of option 1 in the next day or so (getting late again and my brain hurts.)

    Marc

  • Thursday, June 14, 2007 4:08 AMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Oh this is interesting - http://www.panopticoncentral.net/ has a VB parser if your source files are in VB. The source project is at http://www.panopticoncentral.net/Files/VBParser%20Source%20(8.0.0.60327).zip
  • Thursday, June 14, 2007 6:06 PMAnonymous5400 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    I found a parser that might work... http://www.programmar.com .  It already has pre-made VB and C# grammars (I think you'd need to add support for partial classes) and an API.  The API looks messed up Smile, but maybe you could figure it out Smile.

    Thanks for all the great work you're doing!  I didn't even know some of this stuff existed in .NET before Smile.
  • Thursday, June 14, 2007 7:45 PMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Ok cool.

     

    I've pretty much completed my li'l loader but it really isn't the right way to go. I can load forms and components but there's alot of potential for situations that wouldn't be properly handled. Also, controls on the form are locked when they're loaded this way.There's probably a way around that but I'll drop it since I don't think this is a good solution.

     

    The right way is a proper parser (*cough*Microsoft*cough*).

     

    Marc.

  • Thursday, June 14, 2007 10:37 PMAnonymous5400 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Too bad that didn't work... Sad.  How do you think we could get a good "proper" parser?  I'd be happy to have a swing at SharpDevelop's parser/lexxer if it wouldn't be too complicated (what are the limitations/incompatibilities?).
  • Friday, June 15, 2007 12:16 AMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Are you only planning to load VB code, or do you want C# compatibility as well? I guess it's best to include both if for no other reason than future expansion. I'll whip up a sample using SharpDevelop's version.

     

    The "limitation" of using SharpDevelop's is that it's subject to the GNU Lesser General Public License. That's a pretty forgiving license so it'll probably fit in with what you're doing, but look it over anyway.

     

    Marc.
  • Friday, June 15, 2007 8:32 PMAnonymous5400 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Yeah, in the end I'll want C# support too.  I'll anxiously await your sample Smile!

    Okay, I'll take a look over the licensing there.  Thanks for the heads up!
  • Sunday, June 17, 2007 6:02 AMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    Ok, sorry it took so long... been busy with other things.

     

    Here's a sample design form, designer loader, parser, and ITypeResolutionService. The parser uses SharpDevelop's parser, so you'll need a copy of ICSharpCode.NRefactory.dll. You can get it by downloading the package at http://www.sharpdevelop.net/OpenSource/SD/Download/GetFile.aspx?What=Setup&Release=Serralongue

     

    Here's a breakdown of it all.

     

    1. Form1 has an empty tabpage control and a single menu item that allows you to choose a source VB or C# file.

    2. Pick a file and a new design surface is created. The surface using DesginerLoader to load the file.

    3. DesignerLoader loads the file you selected, as well as any others associated with it (eg. Form1.vb and Form1.Designer.vb).

    4. The two (or more, see the comments) files are merged into a temporary file and parsed via SharpDevParser.

    5. The result is displayed in a new tab page.

     

    And that's about it. Here's the code:

     

    Form1.vb

     

    Code Snippet

    Imports System.IO

    Imports System.ComponentModel

    Imports System.ComponentModel.Design

    Imports Microsoft.VisualBasic

    Imports System.ComponentModel.Design.Serialization

    Public Class Form1

       Dim serviceContainer As ServiceContainer

       Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)

          serviceContainer = New ServiceContainer

          serviceContainer.AddService(GetType(ITypeResolutionService), New TypeResolutionService())

          MyBase.OnLoad(e)

       End Sub

       Private Sub OpenToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles

          OpenToolStripMenuItem.Click

          Dim formFile As String

          Dim openDialog As OpenFileDialog = New OpenFileDialog()

          With openDialog

             .AddExtension = True

             .CheckFileExists = True

             .CheckPathExists = True

             .DefaultExt = "vb"

             .FileName = ""

             .Filter = "VB Files|*.vb|C# Files|*.cs"

             .FilterIndex = 0

             .InitialDirectory = Application.StartupPath

             If .ShowDialog() = Windows.Forms.DialogResult.OK Then

                OpenForm(.FileName)

             End If

          End With

       End Sub

       Private Sub OpenForm(ByVal filePath As String)

          Dim ds As DesignSurface

          Dim loader As DesignerLoader

          Dim tabPage As TabPage

          Dim view As Control

     

          If File.Exists(filePath) Then

             tabPage = New TabPage(Path.GetFileName(filePath))

             ds = New DesignSurface(serviceContainer)

            

             loader = New DesignerLoader(filePath)

             ds.BeginLoad(loader)

     

             view = ds.View

             view.Dock = DockStyle.Fill

             tabPage.Controls.Add(view)

             tcDesigners.TabPages.Add(tabPage)

             tcDesigners.SelectedTab = tabPage

          Else

             MsgBox(filePath & " not found.", MsgBoxStyle.Exclamation, "Open Form")

          End If

       End Sub

    End Class

     

    Form1.Designer.vb:

    Code Snippet

    <Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _

    Partial Class Form1

       Inherits System.Windows.Forms.Form

       'Form overrides dispose to clean up the component list.

       <System.Diagnostics.DebuggerNonUserCode()> _

       Protected Overrides Sub Dispose(ByVal disposing As Boolean)

          Try

             If disposing AndAlso components IsNot Nothing Then

                components.Dispose()

             End If

          Finally

             MyBase.Dispose(disposing)

          End Try

       End Sub

     

       'Required by the Windows Form Designer

       Private components As System.ComponentModel.IContainer

     

       'NOTE: The following procedure is required by the Windows Form Designer

       'It can be modified using the Windows Form Designer.

       'Do not modify it using the code editor.

       <System.Diagnostics.DebuggerStepThrough()> _

       Private Sub InitializeComponent()

          Dim resources As System.ComponentModel.ComponentResourceManager = New       System.ComponentModel.ComponentResourceManager(GetType(Form1))

          Me.MenuStrip1 = New System.Windows.Forms.MenuStrip

          Me.FileToolStripMenuItem = New System.Windows.Forms.ToolStripMenuItem

          Me.OpenToolStripMenuItem = New System.Windows.Forms.ToolStripMenuItem

          Me.tcDesigners = New System.Windows.Forms.TabControl

          Me.MenuStrip1.SuspendLayout()

          Me.SuspendLayout()

          '

          'MenuStrip1

          '

          Me.MenuStrip1.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.FileToolStripMenuItem})

          Me.MenuStrip1.Location = New System.Drawing.Point(0, 0)

          Me.MenuStrip1.Name = "MenuStrip1"

          Me.MenuStrip1.RenderMode = System.Windows.Forms.ToolStripRenderMode.Professional

          Me.MenuStrip1.Size = New System.Drawing.Size(789, 24)

          Me.MenuStrip1.TabIndex = 0

          Me.MenuStrip1.Text = "MenuStrip1"

          '

          'FileToolStripMenuItem

          '

          Me.FileToolStripMenuItem.DropDownItems.AddRange(New System.Windows.Forms.ToolStripItem()       {Me.OpenToolStripMenuItem})

          Me.FileToolStripMenuItem.Name = "FileToolStripMenuItem"

          Me.FileToolStripMenuItem.Size = New System.Drawing.Size(35, 20)

          Me.FileToolStripMenuItem.Text = "&File"

          '

          'OpenToolStripMenuItem

          '

          Me.OpenToolStripMenuItem.Image = CType(resources.GetObject("OpenToolStripMenuItem.Image"), System.Drawing.Image)

          Me.OpenToolStripMenuItem.ImageTransparentColor = System.Drawing.Color.Magenta

          Me.OpenToolStripMenuItem.Name = "OpenToolStripMenuItem"

          Me.OpenToolStripMenuItem.ShortcutKeys = CType((System.Windows.Forms.Keys.Control Or System.Windows.Forms.Keys.O), System.Windows.Forms.Keys)

          Me.OpenToolStripMenuItem.Size = New System.Drawing.Size(152, 22)

          Me.OpenToolStripMenuItem.Text = "&Open"

          '

          'tcDesigners

          '

          Me.tcDesigners.Dock = System.Windows.Forms.DockStyle.Fill

          Me.tcDesigners.Location = New System.Drawing.Point(0, 24)

          Me.tcDesigners.Name = "tcDesigners"

          Me.tcDesigners.SelectedIndex = 0

          Me.tcDesigners.Size = New System.Drawing.Size(789, 473)

          Me.tcDesigners.TabIndex = 1

          '

          'Form1

          '

          Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)

          Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font

          Me.ClientSize = New System.Drawing.Size(789, 497)

          Me.Controls.Add(Me.tcDesigners)

          Me.Controls.Add(Me.MenuStrip1)

          Me.MainMenuStrip = Me.MenuStrip1

          Me.Name = "Form1"

          Me.Text = "Form1"

          Me.MenuStrip1.ResumeLayout(False)

          Me.MenuStrip1.PerformLayout()

          Me.ResumeLayout(False)

          Me.PerformLayout()

       End Sub

       Friend WithEvents MenuStrip1 As System.Windows.Forms.MenuStrip

       Friend WithEvents FileToolStripMenuItem As System.Windows.Forms.ToolStripMenuItem

       Friend WithEvents OpenToolStripMenuItem As System.Windows.Forms.ToolStripMenuItem

       Friend WithEvents tcDesigners As System.Windows.Forms.TabControl

    End Class

     

    DesignerLoader.vb

    Code Snippet

    Imports ICSharpCode.NRefactory

    Imports system.CodeDom

    Imports System.CodeDom.Compiler

    Imports System.ComponentModel.Design

    Imports System.ComponentModel.Design.Serialization

    Imports System.IO

    Imports System.Text

    Public Class DesignerLoader

       Inherits CodeDomDesignerLoader

       ' Enum of supported language file extensions. These entries match corresponding SharpDevelop SupportedLanguage

       ' values.

       Private Enum SupportedLanguages

          CS

          VB

       End Enum

     

       ' Imports / using statements for supported languages.

       Dim importTokens As String() = {"using", "Imports"}

     

       ' Statement termination characters for supported languages.

       Dim statementTerminators As String() = {";", ""}

     

       Dim sourceFile As String

       Dim provider As CodeDomProvider

       Dim errors As String

     

    #Region " Construction / Initialization "

       ''' <summary>

       ''' Duh

       ''' </summary>

       ''' <param name="fileName">A code file to open.</param>

       ''' <remarks>If the file has matching associates, they will be loaded as well. See LoadReaders()

       ''' for details.</remarks>

       Public Sub New(ByVal fileName As String)

          Init(fileName)

       End Sub

     

       ''' <summary>

       ''' Saves the requested source files and gets a CodeDomProvider appropriate for the file type.

       ''' </summary>

       ''' <param name="fileName">Name of the file to load.</param>

       ''' <remarks></remarks>

       Private Sub Init(ByVal fileName As String)

          sourceFile = fileName

          provider = GetProvider(fileName)

       End Sub

    #End Region

    #Region " CodeDomDesignerLoader implementation / overrides "

       ''' <summary>

       ''' Returns a reference to our CodeDomProvider, which is specific to the type of file passed in

       ''' at initialization.

       ''' </summary>

       ''' <value></value>

       ''' <returns></returns>

       ''' <remarks></remarks>

       Protected Overrides ReadOnly Property CodeDomProvider() As System.CodeDom.Compiler.CodeDomProvider

          Get

             Return provider

          End Get

       End Property

       ''' <summary>

       ''' Parses the specified source file (and associated designer / partial files) with the help of SharpDevelop.

       ''' </summary>

       ''' <returns>A CodeCompileUnit representing the class(es) included in the source file.</returns>

       ''' <remarks></remarks>

       Protected Overrides Function Parse() As System.CodeDom.CodeCompileUnit

          Dim parser As SharpDevParser

          Dim mergeFileName As String = ""

          Dim reader As TextReader = Nothing

          Dim ccu As CodeCompileUnit = Nothing

          Dim readers As List(Of TextReader) = LoadReaders(sourceFile)

     

          If readers.Count > 0 Then

             mergeFileName = MergeFiles(readers)

             If mergeFileName.Length > 0 Then

                reader = File.OpenText(mergeFileName)

             End If

          End If

     

          If reader IsNot Nothing Then

                Try

                   parser = New SharpDevParser()

                   ccu = parser.Parse(GetLanguageIndex(provider), reader, GetService(GetType(ITypeResolutionService)))

                Catch ex As Exception

                Finally

                   reader.Close()

                   Try

                      File.Delete(mergeFileName)

                   Catch ex As Exception

                   End Try

                End Try

          End If

     

          CloseReaders(readers)

          Return ccu

       End Function

     

       ''' <summary>

       ''' Returns an instance of a Type Resultion Service. We don't provide one ourselves... just return whatever the

       ''' LoaderHost gives us.

       ''' </summary>

       ''' <value>Whatever the LoaderHost gives us via GetService().</value>

       ''' <returns>Uhhh, I just said that... Whatever the LoaderHost gives us via GetService().</returns>

       ''' <remarks></remarks>

       Protected Overrides ReadOnly Property TypeResolutionService() As    System.ComponentModel.Design.ITypeResolutionService

          Get

             Return LoaderHost.GetService(GetType(TypeResolutionService))

          End Get

       End Property

     

       ''' <summary>

       ''' Not supported in this implementation.

       ''' </summary>

       ''' <param name="unit">CodeCompileUnit to write to file.</param>

       ''' <remarks></remarks>

       Protected Overrides Sub Write(ByVal unit As System.CodeDom.CodeCompileUnit)

          '

       End Sub

    #End Region

    #Region " Nitty gritty "

       ''' <summary>

       ''' Attempts to load all partial class definitions given a source code file.

       ''' </summary>

       ''' <param name="sourceFile">One of the source files in a set of related files</param>

       ''' <returns>A list of TextReaders, each loaded with a file assumed to belong to the same

       ''' class definition. This list will be empty (count = 0) if an error occurs or the specified

       ''' file is not found.</returns>

       ''' <remarks>Partial classes are supported but will only be located if all class files reside

       ''' in the same folder and follow the same naming pattern as the file specified. The naming pattern

       ''' looked for is {some name}{optional ".*"}.{language extension}

       '''

       ''' Eg. If Form1.Designer.vb is specified, then its code-behind file Form1.vb would also be loaded,

       ''' as would a file named Form1.partial2.vb, etc.

       ''' </remarks>

       Private Function LoadReaders(ByVal sourceFile As String) As List(Of TextReader)

          Dim basePath As String = Path.GetDirectoryName(sourceFile)

          Dim fileExtension As String = Path.GetExtension(sourceFile)

          Dim baseName As String = GetBaseFileName(sourceFile)

          Dim files() As String = Directory.GetFiles(basePath, baseName & "*" & fileExtension)

          Dim validFiles As List(Of String) = New List(Of String)

          Dim readerList As List(Of TextReader) = New List(Of TextReader)

          Dim regEx As RegularExpressions.Regex = New RegularExpressions.Regex("(" & baseName & ")(\..+)*(" & fileExtension & ")")

     

          Try

             For Each file As String In files

                If regEx.IsMatch(file) Then

                   validFiles.Add(file)

                End If

             Next

             For Each name As String In validFiles

                readerList.Add(File.OpenText(name))

             Next

          Catch ex As Exception

             Trace.WriteLine("Exception opening source files - " & ex.Message)

             CloseReaders(readerList)

             readerList.Clear()

          End Try

          Return readerList

       End Function

     

       ''' <summary>

       ''' Gets the first part of a source code file name (ie. everything before the first ".").

       ''' </summary>

       ''' <param name="fullPath">Full path to the file to evaluate.</param>

       ''' <returns>Base file name.</returns>

       ''' <remarks>Give a file named "Form1.Designer.vb", this function would return "Form1".</remarks>

       Private Function GetBaseFileName(ByVal fullPath As String) As String

          Dim baseName As String = Path.GetFileNameWithoutExtension(fullPath)

          Dim theDot As Integer = baseName.IndexOf(".")

     

          If theDot > 0 Then

             baseName = baseName.Substring(0, theDot)

          End If

          Return baseName

       End Function

     

       ''' <summary>

       ''' Merges related code files into a temporary file.

       ''' </summary>

       ''' <param name="fileReaders">List of open TextReaders, each loaded with a related code file.</param>

       ''' <returns>Path to the merged file.</returns>

       ''' <remarks>The resulting file will have all Import / using statements from all files moved to the top.

       ''' Parsers like it that way.

       ''' </remarks>

       Private Function MergeFiles(ByVal fileReaders As List(Of TextReader)) As String

          Dim sourceCode As String = ""

          Dim importList As List(Of String)

          Dim importToken As String

          Dim statementTerminator As String

          Dim mergeFile As String = ""

          Dim language As Integer = GetLanguageIndex(provider)

     

          If language < 0 Then Return ""

     

          Try

             importList = New List(Of String)

             importToken = importTokens(language)

             statementTerminator = statementTerminators(language)

     

             For Each reader As TextReader In fileReaders

                sourceCode += PullCodeExtractImports(reader, importToken, statementTerminator, importList)

             Next

     

             importList.Sort()

             For index As Integer = 0 To importList.Count - 1

                importList(index) = importToken & " " & importList(index)

             Next

     

             sourceCode = String.Join(statementTerminator & vbCrLf, importList.ToArray) & vbCrLf & sourceCode

             If sourceCode.Length > 0 Then

                mergeFile = Path.GetTempFileName()

                File.AppendAllText(mergeFile, sourceCode)

             End If

     

          Catch ex As Exception

             Trace.WriteLine(ex.ToString)

             If mergeFile.Length > 0 Then

                Try

                   File.Delete(mergeFile)

                Catch ex2 As Exception

                End Try

             End If

             mergeFile = ""

          End Try

          Return mergeFile

       End Function

     

       ''' <summary>

       ''' Pulls the code from a text file, excluding Import / using statements.

       ''' </summary>

       ''' <param name="reader">Reader opened with the source file to read.</param>

       ''' <param name="importToken">Language-specific token for the Imports / using statement.</param>

       ''' <param name="statementTerminator">Language-specific code statement terminator. ";" in C#, empty string in VB.</param>

       ''' <param name="importList">Reference to a list of import statements. Imports found in the current file will be added

       ''' to this list.</param>

       ''' <returns>The resulting code, as a string, minus any import statements.</returns>

       ''' <remarks></remarks>

       Private Function PullCodeExtractImports(ByVal reader As TextReader, ByVal importToken As String, ByVal    statementTerminator As String, ByRef importList As List(Of String)) As String

          Dim line As String = Nothing

          Dim resultString As String = ""

          Dim strippedLine As String

     

          line = reader.ReadLine

          Do Until line Is Nothing

             strippedLine = line.Trim.TrimEnd(New Char() {statementTerminator})

     

             If strippedLine.ToLower.StartsWith(importToken.ToLower) AndAlso _

                importList.IndexOf(strippedLine.Substring(importToken.Length + 1)) < 0 Then

                importList.Add(strippedLine.Substring(importToken.Length + 1))

             Else

                resultString += line & vbCrLf

             End If

     

             line = reader.ReadLine

          Loop

          Return resultString

       End Function

     

       ''' <summary>

       ''' Returns a SupportedLanguages value given a language-specific CodeDomProvider.

       ''' </summary>

       ''' <param name="provider">The CodeDomProvider for the language to query.</param>

       ''' <returns>One of the SupportedLanguages values, -1 if CodeDomProvider is not supported by this

       ''' class</returns>

       ''' <remarks></remarks>

       Private Function GetLanguageIndex(ByVal provider As CodeDomProvider) As Integer

          Try

             Return CInt(System.Enum.Parse(GetType(SupportedLanguages), provider.FileExtension, True))

          Catch ex As Exception

             Return -1

          End Try

       End Function

     

       ''' <summary>

       ''' Closes a bunch of text readers.

       ''' </summary>

       ''' <param name="readers">List of open TextReaders to close.</param>

       ''' <remarks></remarks>

       Private Sub CloseReaders(ByVal readers As List(Of TextReader))

          For Each reader As TextReader In readers

             Try

                reader.Close()

             Catch ex As Exception

             End Try

          Next

       End Sub

     

       ''' <summary>

       ''' Returns a CodeDomProvider appropriate for the file specified, based on its file extension.

       ''' </summary>

       ''' <param name="fileName">Code file whose provider is to be loaded.</param>

       ''' <returns>A CodeDomProvider appropriate for the file specified if successful,

       ''' Nothing otherwise.</returns>

       ''' <remarks></remarks>

       Private Function GetProvider(ByVal fileName As String) As CodeDomProvider

          Try

             Dim language As String = CodeDomProvider.GetLanguageFromExtension(Path.GetExtension(fileName))

             Return CodeDomProvider.CreateProvider(language)

          Catch ex As Exception

             Trace.WriteLine(ex.ToString)

             Return Nothing

          End Try

       End Function

    #End Region

    End Class

     

    SharpDevParser.vb

    Code Snippet

    Imports system.CodeDom

    Imports System.CodeDom.Compiler

    Imports System.ComponentModel

    Imports System.ComponentModel.Design

    Imports System.ComponentModel.Design.Serialization

    Imports System.Windows.Forms.Design

    Imports System.IO

    Imports ICSharpCode.NRefactory

    Public Class SharpDevParser

       Dim _errorOutput As String = ""

       Dim _errorCount As Integer = 0

     

       ''' <summary>

       ''' Uses the SharpDevelop parser to parse the source file.

       ''' </summary>

       ''' <returns>A CodeCompileUnit representing the source file, Nothing if parsing fails.</returns>

       ''' <remarks>If the parse fails, ErrorOutput will contain the parser's ErrorOutput string and

       ''' ErrorCount will indicate the number of errors encountered.</remarks>

       Public Function Parse(ByVal language As SupportedLanguage, ByVal sourceReader As TextReader, ByVal typeResolutionService As ITypeResolutionService) As CodeCompileUnit

     

          Dim parser As ICSharpCode.NRefactory.IParser = ParserFactory.CreateParser(language, sourceReader)

          Dim visitor As Visitors.CodeDomVisitor

     

          ResetErrors()

     

          parser.ParseMethodBodies = True

          parser.Parse()

     

          _errorCount = parser.Errors.Count

          _errorOutput = parser.Errors.ErrorOutput

     

          If ErrorCount = 0 Then

             visitor = New Visitors.CodeDomVisitor()

             visitor.EnvironmentInformationProvider = New EnvironmentInformationProvider(typeResolutionService)

             visitor.VisitCompilationUnit(parser.CompilationUnit, Nothing)

             Return visitor.codeCompileUnit

          Else

             Return Nothing

          End If

       End Function

     

       ''' <summary>

       ''' Resets ErrorOutput and ErrorCount to default values.

       ''' </summary>

       ''' <remarks></remarks>

       Private Sub ResetErrors()

          _errorCount = 0

          _errorOutput = ""

       End Sub

     

       ''' <summary>

       ''' Returns a string representing errors encountered during a parse.

       ''' </summary>

       ''' <value></value>

       ''' <returns></returns>

       ''' <remarks></remarks>

       Public ReadOnly Property ErrorOutput() As String

          Get

             Return _errorOutput

          End Get

       End Property

     

       ''' <summary>

       ''' Returns the number of errors encountered during a parse.

       ''' </summary>

       ''' <value></value>

       ''' <returns></returns>

       ''' <remarks></remarks>

       Public ReadOnly Property ErrorCount() As Integer

          Get

             Return _errorCount

          End Get

       End Property

    End Class

     

    ''' <summary>

    ''' Implementation of IEnvironmentInformationProvider.

    ''' </summary>

    ''' <remarks>IEnvironmentInformationProvider is used by a Visitor to determine whether a Type contains

    ''' a field with the specific name. This is necessary because the parser can't tell, from reading the code,

    ''' whether a member is a field or a property. An Enum's values, for instance, cannot be differentiated

    ''' from a class type's properties.

    '''

    ''' Failure to make this determination can result in failure to load the member. In the absence of an

    ''' IEnvironmentInformationProvider the parser will assume a property/</remarks>

    Friend Class EnvironmentInformationProvider

       Implements IEnvironmentInformationProvider

       Dim resolutionService As ITypeResolutionService

     

       ''' <summary>

       ''' Constructor.

       ''' </summary>

       ''' <param name="typeResolutionService">A Type Resolution Service instance. This is used to resolve the type

       ''' requested.</param>

       ''' <remarks></remarks>

       Public Sub New(ByVal typeResolutionService As ITypeResolutionService)

          resolutionService = typeResolutionService

       End Sub

     

       ''' <summary>

       ''' Determines if a type has a field with a given name.

       ''' </summary>

       ''' <param name="fullTypeName">Name of the type to query.</param>

       ''' <param name="fieldName">Name of the field to look for.</param>

       ''' <returns>True if the type has a field by the given name, False if an error occurs or the type

       ''' doesn't contain a corresponding field.</returns>

       ''' <remarks></remarks>

       Public Function HasField(ByVal fullTypeName As String, ByVal fieldName As String) As Boolean Implements    ICSharpCode.NRefactory.IEnvironmentInformationProvider.HasField

          Dim theType As Type = Type.GetType(fullTypeName)

     

          If theType Is Nothing AndAlso resolutionService IsNot Nothing Then

             theType = resolutionService.GetType(fullTypeName)

          End If

          If theType Is Nothing Then

             Return False

          Else

             Return Not theType.GetField(fieldName) Is Nothing

          End If

       End Function

    End Class

     

    And finally, you need a decent ITypeResolutionService. This is a modified version of SharpDevelop's.

    TypeResolutionService.vb

    Code Snippet

    Imports System

    Imports System.ComponentModel.Design

    Imports System.Reflection

    Imports System.IO

    Imports Microsoft.Win32

    ''' <summary>

    ''' Modified port of SharpDev's TypeResolutionService. Resolves types during a designer load.

    ''' </summary>

    ''' <remarks></remarks>

    Public Class TypeResolutionService

       Implements ITypeResolutionService

       <System.Runtime.InteropServices.DllImport("kernel32.dll")> _

       Private Shared Function MoveFileEx(ByVal lpExistingFileName As String, ByVal lpNewFileName As String, ByVal dwFlags As Integer) As Boolean

       End Function

     

       Shared ReadOnly _designerAssemblies As List(Of Assembly) = New List(Of Assembly)

       Shared ReadOnly assemblyDict As Dictionary(Of String, Assembly) = New Dictionary(Of String, Assembly)

       '''<summary>

       ''' List of assemblies used by the form designer. This static list is not an optimal solution,

       ''' but better than using AppDomain.CurrentDomain.GetAssemblies(). See SD2-630.

       ''' </summary>

       Public Shared ReadOnly Property DesignerAssemblies() As List(Of Assembly)

          Get

             Return _designerAssemblies

          End Get

       End Property

       Shared Sub New()

          ClearMixedAssembliesTemporaryFiles()

          ' Preload commonly referenced assemblies.

          DesignerAssemblies.Add(GetType(Object).Assembly) ' MSCorlib

          DesignerAssemblies.Add(GetType(Uri).Assembly) ' System

          DesignerAssemblies.Add(GetType(System.Drawing.Point).Assembly) ' System.Drawing

          DesignerAssemblies.Add(GetType(System.Windows.Forms.AutoScaleMode).Assembly) ' System.Windows.Forms

          DesignerAssemblies.Add(GetType(System.Windows.Forms.Design.AnchorEditor).Assembly) ' System.Windows.Forms.Design

          RegisterVSDesignerWorkaround()

          AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf AssemblyResolveEventHandler

       End Sub

     

       Const MOVEFILE_DELAY_UNTIL_REBOOT As Integer = &H4

       Shared Sub MarkFileToDeleteOnReboot(ByVal fileName As String)

          MoveFileEx(fileName, Nothing, MOVEFILE_DELAY_UNTIL_REBOOT)

       End Sub

       Shared Sub ClearMixedAssembliesTemporaryFiles()

          Dim files As String() = Directory.GetFiles(Path.GetTempPath(), "*.sd_forms_designer_mixed_assembly.dll")

          For Each filename As String In files

             Try

                File.Delete(filename)

             Catch

             End Try

          Next

       End Sub

       Dim formSourceFileName As String

       Public Sub New()

       End Sub

       Public Sub New(ByVal formSourceFileName As String)

          Me.formSourceFileName = formSourceFileName

       End Sub

       Shared Function GetHash(ByVal fileName As String) As String

          Return Path.GetFileName(fileName).ToLowerInvariant() + File.GetLastWriteTimeUtc(fileName).Ticks.ToString()

       End Function

       ''' <summary>

       ''' Loads the file in none-locking mode. Returns null on failure.

       '''</summary>

       Public Shared Function LoadAssembly(ByVal fileName As String) As Assembly

          If Not File.Exists(fileName) Then

             Return Nothing

          End If

          ' FIX for SD2-716, remove when designer gets its own AppDomain

          For Each asm As Assembly In AppDomain.CurrentDomain.GetAssemblies()

             Try

                If String.Equals(asm.Location, fileName, StringComparison.InvariantCultureIgnoreCase) Then

                   RegisterAssembly(asm)

                   Return asm

                End If

             Catch e As NotSupportedException

                ' Fixes forum-12823

             End Try

          Next

          Dim hash As String = GetHash(fileName)

          SyncLock assemblyDict

             Dim asm As Assembly

             If assemblyDict.TryGetValue(hash, asm) Then

                Return asm

             End If

             Try

                asm = Assembly.Load(File.ReadAllBytes(fileName))

             Catch e As BadImageFormatException

                If e.Message.Contains("HRESULT: 0x8013141D") Then

                   Dim tempPath As String = Path.GetTempFileName()

                   File.Delete(tempPath)

                   tempPath += ".sd_forms_designer_netmodule_assembly.dll"

                   Try

                      'convert netmodule to assembly

                      Dim p As Process = New Process()

                      p.StartInfo.UseShellExecute = False

                      p.StartInfo.FileName = Path.GetDirectoryName(GetType(Object).Module.FullyQualifiedName) +    Path.DirectorySeparatorChar + "al.exe"

                      p.StartInfo.Arguments = """" & fileName & " /out:""" + tempPath + """"

                      p.StartInfo.CreateNoWindow = True

                      p.Start()

                      p.WaitForExit()

                      If p.ExitCode = 0 And File.Exists(tempPath) Then

                         Dim asm_data As Byte() = File.ReadAllBytes(tempPath)

                         asm = Assembly.Load(asm_data)

                         asm.LoadModule(Path.GetFileName(fileName), File.ReadAllBytes(fileName))

                      End If

                   Catch ex As Exception

                      MsgBox(ex.ToString, MsgBoxStyle.Critical, "Error calling linker for netmodule")

                   End Try

                   Try

                      File.Delete(tempPath)

                   Catch

                   End Try

                Else

                   Throw ' don't ignore other load errors

                End If

             Catch e As FileLoadException

                If e.Message.Contains("HRESULT: 0x80131402") Then

                   'this is C++/CLI Mixed assembly which can only be loaded from disk, not in-memory

                   Dim tempPath As String = Path.GetTempFileName()

                   File.Delete(tempPath)

                   tempPath += ".sd_forms_designer_mixed_assembly.dll"

                   File.Copy(fileName, tempPath)

                   asm = Assembly.LoadFile(tempPath)

                   MarkFileToDeleteOnReboot(tempPath)

                Else

                   Throw ' don't ignore other load errors

                End If

             End Try

             SyncLock DesignerAssemblies

                If Not DesignerAssemblies.Contains(asm) Then

                   DesignerAssemblies.Insert(0, asm)

                End If

             End SyncLock

             assemblyDict(hash) = asm

             Return asm

          End SyncLock

       End Function

       Public Shared Sub RegisterAssembly(ByVal asm As Assembly)

          Dim file As String = asm.Location

          If file.Length > 0 Then

             SyncLock assemblyDict

                assemblyDict(GetHash(file)) = asm

             End SyncLock

          End If

          SyncLock DesignerAssemblies

             If Not DesignerAssemblies.Contains(asm) Then

                DesignerAssemblies.Insert(0, asm)

             End If

          End SyncLock

       End Sub

       Public Function GetAssembly(ByVal name As AssemblyName) As Assembly Implements    System.ComponentModel.Design.ITypeResolutionService.GetAssembly

          Return LoadAssembly(name, False)

       End Function

       Public Function GetAssembly(ByVal name As AssemblyName, ByVal throwOnError As Boolean) As Assembly Implements System.ComponentModel.Design.ITypeResolutionService.GetAssembly

          Return LoadAssembly(name, throwOnError)

       End Function

       Shared Function LoadAssembly(ByVal name As AssemblyName, ByVal throwOnError As Boolean) As Assembly

          Try

             Dim asm As Assembly = Assembly.Load(name)

             RegisterAssembly(asm)

             Return asm

          Catch e As System.IO.FileLoadException

             If throwOnError Then

                Throw

             End If

             Return Nothing

          End Try

       End Function

       Public Function GetPathOfAssembly(ByVal name As AssemblyName) As String Implements System.ComponentModel.Design.ITypeResolutionService.GetPathOfAssembly

          Dim Assembly As Assembly = GetAssembly(name)

          If Assembly IsNot Nothing Then

             Return Assembly.Location

          End If

          Return Nothing

       End Function

       Public Overloads Function [GetType](ByVal name As String) As Type Implements    System.ComponentModel.Design.ITypeResolutionService.GetType

          Return [GetType](name, False, False)

       End Function

       Public Overloads Function [GetType](ByVal name As String, ByVal throwOnError As Boolean) As Type Implements System.ComponentModel.Design.ITypeResolutionService.GetType

          Return [GetType](name, throwOnError, False)

       End Function

       Public Overloads Function [GetType](ByVal name As String, ByVal throwOnError As Boolean, ByVal ignoreCase As Boolean) As Type Implements System.ComponentModel.Design.ITypeResolutionService.GetType

          If name Is Nothing OrElse name.Length = 0 Then

             Return Nothing

          End If

          If IgnoreType(name) Then

             Return Nothing

          End If

          Try

             Dim type As Type = System.Type.GetType(name, False, ignoreCase)

             ' type lookup for typename, assembly, xyz style lookups

             If type Is Nothing Then

                Dim idx As Integer = name.IndexOf(",")

                If idx > 0 Then

                   Dim splitName As String() = name.Split(",")

                   Dim typeName As String = splitName(0)

                   Dim assemblyName As String = splitName(1).Substring(1)

                   Dim assembly As Assembly = Nothing

                   Try

                      assembly = assembly.Load(assemblyName)

                   Catch e As Exception

                   End Try

                   If assembly IsNot Nothing Then

                      SyncLock DesignerAssemblies

                         If Not DesignerAssemblies.Contains(assembly) Then

                            DesignerAssemblies.Add(assembly)

                         End If

                      End SyncLock

                      type = assembly.GetType(typeName, False, ignoreCase)

                   Else

                      type = type.GetType(typeName, False, ignoreCase)

                   End If

                End If

             End If

             If type Is Nothing Then

                SyncLock DesignerAssemblies

                   For Each asm As Assembly In DesignerAssemblies

                      Dim t As Type = asm.GetType(name, False)

                      If t IsNot Nothing Then

                         Return t

                      End If

                   Next

                End SyncLock

             End If

             If type Is Nothing Then

                type = SearchLoadedAssemblies(name, ignoreCase)

             End If

             If throwOnError And type Is Nothing Then

                Throw New TypeLoadException(name + " not found by TypeResolutionService")

             End If

             Return type

          Catch e As Exception

          End Try

          Return Nothing

       End Function

       Public Sub ReferenceAssembly(ByVal name As AssemblyName) Implements System.ComponentModel.Design.ITypeResolutionService.ReferenceAssembly

          Trace.WriteLine(name)

       End Sub

    #Region " VSDesigner workarounds "

       '''<summary>

       ''' HACK - Ignore any requests for types from the Microsoft.VSDesigner

       ''' assembly. There are smart tag problems if data adapter

       ''' designers are used from this assembly.

       ''' </summary>

       Function IgnoreType(ByVal name As String) As Boolean

          Dim idx As Integer = name.IndexOf(",")

          If idx > 0 Then

             Dim splitName As String() = name.Split(",")

             Dim assemblyName As String = splitName(1).Substring(1)

             If assemblyName = "Microsoft.VSDesigner" Then

                Return True

             End If

          End If

          Return False

       End Function

       Shared vsDesignerIdeDir As String

       Shared Sub RegisterVSDesignerWorkaround()

          If vsDesignerIdeDir Is Nothing Then

             vsDesignerIdeDir = ""

             Dim key As RegistryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\Microsoft\VisualStudio\8.0\Setup\VS")

             If key IsNot Nothing Then

                vsDesignerIdeDir = key.GetValue("VS7CommonDir").ToString

                If vsDesignerIdeDir.Length > 0 Then

                   vsDesignerIdeDir = Path.Combine(vsDesignerIdeDir, "IDE")

                   AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf DomainResolveHandler

                End If

             End If

          End If

       End Sub

       Shared Function DomainResolveHandler(ByVal sender As Object, ByVal args As ResolveEventArgs) As Assembly

          Dim shortName As String = args.Name

          If shortName.IndexOf(",") >= 0 Then

             shortName = shortName.Substring(0, shortName.IndexOf(","))

          End If

          If shortName.StartsWith("Microsoft.") And File.Exists(Path.Combine(vsDesignerIdeDir, shortName & ".dll")) Then

             Return Assembly.LoadFrom(Path.Combine(vsDesignerIdeDir, shortName + ".dll"))

          End If

          Return Nothing

       End Function

    #End Region

       Shared Function SearchLoadedAssemblies(ByVal name As String, ByVal ignoreCase As Boolean) As Type

          Dim lastAssembly As Assembly = Nothing

          Dim foundType As Type = Nothing

          For Each asm As Assembly In AppDomain.CurrentDomain.GetAssemblies

             foundType = asm.GetType(name, False, ignoreCase)

             If foundType IsNot Nothing Then

                lastAssembly = asm

                Exit For

             End If

          Next

          If lastAssembly IsNot Nothing Then

             TypeResolutionService.DesignerAssemblies.Add(lastAssembly)

          End If

          Return foundType

       End Function

       Shared Function AssemblyResolveEventHandler(ByVal sender As Object, ByVal args As ResolveEventArgs) As Assembly

          Dim lastAssembly As Assembly = Nothing

          For Each asm As Assembly In TypeResolutionService.DesignerAssemblies

             If asm.FullName = args.Name Then

                Return asm

             End If

          Next

          For Each asm As Assembly In AppDomain.CurrentDomain.GetAssemblies

             If asm.FullName = args.Name Then

                lastAssembly = asm

             End If

          Next

          If lastAssembly IsNot Nothing Then

             TypeResolutionService.DesignerAssemblies.Add(lastAssembly)

          End If

       Return lastAssembly

       End Function

    End Class

     

  • Wednesday, June 20, 2007 9:16 PMSoftware Pathologist Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Marc, you rock.

     

    I've been looking for an example of this stuff for over a week.  Everybody shows you how to use the CodeDom to generate the code but nobody worries about modifying the code generated.

     

    I'm going to work on converting your sample to C#, let me know if you want me to repost it or send it to you or something appropriate.

     

    Russ

  • Thursday, June 21, 2007 2:10 AMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Russ,

     

    Everybody shows you how to use the CodeDom to generate the code but nobody worries about modifying the code generated.

     

    Yeah, I noticed Smile Looking into this taught me why that is though. Even if Microsoft provided parsers with its CodeDomProviders, they wouldn't be bulletproof because you're opening a file that wasn't created or managed in a known way (as opposed to creating and modifying a form in VS, where they can control the circumstances.) Still I wish they'd supply a best-guess implementation and save us this aggravation.

     

    I personally don't need the parser but feel free to post a C# translation. I'm sure many would appreciate it.

     

    Regards,

    Marc.

  • Thursday, June 21, 2007 3:12 AMAnonymous5400 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Incredible!  Your sample accomplished what I haven't been able to do for weeks! Smile  I do have a couple more quick questions, though.  First, under the LGPL with ICSharpCode.NRefactory.dll, am I allowed to use the library if I just add it as a reference like for using your sample then distributing it as such in a commercial application?  What kind of license info would I have to provide?  Also, is there any way to edit the DesignerLoader class so that it can generate code from the designer, like in the MSDN Magazine sample's CodeDomHostLoader?  Finally, is it possible to associate an opened form with a PropertyGrid?  I can get this to work with new forms, just not opened ones! Smile

    Thank you so much for your terrific help - you rock!
  • Thursday, June 21, 2007 4:33 PMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    First, under the LGPL with ICSharpCode.NRefactory.dll, am I allowed to use the library if I just add it as a reference like for using your sample then distributing it as such in a commercial application? 

     

    I'm not a lawyer or anything, but my understanding is that you can distribute the DLL with your app. as long as you haven't made any code changes to it. If you do, then you have to publish the source for the modified DLL.

     

    Here are relevant quotes:

     

    From the #Develop licensing FAQ:

     

    What’s in for me (the developer) now that you changed the license?

    We imagine two main scenarios: you writing arbitrarily-licensed addins for #develop, as well as you using our assemblies to build your applications (especially the core, the text editor and #report). After all, linking to our binary assemblies is now restriction-free. All we ask (but not require) is that you do not rename the assemblies you use (we see this as free PR for us).

     

    What if I need to modify the assemblies to suit my needs?

    The only difference between GPL and LGPL is the linking exception. So if you need to modify the source code, the restriction of shipping your source code modifications alongside the compiled binary still kicks in. If you do not want to ship your source code modification, get in touch with us.

     

    From Christoph Wille (one of the brains behind #D) in a forum post:

     

    any modifications to our codebase must be made public. Linking to assemblies that are based on such a modified codebase is fine. Because our codebase is LGPL (linking to assemblies fine, modifications of the source code must be released under the same license).

     

    From GNU.Org:

     

    5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License.

    However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables.

     

    Also, is there any way to edit the DesignerLoader class so that it can generate code from the designer, like in the MSDN Magazine sample's CodeDomHostLoader?

     

    Sure Smile Once you have it in the designer, it's in object form and is available for code generation. You'll need to use some smarts if you want to split the object into multiple source files the way VS does (one designer file, one "user" file.)  I'll see about putting something together.

     

    Finally, is it possible to associate an opened form with a PropertyGrid?

     

    I'm sure there is but I'm drawing a blank on it at the moment. Gotta head out for a bit but I'll get back to you on that a little later.

     

    Marc.

  • Friday, June 22, 2007 4:43 PMSoftware Pathologist Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    I'm not going to modify the parser so much as to modify your sample code and make all of it C#.

     

    Annonymous 5400 was asking about linking to a Property Grid.  I am in the process of getting that up and running and can post it in a while.  The project I am coding also includes generating and storing the C# code that is derived from the CodeCompileUnit so that too is part of my next steps.  It sounds like Anonymous and I are on a parallel tack.

     

    Marc, when I am done are you interested in co-publishing anything on this?  The only ones who really know this stuff and tell you so are the CSharp folks and there is so much there as an example that it's overwhelming.  It wasn't until I saw you override the Parse method of the loader that I realized what was going on.  Until then I knew how to get a ccu but didn't know what to do with it.

     

    Russ

  • Friday, June 22, 2007 5:08 PMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Hey Russ,

     

    I was just about to post the propertygrid thing Smile I'll do the VB side, you go ahead and post your C# version. Anonnymous, if you're able to get it working with an existing form you shouldn't have a problem with the new one. It's all in the design surface.

     

    As far as co-publishing, I'm game for pretty much anything. Drop me a line at the email address in my profile.

     

    I should make it clear, though, that there are pitfalls to this approach. I just now ran into a problem - #D's parser doesn't like the VB event handler construct when you're handling an event in your own class (for instance, MyForm_Load(...) Handles Me.Load)  The visitor will still work but I don't know yet what it does behind the scenes. I'll go search the #D forums to see if it's a known limitation.

     

    I also ran into a problem with a referencing a resource property  - the statement "Me.PictureBox1.Image = Global.WindowTester.My.Resources.Resources.Test21"  is accepted by SharpDev but VS complains that Test21 isn't a valid property. Again, seems to be a compatibility issue between #D and VS.

     

    Anyway, Anonnymous, here's a very basic design surface that'll refresh the property grid when you select something in your opened form. Create the following design surface, then modify the OpenForm sub in the sample I posted. Replace the line ds = New DesignSurface(serviceContainer)  with  ds = New BasicDesignSurface(serviceContainer) 

     

    BasicDesignSurface:

     

    Code Snippet

    Imports System.ComponentModel.Design

    Public Class BasicDesignSurface

       Inherits DesignSurface

       Dim selectionSvc As ISelectionService

       Public Sub New()

          Me.New(Nothing)

       End Sub

       Public Sub New(ByVal parentProvider As IServiceProvider)

          MyBase.New(parentProvider)

          HookSelectionChangeEvents()

       End Sub

       Private Sub HookSelectionChangeEvents()

          If SelectionService IsNot Nothing Then

             AddHandler SelectionService.SelectionChanged, AddressOf selectionService_SelectionChanged

          End If

       End Sub

       Private Sub UnhookSelectionChangeEvents()

          If SelectionService IsNot Nothing Then

             RemoveHandler SelectionService.SelectionChanged, AddressOf selectionService_SelectionChanged

          End If

       End Sub

       Public ReadOnly Property SelectionService() As ISelectionService

          Get

             If IsNothing(selectionSvc) Then

                selectionSvc = GetService(GetType(ISelectionService))

             End If

             Return selectionSvc

          End Get

       End Property

       ''' <summary>

       ''' When the selection changes this sets the PropertyGrid's selected component

    ''' </summary>

       Private Sub selectionService_SelectionChanged(ByVal sender As Object, ByVal e As EventArgs)

          Dim comps As ArrayList = New ArrayList

          Dim propertyGrid As PropertyGrid

          If SelectionService IsNot Nothing Then

             propertyGrid = TryCast(GetService(GetType(PropertyGrid)), PropertyGrid)

             If propertyGrid IsNot Nothing Then

                propertyGrid.SelectedObjects = SelectionService.GetSelectedComponents

             End If

          End If

       End Sub

       Protected Overrides Sub Dispose(ByVal disposing As Boolean)

          UnhookSelectionChangeEvents()

          MyBase.Dispose(disposing)

       End Sub

    End Class

     

     

    Regards,

    Marc.

  • Friday, June 22, 2007 7:28 PMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Ok the event handler thing is documented here. Here's how to fix the sample code.

     

    1. Replace the If / Else in the loop in function DesignerLoader.PullCodeExtractImports() with the following:

     

    Code Snippet

    If strippedLine.ToLower.StartsWith(importToken.ToLower) AndAlso _

       importList.IndexOf(strippedLine.Substring(importToken.Length + 1)) < 0 Then

     

       importList.Add(strippedLine.Substring(importToken.Length + 1))

    Else

       ' Compatibility work-around: #Develop's parser doesn't like VB's "Handles Me.xxx" event handler

       ' statement. The fix is to replace "Handles Me." with "Handles MyBase." Fortunately the latter

       ' statement is also valid in VS

       resultString += ReplaceHandlesClause(line) & vbCrLf

    End If

     

    2. Add new function DesignerLoader.ReplacesHandlesClause():

     

    Code Snippet

    ''' <summary>

    ''' Replaces a VB Handles clause ("MyForm_Load(...) Handles Me.Load") with one that is compatible with

    ''' SharpDev's parser ("Handles MyBase.Load").

    ''' </summary>

    ''' <param name="line">Source code line to work on.</param>

    ''' <returns>Source line with a modified Handles clause.</returns>

    ''' <remarks>A Handles clause can include a list of events being handled, so we have to find the

    ''' start of the clause and work through the remainder of the string to find the "Me" references.</remarks>

    Private Function ReplaceHandlesClause(ByVal line As String) As String

       Dim handlesClause As String = "Handles"

       Dim meStatement As String = "Me"

       Dim myBaseStatement As String = "MyBase"

       Dim handlesClauseIndex As Integer

       Dim meIndex As Integer

       Dim handlesSubstring As String

     

       handlesClauseIndex = line.IndexOf(handlesClause, 0, StringComparison.InvariantCultureIgnoreCase)

       If handlesClauseIndex >= 0 Then

     

          ' The simplest way to do this would be to use handlesSubstring.Replace() to replace all

          ' occurences of Me with MyBase, but we can't rely on the code being mixed case. Ohhh, it probably

          ' is, but ya never know.

          meIndex = 0

          handlesSubstring = line.Substring(handlesClauseIndex)

          meIndex = handlesSubstring.IndexOf(meStatement, meIndex, StringComparison.InvariantCultureIgnoreCase)

     

          Do While meIndex >= 0

             handlesSubstring = handlesSubstring.Substring(0, meIndex) & myBaseStatement & handlesSubstring.Substring(meIndex + meStatement.Length)

             meIndex = handlesSubstring.IndexOf(meStatement, meIndex, StringComparison.InvariantCultureIgnoreCase)

          Loop

          Return line.Substring(0, handlesClauseIndex) & handlesSubstring

       Else

          Return line

       End If

    End Function

     

    That should do it. I'll look into the other problem.

    Marc.

     

     

  • Monday, June 25, 2007 8:33 PMSoftware Pathologist Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    I'm not sure how it translates into VB but I was able to get the Property grid running at a higher level so I didn't have to create a BasicDesignSurface.  If you call Host.GetServices for ISelectionService you get an object that you can then dynamically wire to the SelectionChanged event.

     

    Either way you do it, supposing you can do what I describe in VB, you have to make sure that the event handler has access to the PropertyGrid instance you want to update. 

  • Wednesday, June 27, 2007 1:47 AMMalacki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Oh, yeah that's right. I assumed Anonymous had a custom design surface but you can do it from anywhere as long as you can get to the services container and have access to the propertygrid.

     

    I've gotten busy lately so I haven't had time write a sample on saving the loaded file back to disk, sorry. Basically you just need to do the following:

     

    1. Call the DesignerLoader's Flush() method;
    2. If any changes have been made, DesignerLoader will call the protected Write() method (which I left empty in the sample) with a code compile unit representing the form currently loaded;
    3. Use that code compile unit in a call to CodeDomProvider.GenerateCodeFromCompileUnit() to write it out to a file. You can either use the same provider you used to open the file if you're saving to the same language; otherwise create one of the proper type. 

    Note that Flush() won't call Write() unless a change has been made. Specifically, it'll look at the protected Modified property, which is set by DesignerLoader as it monitors PropertyChanged, ComponentAdded, ComponentRemoved,  and other events in the designer. If you want to force the flush, either override Flush() and set Modified = True before calling the base method, or provide a public method (ForceFlush(), ForceSave(), whatever) that sets the property and then calls Flush().

     

    I gotta say, though, that without some form of document or project management, loading a standalone code file into a designer will be tricky Anonymous. If you try loading a file with some non-VS component / control, or a reference to a non- .Net assembly, you won't be able to resolve it unless you have that same reference in your app. I don't know what type of environment you're planning to do this in so maybe that won't be an issue for you.

     

    At any rate, good luck with it Smile

    Marc.

  • Thursday, July 05, 2007 7:03 PMSoftware Pathologist Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Not sure if this should start a new thread or not but the parser we have been talking about doesn't handle the switch statement, at least not the version I have.

  • Friday, July 13, 2007 12:27 AMAnonymous5400 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Let's keep going in this thread... it's easy to find!  Anyway, what switch statement are you talking about?  Give us more info and maybe we can help out...

    I've been busy, and now have a working code generating version of DesignerLoader.  Here it is in VB:


    DesignerLoader.vb
    Imports ICSharpCode.NRefactory
    Imports System.CodeDom

    Imports System.CodeDom.Compiler
    Imports System.ComponentModel.Design
    Imports System.ComponentModel.Design.Serialization
    Imports System.IO
    Imports System.Text
    Imports Microsoft.CSharp

    Public Class DesignerLoader
        Inherits CodeDomDesignerLoader

        Private codeCompileUnit As CodeCompileUnit = Nothing

        ' Enum of supported language file extensions. These entries match corresponding SharpDevelop SupportedLanguage
        ' values.

        Private Enum SupportedLanguages
            CS
            VB
        End Enum



        ' Imports / using statements for supported languages.
        Dim importTokens As String() = {"using", "Imports"}

        ' Statement termination characters for supported languages.
        Dim statementTerminators As String() = {";", ""}

        Dim sourceFile As String

        Dim provider As CodeDomProvider

        Dim errors As String

    #Region " Construction / Initialization "

        ''' <summary>
        ''' Duh
        ''' </summary>
        ''' <param name="fileName">A code file to open.</param>
        ''' <remarks>If the file has matching associates, they will be loaded as well. See LoadReaders()
        ''' for details.</remarks>
        Public Sub New(ByVal fileName As String)

            Init(fileName)

        End Sub



        ''' <summary>
        ''' Saves the requested source files and gets a CodeDomProvider appropriate for the file type.
        ''' </summary>
        ''' <param name="fileName">Name of the file to load.</param>
        ''' <remarks></remarks>
        Private Sub Init(ByVal fileName As String)

            sourceFile = fileName

            provider = GetProvider(fileName)

        End Sub

    #End Region

    #Region " CodeDomDesignerLoader implementation / overrides "

        ''' <summary>
        ''' Returns a reference to our CodeDomProvider, which is specific to the type of file passed in
        ''' at initialization.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Protected Overrides ReadOnly Property CodeDomProvider() As System.CodeDom.Compiler.CodeDomProvider
            Get
                Return provider
            End Get
        End Property

        ''' <summary>
        ''' Parses the specified source file (and associated designer / partial files) with the help of SharpDevelop.
        ''' </summary>
        ''' <returns>A CodeCompileUnit representing the class(es) included in the source file.</returns>
        ''' <remarks></remarks>
        Protected Overrides Function Parse() As System.CodeDom.CodeCompileUnit

            Dim parser As SharpDevParser

            Dim mergeFileName As String = ""

            Dim reader As TextReader = Nothing

            Dim ccu As CodeCompileUnit = Nothing

            Dim readers As List(Of TextReader) = LoadReaders(sourceFile)



            If readers.Count > 0 Then

                mergeFileName = MergeFiles(readers)

                If mergeFileName.Length > 0 Then

                    reader = File.OpenText(mergeFileName)

                End If

            End If

            If reader IsNot Nothing Then

                Try

                    parser = New SharpDevParser()

                    ccu = parser.Parse(GetLanguageIndex(provider), reader, GetService(GetType(ITypeResolutionService)))

                Catch ex As Exception

                Finally

                    reader.Close()

                    Try

                        File.Delete(mergeFileName)

                    Catch ex As Exception

                    End Try

                End Try

            End If

            CloseReaders(readers)

            Return ccu

        End Function



        ''' <summary>
        ''' Returns an instance of a Type Resultion Service. We don't provide one ourselves... just return whatever the
        ''' LoaderHost gives us.
        ''' </summary>
        ''' <value>Whatever the LoaderHost gives us via GetService().</value>
        ''' <returns>Uhhh, I just said that... Whatever the LoaderHost gives us via GetService().</returns>
        ''' <remarks></remarks>
        Protected Overrides ReadOnly Property TypeResolutionService() As System.ComponentModel.Design.ITypeResolutionService

            Get

                Return LoaderHost.GetService(GetType(TypeResolutionService2))

            End Get

        End Property



        ''' <summary>
        ''' Supporting function for the <see cref="GetCode">GetCode</see> function
        ''' </summary>
        ''' <param name="unit">CodeCompileUnit to write to file.</param>
        ''' <remarks></remarks>
        Protected Overrides Sub Write(ByVal unit As System.CodeDom.CodeCompileUnit)

            codeCompileUnit = unit

        End Sub

        Public Function GetCode(ByVal context As String) As String
            Me.Modified = True
            Flush()

            Dim o As New CodeGeneratorOptions()

            o.BlankLinesBetweenMembers = True
            o.BracingStyle = "C"
            o.ElseOnClosing = False
            o.IndentString = "    "
            If context = "C#" Then
                Dim swCS As New StringWriter()
                Dim cs As New CSharpCodeProvider()

                cs.GenerateCodeFromCompileUnit(codeCompileUnit, swCS, o)
                Dim code As String = swCS.ToString()
                swCS.Close()
                Return code
            ElseIf context = "VB" Then
                Dim swVB As New StringWriter()
                Dim vb As New VBCodeProvider()

                vb.GenerateCodeFromCompileUnit(codeCompileUnit, swVB, o)
                Dim code As String = swVB.ToString()
                swVB.Close()
                Return code
            End If

            Return [String].Empty
        End Function

    #End Region

    #Region " Nitty gritty "

        ''' <summary>
        ''' Attempts to load all partial class definitions given a source code file.
        ''' </summary>
        ''' <param name="sourceFile">One of the source files in a set of related files</param>
        ''' <returns>A list of TextReaders, each loaded with a file assumed to belong to the same
        ''' class definition. This list will be empty (count = 0) if an error occurs or the specified
        ''' file is not found.</returns>
        ''' <remarks>Partial classes are supported but will only be located if all class files reside
        ''' in the same folder and follow the same naming pattern as the file specified. The naming pattern
        ''' looked for is {some name}{optional ".*"}.{language extension}
        '''
        ''' Eg. If Form1.Designer.vb is specified, then its code-behind file Form1.vb would also be loaded,
        ''' as would a file named Form1.partial2.vb, etc.
        ''' </remarks>
        Private Function LoadReaders(ByVal sourceFile As String) As List(Of TextReader)

            Dim basePath As String = Path.GetDirectoryName(sourceFile)

            Dim fileExtension As String = Path.GetExtension(sourceFile)

            Dim baseName As String = GetBaseFileName(sourceFile)

            Dim files() As String = Directory.GetFiles(basePath, baseName & "*" & fileExtension)

            Dim validFiles As List(Of String) = New List(Of String)

            Dim readerList As List(Of TextReader) = New List(Of TextReader)

            Dim regEx As RegularExpressions.Regex = New RegularExpressions.Regex("(" & baseName & ")(\..+)*(" & fileExtension & ")")



            Try

                For Each file As String In files

                    If regEx.IsMatch(file) Then

                        validFiles.Add(file)

                    End If

                Next

                For Each name As String In validFiles

                    readerList.Add(File.OpenText(name))

                Next

            Catch ex As Exception

                Trace.WriteLine("Exception opening source files - " & ex.Message)

                CloseReaders(readerList)

                readerList.Clear()

            End Try

            Return readerList

        End Function


        ''' <summary>
        ''' Gets the first part of a source code file name (ie. everything before the first ".").
        ''' </summary>
        ''' <param name="fullPath">Full path to the file to evaluate.</param>
        ''' <returns>Base file name.</returns>
        ''' <remarks>Give a file named "Form1.Designer.vb", this function would return "Form1".</remarks>
        Private Function GetBaseFileName(ByVal fullPath As String) As String

            Dim baseName As String = Path.GetFileNameWithoutExtension(fullPath)

            Dim theDot As Integer = baseName.IndexOf(".")



            If theDot > 0 Then

                baseName = baseName.Substring(0, theDot)

            End If

            Return baseName

        End Function



        ''' <summary>
        ''' Merges related code files into a temporary file.
        ''' </summary>
        ''' <param name="fileReaders">List of open TextReaders, each loaded with a related code file.</param>
        ''' <returns>Path to the merged file.</returns>
        ''' <remarks>The resulting file will have all Import / using statements from all files moved to the top.
        ''' Parsers like it that way.
        ''' </remarks>
        Private Function MergeFiles(ByVal fileReaders As List(Of TextReader)) As String

            Dim sourceCode As String = ""

            Dim importList As List(Of String)

            Dim importToken As String

            Dim statementTerminator As String

            Dim mergeFile As String = ""

            Dim language As Integer = GetLanguageIndex(provider)



            If language < 0 Then Return ""



            Try

                importList = New List(Of String)

                importToken = importTokens(language)

                statementTerminator = statementTerminators(language)



                For Each reader As TextReader In fileReaders

                    sourceCode += PullCodeExtractImports(reader, importToken, statementTerminator, importList)

                Next



                importList.Sort()

                For index As Integer = 0 To importList.Count - 1

                    importList(index) = importToken & " " & importList(index)

                Next



                sourceCode = String.Join(statementTerminator & vbCrLf, importList.ToArray) & vbCrLf & sourceCode

                If sourceCode.Length > 0 Then

                    mergeFile = Path.GetTempFileName()

                    File.AppendAllText(mergeFile, sourceCode)

                End If



            Catch ex As Exception

                Trace.WriteLine(ex.ToString)

                If mergeFile.Length > 0 Then

                    Try

                        File.Delete(mergeFile)

                    Catch ex2 As Exception

                    End Try

                End If

                mergeFile = ""

            End Try

            Return mergeFile

        End Function

        ''' <summary>
        ''' Replaces a VB Handles clause ("MyForm_Load(...) Handles Me.Load") with one that is compatible with
        ''' SharpDev's parser ("Handles MyBase.Load").
        ''' </summary>
        ''' <param name="line">Source code line to work on.</param>
        ''' <returns>Source line with a modified Handles clause.</returns>
        ''' <remarks>A Handles clause can include a list of events being handled, so we have to find the
        ''' start of the clause and work through the remainder of the string to find the "Me" references.</remarks>
        Private Function ReplaceHandlesClause(ByVal line As String) As String

            Dim handlesClause As String = "Handles"
            Dim meStatement As String = "Me"
            Dim myBaseStatement As String = "MyBase"
            Dim handlesClauseIndex As Integer
            Dim meIndex As Integer
            Dim handlesSubstring As String
            handlesClauseIndex = line.IndexOf(handlesClause, 0, StringComparison.InvariantCultureIgnoreCase)
            If handlesClauseIndex >= 0 Then
                ' The simplest way to do this would be to use handlesSubstring.Replace() to replace all
                ' occurences of Me with MyBase, but we can't rely on the code being mixed case. Ohhh, it probably
                ' is, but ya never know.
                meIndex = 0
                handlesSubstring = line.Substring(handlesClauseIndex)
                meIndex = handlesSubstring.IndexOf(meStatement, meIndex, StringComparison.InvariantCultureIgnoreCase)
                Do While meIndex >= 0
                    handlesSubstring = handlesSubstring.Substring(0, meIndex) & myBaseStatement & handlesSubstring.Substring(meIndex + meStatement.Length)
                    meIndex = handlesSubstring.IndexOf(meStatement, meIndex, StringComparison.InvariantCultureIgnoreCase)
                Loop
                Return line.Substring(0, handlesClauseIndex) & handlesSubstring
            Else
                Return line
            End If
        End Function

        ''' <summary>
        ''' Pulls the code from a text file, excluding Import / using statements.
        ''' </summary>
        ''' <param name="reader">Reader opened with the source file to read.</param>
        ''' <param name="importToken">Language-specific token for the Imports / using statement.</param>
        ''' <param name="statementTerminator">Language-specific code statement terminator. ";" in C#, empty string in VB.</param>
        ''' <param name="importList">Reference to a list of import statements. Imports found in the current file will be added
        ''' to this list.</param>
        ''' <returns>The resulting code, as a string, minus any import statements.</returns>
        ''' <remarks></remarks>
        Private Function PullCodeExtractImports(ByVal reader As TextReader, ByVal importToken As String, ByVal statementTerminator As String, ByRef importList As List(Of String)) As String

            Dim line As String = Nothing
            Dim resultString As String = ""
            Dim strippedLine As String

            line = reader.ReadLine

            Do Until line Is Nothing

                strippedLine = line.Trim.TrimEnd(New Char() {statementTerminator})

                If strippedLine.ToLower.StartsWith(importToken.ToLower) AndAlso _
       importList.IndexOf(strippedLine.Substring(importToken.Length + 1)) < 0 Then

                    importList.Add(strippedLine.Substring(importToken.Length + 1))
                Else
                    ' Compatibility work-around: #Develop's parser doesn't like VB's "Handles Me.xxx" event handler
                    ' statement. The fix is to replace "Handles Me." with "Handles MyBase." Fortunately the latter
                    ' statement is also valid in VS
                    resultString += ReplaceHandlesClause(line) & vbCrLf
                End If

                line = reader.ReadLine
            Loop
            Return resultString
        End Function



        ''' <summary>
        ''' Returns a SupportedLanguages value given a language-specific CodeDomProvider.
        ''' </summary>
        ''' <param name="provider">The CodeDomProvider for the language to query.</param>
        ''' <returns>One of the SupportedLanguages values, -1 if CodeDomProvider is not supported by this
        ''' class</returns>
        ''' <remarks></remarks>
        Private Function GetLanguageIndex(ByVal provider As CodeDomProvider) As Integer

            Try

                Return CInt(System.Enum.Parse(GetType(SupportedLanguages), provider.FileExtension, True))

            Catch ex As Exception

                Return -1

            End Try

        End Function



        ''' <summary>
        ''' Closes a bunch of text readers.
        ''' </summary>
        ''' <param name="readers">List of open TextReaders to close.</param>
        ''' <remarks></remarks>
        Private Sub CloseReaders(ByVal readers As List(Of TextReader))

            For Each reader As TextReader In readers

                Try

                    reader.Close()

                Catch ex As Exception

                End Try

            Next

        End Sub



        ''' <summary>
        ''' Returns a CodeDomProvider appropriate for the file specified, based on its file extension.
        ''' </summary>
        ''' <param name="fileName">Code file whose provider is to be loaded.</param>
        ''' <returns>A CodeDomProvider appropriate for the file specified if successful,
        ''' Nothing otherwise.</returns>
        ''' <remarks></remarks>
        Private Function GetProvider(ByVal fileName As String) As CodeDomProvider

            Try

                Dim language As String = CodeDomProvider.GetLanguageFromExtension(Path.GetExtension(fileName))

                Return CodeDomProvider.CreateProvider(language)

            Catch ex As Exception

                Trace.WriteLine(ex.ToString)

                Return Nothing

            End Try

        End Function

    #End Region

    End Class



    Also (this is addressing Malacki's post), isn't there a way to dynamically add references to your program at runtime?  Would this solve the problem?  If it's possible to do this, when the user opens a form, the program could use a Try/Catch block around the view = ds.view line (which seems to be the make-or-break statement for the designer), and show an error message allowing the user to specify the appropriate references.  In the end, I will be using a project management system, but I think it would be a really great feature to load a standalone file into a designer.
  • Saturday, July 21, 2007 11:53 PMAnonymous5400 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Anyone??