Excel VBA - How to Turn On Line Numbers in Code Editor

前端 未结 2 1978
死守一世寂寞
死守一世寂寞 2020-12-16 15:05

Please kindly help: how do I turn on the line numbers in Excel VBA code editor? I am using Excel 2013 version.

Thank you.

2条回答
  •  星月不相逢
    2020-12-16 15:31

    Short answer for excel 2016, have not tried it in 2013 yet.

    Do Once:

    1. Paste the large code from final Module2 in this answer in your workbook.
    2. Paste the code for final Module3 in this answer, in your workbook.
    3. Paste the code for final Module4 in this answer, in your workbook.
    4. Then paste the line Global allow_for_line_addition As Stringthis is just so that you can automatically add linenumbers` above/in the first line of every module.
    5. Delete all empty lines at the end of each module (so no lose enters after the last end sub,end function or End Property of a module).
    6. In the VBA editor, while not running a code, and not being in "break"-mode:click tools>references>mark: `Microsoft Visual Basic for Applications Extensibility 5.3"

    Do every time you have modified your code:

    1. *Run the code for final Module3 to remove line numbers to all the modules in your workbook.
    2. *Run the code for final Module4 to add line numbers to all the modules in your workbook.

    (*because sometimes you get an error if you cut lines out or move them around (e.g. put line 2440: above line 2303:). By removing and re-adding them, the line numbering is automatically correct again)

    Long answer (including learning steps and attempts) - to me it was not straightforward to implement hymced`s answer, so I documented the steps necessary to add line numbers to a module in the VBA code editor (*and remove them again). I followed the following steps to get it working.

    1. From this link I have learned that a vbcomponent can be a module.
    2. I Copyied the first code given, into temporary Module2, and the second code given by hymced into temporary Module3.
    3. Then modified the first line of the 2nd code in temporary Module3 to:

      AddLineNumbers wbName:="Book1.xlsm", vbCompName:="Module1", LabelType:=vbLabelColon, AddLineNumbersToEmptyLines:=True, AddLineNumbersToEndOfProc:=True, Scope:=vbScopeAllProc
      
    4. I got an error at line:

      procName = .ProcOfLine(i, vbext_pk_Proc) ` Type d`argument ByRef incompatible ~~> Requires VBIDE library as a Reference for the VBA Project
      
    5. So I read I had to enable the VBIDE library.

    6. So I stopped the code, clicked tools>references, and could not find the VBIDE library.

    7. On this forum I found the VBIDE is enabled by adding a reference to the VBA extensibility library:

    Click on Tools-References in the VBE, and scroll down and tick the entry for Microsoft Visual Basic for Applications Extensibility 5.3.

    So after doing that the first error disapeared and it did not highlight any line but gave the it gave the error "Invalid procedure call or argument".

    1. Since I am still not sure about the vbCompName, I thought it might need to know the sub in stead of the module so I tried to modify the 2nd code in temporary Module3 to:

      AddLineNumbers wbName:="Book1.xlsm", vbCompName:="learn", LabelType:=vbLabelColon, AddLineNumbersToEmptyLines:=True, AddLineNumbersToEndOfProc:=True, Scope:=vbScopeAllProc
      
    2. That highlighted the line:

      With Workbooks(wbName).VBProject.VBComponents(vbCompName).CodeModule 
      

    saying: subscript out of range. (So I also tried: Module1.learn and Module1:learn, yielding the subscript out of range-error.

    As it turns out,

    AddLineNumbers wbName:="Book1.xlsm", vbCompName:="Module1", LabelType:=vbLabelColon, AddLineNumbersToEmptyLines:=True, AddLineNumbersToEndOfProc:=True, Scope:=vbScopeAllProc
    

    is the right way to call the replacing sub if the sub you want to provide with line numbers is located in a module named Module1. The first error describes occurs, but it does add the line numbers to the code (except for first line containing sub ... and the last line containing end sub.Tested in Module1 named sub learn() of the excel 2016 workbook named Book1.xlsm. For completeness learn consists of:

    Sub learn()
        ThisWorkbook.Worksheets("Sheet1").Activate
        Range("A1").Activate
        Range("A1").Select
        Range("A1").Value = Range("A1").Value + 1
    End Sub
    

    However, on the way back, removing the line numbers, it yielded an error because it asks .lines(0,1) of procName in Sub AddLineNumbers...

    1. So I modified it to exclude the .lines(0,1) by putting the changed code below into final Module2:

      Public Enum vbLineNumbers_LabelTypes
          vbLabelColon    ' 0
          vbLabelTab      ' 1
      End Enum
      
      Public Enum vbLineNumbers_ScopeToAddLineNumbersTo
          vbScopeAllProc  ' 1
          vbScopeThisProc ' 2
      End Enum
                Sub AddLineNumbers(ByVal wbName As String, _
                                                            ByVal vbCompName As String, _
                                                            ByVal LabelType As vbLineNumbers_LabelTypes, _
                                                            ByVal AddLineNumbersToEmptyLines As Boolean, _
                                                            ByVal AddLineNumbersToEndOfProc As Boolean, _
                                                            ByVal Scope As vbLineNumbers_ScopeToAddLineNumbersTo, _
                                                            Optional ByVal thisProcName As String)
      
      ' USAGE RULES
      ' DO NOT MIX LABEL TYPES FOR LINE NUMBERS! IF ADDING LINE NUMBERS AS COLON TYPE, ANY LINE NUMBERS AS VBTAB TYPE MUST BE REMOVE BEFORE, AND RECIPROCALLY ADDING LINE NUMBERS AS VBTAB TYPE
      
          Dim i As Long
          Dim j As Long
          Dim procName As String
          Dim startOfProcedure As Long
          Dim lengthOfProcedure As Long
          Dim endOfProcedure As Long
          Dim strLine As String
      
          With Workbooks(wbName).VBProject.VBComponents(vbCompName).CodeModule
              .CodePane.Window.Visible = False
      
      If Scope = vbScopeAllProc Then
      
              For i = 1 To .CountOfLines - 1
      
                  strLine = .Lines(i, 1)
                  procName = .ProcOfLine(i, vbext_pk_Proc) ' Type d'argument ByRef incompatible ~~> Requires VBIDE library as a Reference for the VBA Project
      
                  If procName <> vbNullString Then
                      startOfProcedure = .ProcStartLine(procName, vbext_pk_Proc)
                      bodyOfProcedure = .ProcBodyLine(procName, vbext_pk_Proc)
                      countOfProcedure = .ProcCountLines(procName, vbext_pk_Proc)
      
                      prelinesOfProcedure = bodyOfProcedure - startOfProcedure
                      'postlineOfProcedure = ??? not directly available since endOfProcedure is itself not directly available.
      
                      lengthOfProcedure = countOfProcedure - prelinesOfProcedure ' includes postlinesOfProcedure !
                      'endOfProcedure = ??? not directly available, each line of the proc must be tested until the End statement is reached. See below.
      
                      If endOfProcedure <> 0 And startOfProcedure < endOfProcedure And i > endOfProcedure Then
                          GoTo NextLine
                      End If
      
                      If i = bodyOfProcedure Then inprocbodylines = True
      
                      If bodyOfProcedure < i And i < startOfProcedure + countOfProcedure Then
                          If Not (.Lines(i - 1, 1) Like "* _") Then
      
                              inprocbodylines = False
      
                              PreviousIndentAdded = 0
      
                              If Trim(strLine) = "" And Not AddLineNumbersToEmptyLines Then GoTo NextLine
      
                              If IsProcEndLine(wbName, vbCompName, i) Then
                                  endOfProcedure = i
                                  If AddLineNumbersToEndOfProc Then
                                      Call IndentProcBodyLinesAsProcEndLine(wbName, vbCompName, LabelType, endOfProcedure)
                                  Else
                                      GoTo NextLine
                                  End If
                              End If
      
                              If LabelType = vbLabelColon Then
                                  If HasLabel(strLine, vbLabelColon) Then strLine = RemoveOneLineNumber(.Lines(i, 1), vbLabelColon)
                                  If Not HasLabel(strLine, vbLabelColon) Then
                                      temp_strLine = strLine
                                      .ReplaceLine i, CStr(i) & ":" & strLine
                                      new_strLine = .Lines(i, 1)
                                      If Len(new_strLine) = Len(CStr(i) & ":" & temp_strLine) Then
                                          PreviousIndentAdded = Len(CStr(i) & ":")
                                      Else
                                          PreviousIndentAdded = Len(CStr(i) & ": ")
                                      End If
                                  End If
                              ElseIf LabelType = vbLabelTab Then
                                  If Not HasLabel(strLine, vbLabelTab) Then strLine = RemoveOneLineNumber(.Lines(i, 1), vbLabelTab)
                                  If Not HasLabel(strLine, vbLabelColon) Then
                                      temp_strLine = strLine
                                      .ReplaceLine i, CStr(i) & vbTab & strLine
                                      PreviousIndentAdded = Len(strLine) - Len(temp_strLine)
                                  End If
                              End If
      
                          Else
                              If Not inprocbodylines Then
                                  If LabelType = vbLabelColon Then
                                      .ReplaceLine i, Space(PreviousIndentAdded) & strLine
                                  ElseIf LabelType = vbLabelTab Then
                                      .ReplaceLine i, Space(4) & strLine
                                  End If
                              Else
                              End If
                          End If
      
                      End If
      
                  End If
      
      NextLine:
              Next i
      
      ElseIf AddLineNumbersToEmptyLines And Scope = vbScopeThisProc Then
      
      End If
      
              .CodePane.Window.Visible = True
          End With
      
      End Sub
                Function IsProcEndLine(ByVal wbName As String, _
                    ByVal vbCompName As String, _
                    ByVal Line As Long) As Boolean
      
      With Workbooks(wbName).VBProject.VBComponents(vbCompName).CodeModule
      If Trim(.Lines(Line, 1)) Like "End Sub*" _
                  Or Trim(.Lines(Line, 1)) Like "End Function*" _
                  Or Trim(.Lines(Line, 1)) Like "End Property*" _
                  Then IsProcEndLine = True
      End With
      
      End Function
                Sub IndentProcBodyLinesAsProcEndLine(ByVal wbName As String, ByVal vbCompName As String, ByVal LabelType As vbLineNumbers_LabelTypes, ByVal ProcEndLine As Long)
          Dim procName As String
          Dim startOfProcedure As Long
          Dim endOfProcedure As Long
      
          With Workbooks(wbName).VBProject.VBComponents(vbCompName).CodeModule
      
              procName = .ProcOfLine(ProcEndLine, vbext_pk_Proc)
              bodyOfProcedure = .ProcBodyLine(procName, vbext_pk_Proc)
              endOfProcedure = ProcEndLine
              strEnd = .Lines(endOfProcedure, 1)
      
              j = bodyOfProcedure
              Do Until Not .Lines(j - 1, 1) Like "* _" And j <> bodyOfProcedure
      
                  strLine = .Lines(j, 1)
      
                  If LabelType = vbLabelColon Then
                      If Mid(strEnd, Len(CStr(endOfProcedure)) + 1 + 1 + 1, 1) = " " Then
                          .ReplaceLine j, Space(Len(CStr(endOfProcedure)) + 1) & strLine
                      Else
                          .ReplaceLine j, Space(Len(CStr(endOfProcedure)) + 2) & strLine
                      End If
                  ElseIf LabelType = vbLabelTab Then
                      If endOfProcedure < 1000 Then
                          .ReplaceLine j, Space(4) & strLine
                      Else
                          Debug.Print "This tool is limited to 999 lines of code to work properly."
                      End If
                  End If
      
                  j = j + 1
              Loop
      
          End With
      End Sub
                Sub RemoveLineNumbers(ByVal wbName As String, ByVal vbCompName As String, ByVal LabelType As vbLineNumbers_LabelTypes)
          Dim i As Long
          With Workbooks(wbName).VBProject.VBComponents(vbCompName).CodeModule
              'MsgBox ("nr of lines = " & .CountOfLines & vbNewLine & "Procname = " & procName)
                  'MsgBox ("nr of lines REMEMBER MUST BE LARGER THAN 7! = " & .CountOfLines)
              For i = 1 To .CountOfLines
                  procName = .ProcOfLine(i, vbext_pk_Proc)
                  If procName <> vbNullString Then
                      If i > 1 Then
                              'MsgBox ("Line " & i & " is a body line " & .ProcBodyLine(procName, vbext_pk_Proc))
                          If i = .ProcBodyLine(procName, vbext_pk_Proc) Then inprocbodylines = True
                              If .Lines(i - 1, 1) <> "" Then
                                  'MsgBox (.Lines(i - 1, 1))
                              End If
                          If Not .Lines(i - 1, 1) Like "* _" Then
                              'MsgBox (inprocbodylines)
                              inprocbodylines = False
                                  'MsgBox ("recoginized a line that should be substituted: " & i)
                              'MsgBox ("about to replace " & .Lines(i, 1) & vbNewLine & " with: " & RemoveOneLineNumber(.Lines(i, 1), LabelType) & vbNewLine & " with label type: " & LabelType)
                              .ReplaceLine i, RemoveOneLineNumber(.Lines(i, 1), LabelType)
                          Else
                              If IsInProcBodyLines Then
                                  ' do nothing
                                      'MsgBox (i)
                              Else
                                  .ReplaceLine i, Mid(.Lines(i, 1), RemovedChars_previous_i + 1)
                              End If
                          End If
                      End If
                  Else
                  ' GoTo NextLine
                  End If
      NextLine:
              Next i
          End With
      End Sub
                Function RemoveOneLineNumber(ByVal aString As String, ByVal LabelType As vbLineNumbers_LabelTypes)
          RemoveOneLineNumber = aString
          If LabelType = vbLabelColon Then
              If aString Like "#:*" Or aString Like "##:*" Or aString Like "###:*" Or aString Like "####:*" Then
                  RemoveOneLineNumber = Mid(aString, 1 + InStr(1, aString, ":", vbTextCompare))
                  If Left(RemoveOneLineNumber, 2) Like " [! ]*" Then RemoveOneLineNumber = Mid(RemoveOneLineNumber, 2)
              End If
          ElseIf LabelType = vbLabelTab Then
              If aString Like "#   *" Or aString Like "##  *" Or aString Like "### *" Or aString Like "#### *" Then RemoveOneLineNumber = Mid(aString, 5)
              If aString Like "#" Or aString Like "##" Or aString Like "###" Or aString Like "####" Then RemoveOneLineNumber = ""
          End If
      End Function
                Function HasLabel(ByVal aString As String, ByVal LabelType As vbLineNumbers_LabelTypes) As Boolean
          If LabelType = vbLabelColon Then HasLabel = InStr(1, aString & ":", ":") < InStr(1, aString & " ", " ")
          If LabelType = vbLabelTab Then
              HasLabel = Mid(aString, 1, 4) Like "#   " Or Mid(aString, 1, 4) Like "##  " Or Mid(aString, 1, 4) Like "### " Or Mid(aString, 1, 5) Like "#### "
          End If
      End Function
                Function RemoveLeadingSpaces(ByVal aString As String) As String
          Do Until Left(aString, 1) <> " "
              aString = Mid(aString, 2)
          Loop
          RemoveLeadingSpaces = aString
      End Function
                Function WhatIsLineIndent(ByVal aString As String) As String
          i = 1
          Do Until Mid(aString, i, 1) <> " "
              i = i + 1
          Loop
          WhatIsLineIndent = i
      End Function
      
                Function HowManyLeadingSpaces(ByVal aString As String) As String
          HowManyLeadingSpaces = WhatIsLineIndent(aString) - 1
      End Function
      

    With calling the replacement on sub learn() with the code below, pasted in temporary module3:

        Sub AddLineNumbers_vbLabelColon()
        AddLineNumbers wbName:="Book1.xlsm", vbCompName:="Module1", LabelType:=vbLabelColon, AddLineNumbersToEmptyLines:=True, AddLineNumbersToEndOfProc:=True, Scope:=vbscopeallproc
    End Sub
    
    Sub AddLineNumbers_vbLabelTab()
        AddLineNumbers wbName:="Book1.xlsm", vbCompName:="Module1", LabelType:=vbLabelTab, AddLineNumbersToEmptyLines:=True, AddLineNumbersToEndOfProc:=True, Scope:=vbscopeallproc
    End Sub
    
    Sub RemoveLineNumbers_vbLabelColon()
        RemoveLineNumbers wbName:="Book1.xlsm", vbCompName:="Module1", LabelType:=vbLabelColon
    End Sub
    
    Sub RemoveLineNumbers_vbLabelTab()
        RemoveLineNumbers wbName:="Book1.xlsm", vbCompName:="Module1", LabelType:=vbLabelTab
    End Sub
    

    Now it worked (both adding and removing line numbers, with all 4 methods of calling the adding/removal of line numbers pasted in temporary module2 for a single sub in a module (module1 in the example case). So I tried to put 2 subs behind eachother in the same module. In that case, the code did not change the add line numbers to the 2nd sub.

    1. So I added the following line above Module1:

      Global allow_for_line_addition As String
      

    Making Module1 look like:

    Global allow_for_line_addition As String
      Sub learn()
        ThisWorkbook.Worksheets("Sheet1").Activate
        Range("A1").Activate
        Range("A1").Select
        Range("A1").Value = Range("A1").Value + 1
    End Sub
    Sub learn2()
        ThisWorkbook.Worksheets("Sheet1").Activate
        Range("A1").Activate
        Range("A1").Select
        Range("A1").Value = Range("A1").Value + 1
    End Sub
    

    Now it added the line numbers to the entire module, but it did not remove the line numbers from the entire module, so I edited the removal code of hymceds answer as well and already put it in the long code of **final**Module2`.

    Note: If you have empty white lines after the end of a sub or function, it will keep on adding white lines every time you run the script to add the line numbers (which after the first run, simply updates the line numbers). These empty line numbers cause an error when executing the code, so you should remove them once. If there are no empty lines at the end of a sub, this code will also not add new ones.

    1. To add line numbers to all of your modules in your workbook, keep the long code in final Module2 as I modified it and replace the code of temporary Module3 with final Module3:

      Global allow_for_line_addition As String 'this is just so that you can automatically add linenumbers
              Sub remove_line_numbering_all_modules()
      'source: https://stackoverflow.com/questions/36791473/vba-getting-the-modules-in-workbook
      'This code numbers all the modules in your .xlsm
          Dim vbcomp As VBComponent
          Dim modules As Collection
      Set modules = New Collection
          For Each vbcomp In ThisWorkbook.VBProject.VBComponents
              'if normal or class module
              If ((vbcomp.Type = vbext_ct_StdModule) Or (vbcomp.Type = vbext_ct_ClassModule)) Then
                     'V0:
                     RemoveLineNumbers wbName:=ThisWorkbook.name, vbCompName:=vbcomp.name, LabelType:=vbLabelColon
                     'V1:
                     'Call RemoveLineNumbers(ThisWorkbook.name, vbcomp.name)
              End If
          Next vbcomp
      End Sub
      

      And add the following code to final Module4:

      Global allow_for_line_addition As String 'this is just so that you can automatically add linenumbers
      'This sub adds line numbers to all the modules after you have added the following line to every module
      'add tools references microsoft visual basic for applications (5.3) as checked
      'Source httpsstackoverflow.comquestions40731182excel-vba-how-to-turn-on-line-numbers-in-code-editor50368332#50368332
              Sub add_line_numbering_all_modules()
      'source: https://www.stackoverflow.com/questions/36791473/vba-getting-the-modules-in-workbook
      'This code numbers all the modules in your .xlsm
          Dim vbcomp As VBComponent
          Dim modules As Collection
          Set modules = New Collection
          For Each vbcomp In ThisWorkbook.VBProject.VBComponents
              'if normal or class module
              If ((vbcomp.Type = vbext_ct_StdModule) Or (vbcomp.Type = vbext_ct_ClassModule)) Then
                     'V0:
                     Call AddLineNumbers(ThisWorkbook.name, vbcomp.name, vbLabelColon, True, True, vbScopeAllProc)
                     'v1
                     'Call AddLineNumbers(ThisWorkbook.name, vbcomp.name)
              End If
          Next vbcomp
      End Sub
      

    where you can either substitute "Book1.xlsm" with the name of your own workbook, or with thisworkbook (notice no ""), or vice versa.

提交回复
热议问题