| < Free Open Study > |
|
If you or other developers within your team are using more than one of the languages supplied with Visual Studio .NET, you certainly want your add-in functionality to support the developers, regardless of the language in which they may be programming. Additionally, as I have just demonstrated, a developer may be using multiple languages in a single solution. In this section you will learn how to enhance some of the add-in features you have previously developed so that they can handle multiple languages.
In this section, you'll make a copy of the code from Chapter 7. Next, you'll want to reorganize some of the code that you've already developed. You'll move the reusable functions from the Connect class of MyFirstAddin1 that are not normally associated with the Connect class. You would have done this earlier in the book, but I wanted to keep the code as simple as possible. The features of the add-in have grown, both in number and complexity, and now it is time to get organized.
The first thing you'll do is create two new classes named CodeManipulation and Utilities. You'll move all of the "business logic" code from the Connect.vb class to the CodeManipulation class. Next, you'll move the utility functions to the Utilities class. Once you do this, you'll immediately have to make some changes. Because all of the methods that are being moved will now be in separate classes, the code that calls them will have to instantiate an instance of the respective class before the methods can be called. Another problem that arises is that the application object (oVB) must be made available to the new classes so that their respective methods can make references to the IDE instance and to its automation objects.
Since the advent of Visual Studio .NET brought constructors and destructors (New and Finalize) to Visual Basic classes, it is possible to pass parameters to the constructor method New. To add a parameter to the New method after the class is added to the project, you should click the drop-down list of methods. In the drop-down list, select the New method. Visual Studio will create a method that looks like the following code snippet:
Public Sub New() oVB = roVB End Sub
Now you can add one or more parameters to the method definition line, as shown in the code snippet in Listing 10-6. In this case, you want to pass the application object (oVB) to the class when it is instantiated.
Listing 10-6: Sample Constructor Usage
Imports EnvDTE Imports Extensibility Public Class CodeManipulation Dim oVB As DTE ' any number of lines of code that constitute the ' code for the class Public Sub New(roVB As DTE) oVB = roVB End Sub End Class
To instantiate the class and pass the application object to it, you'll use the following line of code:
Dim oCM As New CodeManipulation(oVB)
When the object is instantiated by the previous line of code, the New method is called and the application object is set into a module-level variable of the class. This will allow any code anywhere in the class to have access to the extensibility objects in the current instance of the Visual Studio IDE.
At this point, a word to the wise is in order. Because methods of the CodeManipulation object will be calling methods of the Utilities class, that class will have to be instantiated by the CodeManipulation class. You might think that it would be a neat trick to do this by the code highlighted in boldface in Listing 10-7. It is a great idea, but it won't work. The code compiles with no problem, but at execution time the Dim of oUtil executes before the constructor is executed. Consequently, the Utilities class gets instantiated and the new object is set into the variable oUtil, but the variable oVB is set to Nothing at the time of the instantiation of the new object. Again, this is true because the constructor for the CodeManipulation class has not yet been called.
Listing 10-7: Sample Constructor Usage
Imports EnvDTE Imports Extensibility Public Class CodeManipulation Dim oVB As DTE Dim oUtil As New Utilities(oVB) ' any number of lines of code that constitute the ' code for the class Public Sub New(roVB As DTE) oVB = roVB End Sub End Class
In order to pass the application object variable after it has been set in the oUtil object, you must change the code to appear as shown in Listing 10-8. Now, when the new instance of the Utilities class is instantiated, the application object variable has been properly set and can be safely passed.
Listing 10-8: Sample Constructor Usage
Imports EnvDTE Imports Extensibility Public Class CodeManipulation Dim oVB As DTE Public Sub SomeMethod() Dim sText As String Dim oUtil As New Utilities(oVB) SText = "some text" oUtil.PutCodeBack(sText) End Sub Public Sub New(roVB As DTE) oVB = roVB End Sub End Class
You have learned that a developer could be working with code in one or more languages in one instance of the Visual Studio IDE. To make the add-in features that you have developed work in multiple languages, you must determine the type of file that is represented by the active document. You are going to develop two different methods. The first, GetFileType, will return a Short value denoting the file type. In some scenarios you will use it to determine how or even if you will attempt to process a file. At other times, such as in commenting a block of code, you will use the file type to determine the comment character. That is where the second method, GetCommentChar, will be used.
Tip | The EndsWith method of the string class is a neat new feature of the .NET Framework. It returns True when the string object ends with the quoted expression passed to EndsWith. Note that the method is case sensitive. |
In the GetFileType method, shown in Listing 10-9, you are simply testing for the source file extensions that you want to attempt to handle. In the C++ extensibility model, you can use the Language property of the Document object. However, the EnvDTE.Constants collection does not include constants for Visual Basic and C# file types.
Listing 10-9: Determining the File Type
Function GetFileType(ByVal doc As Document) As Integer ' Pass this function the document that you wish ' to get information for. ' Return value: ' 0 Unknown file type ' 1 C-related file, this includes .c, .cpp, .cxx, '.h, .hpp, .hxx ' 2 Java-related file, this includes .jav, .java ' 3 ODL-style file, .odl, .idl ' 4 Resource file, .rc, .rc2 ' 7 Def-style file, .def ' 8 VB, .vb ' 9 C#, .cs ' 10 Batch, .bat Dim pos As Integer Dim ext As String ext = doc.Name.ToUpper If ext.EndsWith(".rc") Or ext.EndsWith(".rc2") Then Return 4 ElseIf ext.EndsWith(".CPP") Or _ ext.EndsWith(".C") Or _ ext.EndsWith(".H") Or _ ext.EndsWith(".HPP") Then Return 1 ElseIf ext.EndsWith(".JAV") Or ext.EndsWith(".JAVA") Then Return 2 ElseIf ext = ".def" Then Return 7 ElseIf ext.EndsWith(".vb") Then Return 8 ElseIf ext.EndsWith(".cs") Then Return 9 ElseIf ext.EndsWith(".bat") Then Else Return 0 End If End Function
Once you have called GetFileType, you can use the return value to call the GetCommentChars to get the comment character for the respective language, as shown in Listing 10-10.
Listing 10-10: GetCommentChars
Public Function GetLanguageCommentChars(ByVal FileType _ As Short) As String Select Case FileType Case 1, 2, 9 : Return "//" ' C languages and java Case 8, 6 : Return "'*" ' VB Case 4,10 : Return ";" ' rc, bat files End Select End Function
In case you just want to get the comment character(s), regardless of file type, you can use the method I have developed that is shown in Listing 10-11. If the document type is not recognized by the function, an empty string is returned.
Listing 10-11: GetCommentCharForDoc Method
Function GetCommentCharForDoc(Optional ByVal doc As Document = Nothing) _ As String If (doc Is Nothing) Then doc = oVB.ActiveDocument End If Dim ext As String = doc.Name If (ext.EndsWith(".cs")) Then Return "//" ElseIf (ext.EndsWith(".cpp")) Then Return "//" ElseIf (ext.EndsWith(".h")) Then Return "//" ElseIf (ext.EndsWith(".vb")) Then Return "'*" ElseIf (ext.EndsWith(".idl")) Then Return "//" Else Return "" End If End Function
Now that you have functions that will tell you the type of file residing in the Active Document, you can modify the methods of the CodeManipulation class. I have divided the code for this class into two listings. Listing 10-12 contains all of the methods that have to do with block commenting and uncommenting. These methods have been enhanced to handle code manipulation for Visual Basic, C#, and C++. The changes made to provide this new functionality are highlighted in boldface.
Listing 10-12: CodeManipulation Class Commenting Methods
Imports Microsoft.Office.Core Imports Extensibility Imports System.Runtime.InteropServices Imports EnvDTE Public Class CodeManipulation Shared UserName As String = "Les Smith" Shared TodaysDate As String Public oVB As DTE Const UnknownDoc = "Unrecognizible document type." Const DocVB = 8 Const DocCSharp = 9 Const DocCPP = 1 Private sCommentChar As String Public Sub BlockComment() Dim iNL As Integer ' number of lines in block Dim sIN As String ' input selection Dim sOUT As String ' commented output Dim i As Integer Dim n As Short ' number of chars before first non ' blank in first line Dim s As String Dim oUtil As New Utilities(oVB) Try sCommentChar = oUtil.GetCommentCharForDoc(oVB.ActiveDocument) If Len(sCommentChar) = 0 Then MsgBox(UnknownDoc) Exit Sub End If ' Get selected text from active window sIN = oUtil.GetCodeFromWindow() ' ensure the user selected something If sIN.Length < 1 Then MsgBox("Please select block to be commented.", _ vbExclamation, "BlockComment") Exit Sub End If ' get the number of lines in the text iNL = oUtil.MLCount(sIN, 0) ' comment the block For i = 1 To iNL s = oUtil.MemoLine(sIN, 0, i) If i = 1 Then n = oUtil.CountSpacesBeforeFirstChar(s) sOUT = CType(IIf(n = 0, "", Space(n)), String) & _ sCommentChar & " Block Commented by " & _ UserName & " on " & _ TodaysDate & vbCrLf End If sOUT = sOUT & Space(n) & sCommentChar & s & vbCrLf Next i ' now end the block sOUT = sOUT & CType(IIf(n = 0, "", Space(n)), String) & _ sCommentChar & " End of Block Commented by " & _ UserName & vbCrLf ' now put the code back oUtil.PutCodeBack(sOUT) Catch e As System.Exception MsgBox("Error: " & e.Message, vbCritical, "BlockComment") End Try oUtil = Nothing End Sub Public Sub BlockDelete() ' Insert a deletion comment block around a block ' that is about to be deleted Dim sCC As String Dim sText As String Dim sLine As String Dim i As Long Dim nL As Integer Dim sTmpText As String Dim liCnt As Integer Dim oUtil As New Utilities(oVB) ' get the selected code from the code window Try sCommentChar = _ oUtil.GetCommentCharForDoc(oVB.ActiveDocument) If Len(sCommentChar) = 0 Then MsgBox(UnknownDoc) Exit Sub End If sText = oUtil.GetCodeFromWindow() If Trim$(sText) = "" Then MsgBox("No deletion text selected!") Exit Sub End If ' we have the text that is to be deleted ' comment it to delete it sTmpText = "" nL = oUtil.MLCount(sText, 0) For i = 1 To nL sLine = oUtil.MemoLine(sText, 0, i) If i = 1 Then liCnt = oUtil.CountSpacesBeforeFirstChar(sLine) End If sTmpText = sTmpText & IIf(liCnt > 0, Space(liCnt), "") _ & sCommentChar & " " & sLine & vbCrLf Next i sCC = IIf(liCnt > 0, Space(liCnt), "") & sCommentChar & _ " Block Deleted by " & UserName & " on " & _ TodaysDate & vbCrLf sCC = sCC & sTmpText sCC = sCC & IIf(liCnt > 0, Space(liCnt), "") & _ sCommentChar & _ " End of Block Deleted by " & UserName & " on " & _ TodaysDate & vbCrLf oUtil.PutCodeBack(sCC) Catch e As System.Exception MsgBox("Error in Block Delete: " & e.Message) End Try oUtil = Nothing Exit Sub End Sub Public Sub BlockChange() Dim sText As String Dim sCC As String Dim liCnt As Integer Dim lsLine As String Dim oUtil As New Utilities(oVB) Try sCommentChar = _ oUtil.GetCommentCharForDoc(oVB.ActiveDocument) If Len(sCommentChar) = 0 Then MsgBox(UnknownDoc) Exit Sub End If sText = oUtil.GetCodeFromWindow() If Trim$(sText) = "" Then MsgBox("No change text selected!") Exit Sub End If liCnt = oUtil.MLCount(sText, 0) If liCnt > 0 Then lsLine = oUtil.MemoLine(sText, 0, 1) liCnt = oUtil.CountSpacesBeforeFirstChar(lsLine) Else liCnt = 0 End If sCC = vbCrLf & IIf(liCnt > 0, Space(liCnt), "") & _ sCommentChar & _ " Block Changed by " & UserName & " on " & _ TodaysDate & vbCrLf sCC = sCC & sText & vbCrLf sCC = sCC & IIf(liCnt > 0, Space(liCnt), "") & _ sCommentChar & _ " End of Block Changed by " & UserName & " on " & _ TodaysDate & vbCrLf & vbCrLf oUtil.PutCodeBack(sCC) Exit Sub Catch e As System.Exception MsgBox("Error in Block Change: " & e.Message) End Try oUtil = Nothing End Sub Public Sub BlockUnComment() Dim iNL As Integer ' number of lines in block Dim sIN As String ' input selection Dim sOUT As String ' commented output Dim i As Integer Dim n As Short ' number of chars before first non blank ' in first line Dim n2 As Short ' nbr chars before first nb char ' subsequent lines Dim s As String Dim lsCD As String Dim oUtil As New Utilities(oVB) Try sCommentChar = _ oUtil.GetCommentCharForDoc(oVB.ActiveDocument) If Len(sCommentChar) = 0 Then MsgBox(UnknownDoc) Exit Sub End If lsCD = sCommentChar & " " ' Get selected text from active window sIN = oUtil.GetCodeFromWindow() ' ensure the user selected something If sIN.Length < 1 Then MsgBox("Please select block to be commented.", _ vbExclamation, "BlockComment") Exit Sub End If ' get the number of lines in the text iNL = oUtil.MLCount(sIN, 0) ' comment the block For i = 1 To iNL s = oUtil.MemoLine(sIN, 0, i) ' look for commented lines Select Case True Case Left(Trim(s), 8) = sCommentChar & " Block" ' comment header, dont write to output Case Left(Trim(s), 15) = sCommentChar & _ " End of Block" ' comment footer, don't write to output Case Left(Trim(s), 3) = lsCD sOUT = sOUT & Replace(s, lsCD, "", , 1) & vbCrLf Case Left(Trim(s), 1) = ""' sOUT = sOUT & Replace(s, lsCD, "", , 1) & vbCrLf End Select Next i ' now put the code back oUtil.PutCodeBack(sOUT) Catch e As System.Exception MsgBox("Error: " & e.Message, vbCritical, _ "BlockUnComment") End Try oUtil = Nothing End Sub Public Sub New(ByVal roVB As DTE) oVB = roVB ' set up today's date for use in all methods TodaysDate = Format(Now(), "Long Date") End Sub End Class
The highlighted changes accomplish the following things:
Instantiate an instance of the Utilities class.
Determine the file type of the Active Document.
Prefix Utilities class method calls with an object variable.
Retrieve and use the respective comment characters.
Destroy the instance of the Utilities class upon completion.
I have not taken the time to modify the other two methods of the CodeManipulation class to handle multiple languages. If you are adept at C# and C++, you might want to go ahead and do that yourself. I have protected them so that they will not work if the Active Document does not contain Visual Basic code. I have done this by calling the GetFileType method of the Utilities class and ensuring that the code is Visual Basic. If it is not, I inform the user that these functions are valid for Visual Basic only and then exit from the method. Again, the code that performs this functionality is highlighted in boldface in Listing 10-13.
Listing 10-13: CodeManipulation Methods for Visual Basic Only
Public Sub GenLocalErrorTrap() Dim sLine As String Dim sTemp As String Dim sTemp2 As String Dim sWord As String Dim i As Long Dim nL As Integer Dim bFound As Boolean Dim sTempLine As String Dim sProcType As String Dim sProcName As String Dim bFoundDefLine As Boolean Const EM = " MsgBox(Error in " Dim oUtil As New Utilities(oVB) Try If oUtil.GetFileType(oVB.ActiveDocument) <> DocVB Then MsgBox("GenLocalErrorTrap only works for VB Code.") Exit Sub End If sTemp = oUtil.GetCodeFromWindow() sTemp2 = "" nL = oUtil.MLCount(sTemp, 0) bFound = False For i = 1 To nL ' look for the first line of code sLine = oUtil.MemoLine(sTemp, 0, i) ' get the procname to make the goto label unique If Not bFoundDefLine Then If InStr(sLine, "Sub ") > 0 Then sProcType = "Sub" ElseIf InStr(sLine, "Function ") > 0 Then sProcType = "Function" Else sTemp2 = sTemp2 & sLine & vbCrLf GoTo JustOutPutThisLine End If bFoundDefLine = True sTempLine = sLine Do While Trim$(sTempLine) <> "" sWord = oUtil.GetToken(sTempLine, "_") ' when we find the Proc type, term the loop ' and retrieve the name next below If sWord = "Sub" Or sWord = "Function" Then _ Exit Do If Trim$(sWord) = "" Then Exit Do sProcName = sWord Loop sProcName = oUtil.GetToken(sTempLine, "_") End If If Not bFound Then If InStr(sLine, "Sub ") > 0 Or _ InStr(sLine, "Function ") > 0 Or _ InStr(sLine, "Global ") > 0 Or _ InStr(sLine, "Const ") > 0 Or _ InStr(sLine, "Dim ") > 0 Then sTemp2 = sTemp2 & sLine & vbCrLf ElseIf Left$(Trim$(sLine), 1) = ""' Then sTemp2 = sTemp2 & sLine & vbCrLf ElseIf Trim$(sLine) = "" Then sTemp2 = sTemp2 & sLine & vbCrLf Else bFound = True sTemp2 = sTemp2 & vbCrLf & " Try" & vbCrLf If Trim$(sLine) <> "End " & sProcType Then sTemp2 = sTemp2 & sLine & vbCrLf Else sTemp2 = sTemp2 & _ " Catch e as System.Exception" & vbCrLf sTemp2 = sTemp2 & _ " MsgBox(" & Chr(34) & _ "Error in " & sProcName & _ ": " & Chr(34) & _ " & e.Message) " & vbCrLf sTemp2 = sTemp2 & " End Try" & vbCrLf sTemp2 = sTemp2 & sLine & vbCrLf End If End If Else If InStr(sLine, "End " & sProcType) > 0 Then sTemp2 = sTemp2 & " Catch e as System.Exception" & vbCrLf sTemp2 = sTemp2 & " MsgBox(" & Chr(34) & "Error in " & _ sProcName & ": " & Chr(34) & " & e.Message)" & vbCrLf sTemp2 = sTemp2 & " End Try" & vbCrLf sTemp2 = sTemp2 & sLine & vbCrLf Exit For Else sTemp2 = sTemp2 & sLine & vbCrLf End If End If JustOutPutThisLine: Next ' now the proc with err code added is ready to paste oUtil.PutCodeBack(sTemp2) oUtil = Nothing Exit Sub Catch e As System.Exception MsgBox("Error in GenLocalErrorTrap: " & e.Message) End Try End Sub Public Sub CloneProcedure() Dim s As String Dim i As Integer Dim rs As String Dim oUtil As New Utilities(oVB) Dim iFileType As Short Try ' get selected proc from active window iFileType = oUtil.GetFileType(oVB.ActiveDocument) If iFileType = DocVB Then s = oUtil.GetWholeProc() Else s = oUtil.GetCodeFromWindow() End If If s = "" Then Exit Sub If iFileType = DocVB Then If InStr(1, s, " Sub ", 0) = 0 And _ InStr(1, s, " Function ", CompareMethod.Binary) = 0 Then MsgBox("Please select a whole Procedure to be cloned.", _ MsgBoxStyle.Exclamation) Exit Sub End If End If Dim oFrm As New CloneProc(oVB) rs = oFrm.Display(s) oFrm.Dispose() If rs <> "" Then oUtil.AddMethodToEndOfDocument(rs) End If Catch e As System.Exception MsgBox("Error: " & e.Message, MsgBoxStyle.Critical, "Clone Procedure") Exit Sub End Try oUtil = Nothing End Sub
The new Utilities class contains the following methods. They are all generalpurpose methods that can be used by all CodeManipulation methods.
GetToken
MLCount
MemoLine
CountSpacesBeforeFirstChar
GetFileType
AddMethodToEndOfDocument
GetCodeFromWindow
GetCommentCharForDoc
GetLanguageCommentChars
PutCodeBack
GetWholeProc
A couple of methods have been changed, and I list only the ones that have changed. The first is PutCodeBack. I have simply modified that method to put the code back into the document without using the Clipboard so that it does not destroy the contents of the Clipboard. Listing 10-14 shows the old code commented out and the new code inserted.
Listing 10-14: PutCodeBack Method
Public Sub PutCodeBack(ByVal s As String) Dim selCodeBlock As TextSelection Dim datobj As New System.Windows.Forms.DataObject() Try selCodeBlock = CType(oVB.ActiveDocument.Selection(), _ EnvDTE.TextSelection) 'datobj.SetData(System.Windows.Forms.DataFormats.Text, s) 'System.Windows.Forms.Clipboard.SetDataObject(datobj) 'selCodeBlock.Paste() selCodeBlock.Delete() selCodeBlock.Insert(s, 1) Catch e As System.Exception MsgBox("Could not put code back in window.", _ MsgBoxStyle.Critical, _ "PutCodeBackInWindow") End Try End Sub
The second method changed in the Utilities class is GetWholeProc. Listing 10-15 shows the changes made to this method. The code for selecting the proc if the user has not completely selected it only works for Visual Basic code windows. Therefore, the method guarantees that the user has selected some code if the file type is not Visual Basic.
Listing 10-15: GetWholeProc Method
Public Function GetWholeProc() As String Dim ts As TextSelection = oVB.ActiveWindow().Selection Dim ep As EditPoint = ts.ActivePoint.CreateEditPoint Dim sLine As String Dim i As Integer Dim sCommentChar As String Try sCommentChar = Me.GetCommentCharForDoc(oVB.ActiveDocument) sCommentChar = Left(sCommentChar, 1) If sCommentChar = "/" Then If Len(ts.Text) = 0 Then MsgBox("For a C#/C++ project you must select the whole proc.") Return "" End If End If ' if the user has selected the whole proc, ' then just return it ' otherwise select it for them... If Len(ts.Text) > 0 Then If (InStr(1, ts.Text, "Sub ", 1) > 0 Or _ InStr(1, ts.Text, "Function ", 1) > 0) And _ (InStr(1, ts.Text, "End Sub", 1) > 0 Or _ InStr(1, ts.Text, "End Function", 1) > 0) _ Then Return ts.Text End If GoTo SelectTheProc Else SelectTheProc: " Get the start of the proc ep.MoveToPoint(ep.CodeElement(EnvDTE.vsCMElement. vsCMElementFunction).GetStartPoint (vsCMPart.vsCMPartWhole)) ' move selection start point to top of proc ts.MoveToPoint(ep, False) ' back up to previous line looking for comments i = 0 Do ep.LineUp() ts.MoveToPoint(ep, False) ts.SelectLine() sLine = ts.Text If Left(Trim(sLine), 1) <> sCommentChar Then ep.LineDown() ts.MoveToPoint(ep, False) Exit Do End If i = i + 1 Loop ' if the count of comment lines > 0 the ' ts point is set properly ' else we must move it back to the original ep.LineDown(i + 1) ' move to bottom of proc ep.MoveToPoint(ep.CodeElement(EnvDTE.vsCMElement. vsCMElementFunction).GetEndPoint (vsCMPart.vsCMPartWhole)) ' select the proc ts.MoveToPoint(ep, True) Return ts.Text End If Catch e As System.Exception System.Windows.Forms.MessageBox.Show( "You must either select " & _ "the whole procedure or your cursor must" & _ " be within the procedure " & _ "to be selected. " & e.Message) Return "" End Try End Function
Finally, I changed the AddMethodToEndOfDocument method. The boldfaced code in Listing 10-16 was added to check for the type of file. C# classes always begin with a namespace, and therefore there are two terminating braces (}) above which the new procedure must be positioned. If the file type is C#, I simply move up one additional line before inserting the code at the end of the class.
Listing 10-16: AddMethodToEndOfDocument Method
Public Sub AddMethodToEndOfDocument(ByVal NewMethod As String) Dim objTD As TextDocument = oVB.ActiveDocument.Object Dim objEP As EditPoint = objTD.EndPoint.CreateEditPoint ' We are past the end of the last line of the document ' move back in front of the End Module/Class objEP.LineUp(1) ' if a c# file, we must get within the namespace ' and the class braces If Me.GetFileType(oVB.ActiveDocument) = 9 Then objEP.LineUp(1) End If objEP.Insert(NewMethod) End Sub
| < Free Open Study > |
|