The CodeModel and FileCodeModel work in concert to explore the code of a project. They can be used in an add-in to copy selected controls and their respective event code from one form to another. One of the most useful functionalities in an add-in is the ability to copy a group of selected controls and the event code associated with them.
The CCopyControlsAndCode Class contains all of the code, with the exception of some utility parsing functions, that are needed to copy the controls to the clipboard and to concatenate the event code into a StringBuilder for inserting into another forms code window. It also contains the code to paste the controls and the code into another form. This class is the core of the functionality, but it would have to have a UI in the add-in to direct the user on the copying and pasting of the controls. This class was taken from NetCommander, which provides this functionaliy as one of the over 50 time-saving features that are added to the Visual Studio .NET IDE. You can download a fully feature copy for a free 30-day trial by clicking here.
The constructor for this class requires that the applicationObject of the IDE be passed to it. I will intermingle a description of the code, where it is needed, with the code of the class.
Imports System.Windows.Forms Imports EnvDTE Imports Extensibility Imports System.Text Imports System.Math Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Drawing Public Class CCopyControlsAndCode Private oVB As EnvDTE.DTE Private msEventCode As String Private sList As String = String.Empty |
| Friend Function CopyControlsAndCode(ByRef oFrm As frmCopyControlsAndCode) As Boolean Dim aComps As New ArrayList() Dim j As Integer Dim oCM As New CCodeModel(oVB, modMain.miPrj) Dim oUtil As New CUtilities(oVB) Dim sbCodeOut As New StringBuilder() Dim IC As IComponent Dim c As Component Try ' get a list of the selected components Dim fdHost As IDesignerHost fdHost = CType(oVB.ActiveWindow.Object, IDesignerHost) Dim sel As ISelectionService sel = CType(fdHost.GetService(Type. _ GetType("System.ComponentModel.Design.ISelectionService,System")), _ System.ComponentModel.Design.ISelectionService) If sel.GetSelectedComponents.Count = 0 Then MsgBox("No controls selected", vbExclamation) Exit Function End If Dim cmp As Component ' build arraylist of control names For Each cmp In sel.GetSelectedComponents aComps.Add(cmp.Site.Name) Next ' now destroy the designer object as we no ' longer need it. cmp = Nothing sel = Nothing fdHost = Nothing ' now having the list of control names in the array ' attempt to open the code window so we can examine ' the text via TextSelection object ' activate the code window ' first, copy the controls to the clipboard oVB.ExecuteCommand("Edit.Copy") Dim oWin As New CWindows(oVB) For j = 0 To aComps.Count - 1 ' iCmpCount - 1 ' get any events for this control name sbCodeOut.Append(GetAllEventsForControl(aComps(j))) Next j ' place the concatenated code in module level ' variable so that the paste method can see it Me.msEventCode = sbCodeOut.ToString Return True Catch ex As System.Exception MsgBox("Error in CopyControlsAndCode: " & _ ex.Message & "; Ensure that the form designer is active window.", _ MsgBoxStyle.Exclamation) Return False End Try End Function |
| Friend Overloads Function GetAllEventsForControl( _ ByVal sControlName As String) As String '` Returns all event code for a specified control. '` At end returns the string from '` the stringbuilder, which is all of the code for '` the passed control name. Dim j As Integer Dim sLine As String Dim sb As New StringBuilder() Try Dim pi As ProjectItem = oVB.ActiveWindow.ProjectItem Dim sModule As String = pi.Name ' dont chase the member list if we already have it If sList.Length = 0 Then sList = Me.GetMembersList(1, _ Replace(Replace(sModule, ".vb", ""), ".cs", ""), _ sControlName & "_") End If ' return string from GetMembersList: ' Class: frmName Kind vsCMElementClass ' Class: frmName,Method: sControlNameName,Start: 211, Lines: 21 If sList Is Nothing Then Exit Function Dim iPtr As Integer = InStr(sList, sControlName) If iPtr > 0 Then ' get the second line Dim oUtil As New CUtilities(oVB) Dim sCode As String With oUtil Dim iNL As Integer = MLCount(sList) If iNL < 2 Then Return "" End If ' get name and strip unneeded tokens ' Class: frmName,Method: membername, Start: 211, Lines: 21 For j = 1 To iNL - 1 sLine = .MemoLine(j) If Not sLine Is Nothing AndAlso sLine.Length > 0 Then sLine = Replace(sLine, "Class:", "") sLine = Replace(sLine, "Method:", "") sLine = Replace(sLine, "Start:", "") sLine = Replace(sLine, "Lines:", "") ' discard module name Dim i As Integer = 1 Dim sTrash As String = _ .GetNextCommaDelimitedToken(i, sLine) Dim sName As String = _ Trim(.GetNextCommaDelimitedToken(i, sLine)) Dim iStart As Integer = _ CInt(Val(.GetNextCommaDelimitedToken(i, sLine))) Dim iLines As Integer = _ CInt(Val(.GetNextCommaDelimitedToken(i, sLine))) sCode = Me.GetEventCode(iStart, iStart + iLines - 1) sb.Append(sCode & vbCrLf) End If Next j Return sb.ToString End With Else Return "" End If Catch ex As System.Exception MsgBox("GetAllEventsForControl: " & ex.Message) End Try End Function |
Public Function GetMembersList(ByVal PI As Integer, _ Optional ByVal rsCompName As String = "", _ Optional ByVal rsMemberName As String = "") _ As String ' this method and its helper sub GetMethods ' will list all of the members in the project. ' If passed a rsCompName, only that component will ' be searched. ' If passed a rsMemberName, only that name will be ' reported as found. This feature is used when we ' want to know if a member is already in a component. ' In that case, the caller will pass both the component ' name and the membername and if list is not blank on ' return, the member was found. Dim cm As CodeModel Dim bDone As Boolean = False Dim prj As Project = oVB.Solution.Projects.Item(1) cm = prj.CodeModel ' Look for all the namespaces and classes in the ' project. Dim list As String Dim ce As CodeElement On Error Resume Next Cursor.Current = Cursors.WaitCursor For Each ce In cm.CodeElements If (TypeOf ce Is CodeClass Or TypeOf ce Is CodeNamespace) Then ' See if that namespace or class contains ' other classes. If (rsCompName = "") Or _ (ce.Name.ToUpper = rsCompName.ToUpper) Or _ (TypeOf ce Is CodeNamespace) _ Then GetMembers(ce, list, rsMemberName, bDone, rsCompName) If bDone Then Exit For End If End If Next Cursor.Current = Cursors.Default Return list End Function |
| Sub GetMembers(ByVal ct As CodeElement, _ ByRef list As String, _ ByVal rsMemberName As String, _ ByRef bDone As Boolean, _ ByVal rsCompName As String) ' ct could be a namespace or a class. ' Add it to the list ' if it is a class. Static sClass As String Dim sp As Integer Dim ep As Integer On Error Resume Next If (TypeOf ct Is CodeClass) Then If ct.Name.ToUpper = rsCompName.ToUpper Or _ rsCompName = "" Then list &= "Class: " & ct.Name & " Kind: " & _ ct.Kind.ToString & vbCrLf sClass = ct.Name Else Exit Sub End If ElseIf (TypeOf ct Is CodeNamespace) Then If ct.Name = "Microsoft" Then bDone = True Exit Sub End If sp = ct.StartPoint.Line ep = ct.EndPoint.Line list &= "NameSpace: " & ct.Name & _ ", Start: " & sp.ToString & _ ", Lines: " & (ep - sp + 1).ToString & vbCrLf ElseIf (TypeOf ct Is CodeFunction) Then If (rsMemberName = "") Or _ (rsMemberName = ct.Name) Then sp = ct.StartPoint.Line ep = ct.EndPoint.Line list &= "Class: " & sClass & _ ", Method: " & ct.Name & _ ", Start: " & sp.ToString & _ ", Lines: " & _ (ep - sp + 1).ToString & vbCrLf ElseIf rsMemberName.EndsWith("_") Then ' we were suppled with a control name and "_" which ' means that we are looking for all events for a ' specified control, regardless of the event type If ct.Name.StartsWith(rsMemberName) Then sp = ct.StartPoint.Line ep = ct.EndPoint.Line list &= "Class: " & sClass & _ ", Method: " & ct.Name & _ ", Start: " & sp.ToString & _ ", Lines: " & _ (ep - sp + 1).ToString & vbCrLf End If End If ElseIf (TypeOf ct Is CodeVariable) Then If (rsMemberName <> "" And ct.Name = rsMemberName) Or _ rsMemberName = "" Then sp = ct.StartPoint.Line list &= "Class: " & sClass & _ ", Variable: " & ct.Name & _ ", Start: " & sp.ToString & vbCrLf End If Else sp = ct.StartPoint.Line If ct.Name = "Microsoft" Then bDone = True Exit Sub End If list &= "Class: " & sClass & _ " Element: " & ct.Name & _ " StartLine: " & sp.ToString & vbCrLf End If ' See if there are any nested namespaces or ' classes that might ' contain other classes. Dim ce As CodeElement For Each ce In ct.Members If (TypeOf ce Is CodeNamespace) Or _ (TypeOf ce Is CodeClass) Or _ (TypeOf ce Is CodeFunction) Or _ (TypeOf ce Is CodeVariable) Then GetMembers(ce, list, rsMemberName, bDone, rsCompName) If bDone Then Exit For End If Next End Sub |
| Friend Function GetEventCode(ByVal StPt As Integer, _ ByVal EndPt As Integer) As String ' new code using filecodemodel Dim pi As ProjectItem = oVB.ActiveWindow.ProjectItem Dim fcm As FileCodeModel = pi.FileCodeModel If modMain.mPrj Is Nothing Then modMain.GetActiveSolutionProject() End If Dim rets As String = GetCodeForEventMethod(pi, StPt, EndPt) Return rets End Function |
| Function GetCodeForEventMethod(ByRef roPI As ProjectItem, _ ByVal iStPt As Integer, _ Optional ByVal iEndPt As Integer = 1) _ As String ' returns code for a module which is pointed ' to by projectitem, start and end pts. ' handles VB and C#, with/without namespaces. Dim pi As ProjectItem = roPI Dim filecm As FileCodeModel = pi.FileCodeModel Dim ce As CodeElement Dim i As Integer Try If filecm.CodeElements.Count = 0 Then Exit Function For Each ce In filecm.CodeElements If ce.Kind = vsCMElement.vsCMElementNamespace Then Dim cns As CodeNamespace = ce Dim cl As CodeClass For Each cl In cns.Members Dim cetype As CodeType = CType(cl, CodeType) Dim ep As EditPoint = _ cetype.GetStartPoint(vsCMPart.vsCMPartHeader).CreateEditPoint Dim sTextLine As String Dim sb As New Text.StringBuilder() ep.MoveToLineAndOffset(iStPt, 1) ' loop to get all lines in the method ' use for instead of do so c# will work For i = 1 To iStPt - iEndPt 'Do sTextLine = ep.GetText(ep.LineLength) sb.Append(sTextLine & vbCrLf) ep.LineDown() Next i 'Loop Return sb.ToString Next Else Dim celt As CodeElement = ce Dim cetype As CodeType = CType(celt, CodeType) Dim ep As EditPoint = _ cetype.GetStartPoint(vsCMPart.vsCMPartHeader).CreateEditPoint Dim sTextLine As String Dim sb As New Text.StringBuilder() ep.MoveToLineAndOffset(iStPt, 1) ' loop to get all lines in the method ' use for instead of do so c# will work For i = 0 To iEndPt - iStPt 'Do sTextLine = ep.GetText(ep.LineLength) sb.Append(sTextLine & vbCrLf) ep.LineDown() Next i 'Loop Return sb.ToString End If Next Catch ex As System.Exception MsgBox("Error in CFileCodelModel.GetCodeForMethod: " & ex.Message) End Try End Function |
| Friend Sub PasteControlsAndCode() 'The copy function should have placed the event ' code in the variable msEventCode. This method ' will simply paste the controls from the clipboard ' to the current form designer and then add the code ' to the end of the code module for the form. ' paste the controls to the form Try oVB.ExecuteCommand("Edit.Paste") Application.DoEvents() oVB.ExecuteCommand("View.ViewCode") Application.DoEvents() ' paste the code to the window Dim oUtil As New CUtilities(oVB) oUtil.AddMethodToEndOfDocument(msEventCode) Catch ex As System.Exception MsgBox(ex.ToString) End Try End Sub Public Sub New(ByRef roVB As EnvDTE.DTE) oVB = roVB End Sub End Class |