VBA walk through multidimensional array (dimensions limited by system only!) - vba

Via Google I found no function that can emit a multidimensional array as structured text. VBA arrays behave somewhat bulky in this respect, if you want to iterate through a multidimensional array.
Corrective suggestions, answers and / or comments are welcome.
So here's my Code suggestion - For free private Use (not excessively tested!):
'**********************************************************************************************************
' Helper-Function: emits a multidimensional array as structured text (c) 2018 F. Heinemann, Freiburg, Germany
Function Arr2Str(ByVal aArr As Variant, Optional ByVal nLevel As Long = 0, Optional ByVal nMaxDim As Long = 0) As String
Dim cRet As String
Dim nI, nK As Long
Dim nCount1 As Long
On Error Resume Next
Arr2Str = ""
cRet = ""
nMaxDim = MaxUboundFH(aArr)
nCount1 = UboundFH(aArr, nMaxDim)
If nCount1 = -1 Then
Debug.Print "Error: wrong MaxDim! [" & CStr(nMaxDim) & "]"
Exit Function
End If
For nI = LBound(aArr) To UBound(aArr)
cRet = cRet & Space(nLevel) & "- " & "[" & nI & "]: " & aArr(nI) & vbCrLf
If Err.Number = 0 Then
GoTo NextIteration_nI
End If
nCount1 = UboundFH(aArr, nMaxDim)
For nK = 0 To nCount1 + 1
If IsArrayFH(aArr, nI, nK) = True Then
cRet = cRet & Arr2Str(aArr(nI, nK), nLevel + 1, nMaxDim - 1)
Else
cRet = cRet & Space(nLevel) & "- " & "[" & nI & "]: " & aArr(nI, nK) & vbCrLf
End If
Next nK
NextIteration_nI:
Next nI
Arr2Str = cRet
End Function
Function MaxUboundFH(ByRef aArr As Variant) As Long
Dim nI, nDimGet As Long
MaxUboundFH = 1
On Error GoTo MaxUboundFH_Error
For nI = 2 To 64
nDimGet = UBound(aArr, nI)
MaxUboundFH = nI
Exit Function
Next nI
Exit Function
MaxUboundFH_Error:
End Function
Function UboundFH(ByRef aArr As Variant, ByVal nDim As Long) As Long
Dim nDimGet As Long
UboundFH = -1
On Error GoTo UboundFH_Error
nDimGet = UBound(aArr, nDim)
UboundFH = nDimGet
Exit Function
UboundFH_Error:
UboundFH = -1
End Function
Function IsArrayFH(ByRef aArr As Variant, ByVal nIndex1 As Long, ByVal nIndex2 As Long) As Boolean
IsArrayFH = False
On Error GoTo IsArrayFH_Error
If IsArray(aArr(nIndex1, nIndex2)) = True Then
IsArrayFH = True
Exit Function
End If
IsArrayFH_Error:
IsArrayFH = False
End Function

Related

Why does VBA doesn't reach data after Row number 65,000 when using DAO with SQL language?

I have a VBA module that receives a database object, worksheet name, and two column field names as parameters to make a SQL query into another Excel table that has over 1,000,000 rows with information. But when I was debugging I noticed that my VBA code does not return the info after the row number 65,000 (approximately). This is returning wrong info and not acting properly as expected.
So, how can I handle it in my existing code?
Here is my code:
Functions
Const diretorioSA = "C:\Users\Bosch-PC\Desktop\dbLEGENDAS_ELETROPAR\"
Const BaseEletro = "dbClientesEletropar.xlsb"
Const dbClientes = "CLIENTESLDA"
Public Function Number2Letter(ByVal ColNum As Long) As String
Dim ColumnNumber As Long
Dim ColumnLetter As String
ColumnNumber = ColNum
ColumnLetter = Split(Cells(1, ColumnNumber).Address, "$")(1)
Number2Letter = ColumnLetter
End Function
Public Function GetWorkbook(ByVal sFullName As String) As Workbook
Dim sFile As String
Dim wbReturn As Workbook
sFile = DIR(sFullName)
On Error Resume Next
Set wbReturn = Workbooks(sFile)
If wbReturn Is Nothing Then
Set wbReturn = Workbooks.Open(sFullName)
End If
On Error GoTo 0
Set GetWorkbook = wbReturn
End Function
Public Function ReplaceChars(ByVal str As String, ByVal Lista As String) As String
Dim buff(), buffChars() As String
ReDim buff(Len(str) - 1): ReDim buffChars(Len(Lista) - 1)
For i = 1 To Len(str): buff(i - 1) = Mid$(str, i, 1): Next
For i = 1 To Len(Lista): buffChars(i - 1) = Mid$(Lista, i, 1): Next
For strEle = 0 To UBound(buff)
For listaEle = 0 To UBound(buffChars)
If buff(strEle) = buffChars(listaEle) Then
buff(strEle) = ""
End If
Next listaEle
novoTexto = novoTexto & buff(strEle)
Next strEle
ReplaceChars = novoTexto
End Function
Function ConsultaBaseDeDadosELETRO(ByVal CAMPO_PESQUISA As String, _
ByVal CAMPO_RETORNO As String, _
ByVal NOME_PLANILHA As String, _
ByRef BASES As Object, _
ByVal ARGUMENTO As String) As String
On Error GoTo ERRO:
Debug.Print BASES.Name
Dim RSt22 As Recordset
Set RSt22 = BASES.OpenRecordset("SELECT [" & CAMPO_RETORNO & "] FROM [" & NOME_PLANILHA & "$] WHERE [" & CAMPO_PESQUISA & "] IN ('" & ARGUMENTO & "') ;", dbOpenForwardOnly, dbReadOnly)
Debug.Print RSt22.CacheSize & " | CONTAGEM: " & RSt22.RecordCount
ConsultaBaseDeDadosELETRO = RSt22(CAMPO_RETORNO)
Exit Function
ERRO:
Debug.Print VBA.Err.Description & " | Error number: " & VBA.Err.Number & " | " & VBA.Err.HelpFile
ConsultaBaseDeDadosELETRO = "Sem registros"
End Function
Main Subroutine
Sub ProcurarBaseEletro(ByVal PASTA As String, ByVal ARQUIVO As String, ByVal NOME_PLANILHA As String, ByVal CAMPO As String)
If ActiveCell.value = "CGC" Or ActiveCell.value = "CNPJ" Or ActiveCell.value = "cgc" Or ActiveCell.value = "cnpj" Then
Application.ScreenUpdating = False
Dim wks As Worksheet: Set wks = ActiveSheet
Dim db2 As database
Dim CellRow As Single
Dim Cellcol_info, CellCol As String
Dim DiretorioBase As String: DiretorioBase = diretorioSA & BaseEletro
Dim wb As Workbook: Set wb = GetWorkbook(DiretorioBase)
If wb Is Nothing Then
MsgBox "Base de dados não localizada!" & vbNewLine & "EM: " & DiretorioBase, vbCritical, "Atenção"
Set wb = Nothing
Set wks = Nothing
Application.ScreenUpdating = True
Exit Sub
Else
wks.Activate
CellRow = ActiveCell.row
CellCol = Number2Letter(ActiveCell.Column)
Cellcol_info = Number2Letter(ActiveCell.Column + 1)
CELLCOL_LROW = ActiveSheet.Cells(ActiveSheet.Rows.Count, CellCol).End(xlUp).row
Set db2 = OpenDatabase(DiretorioBase, False, False, "Excel 8.0")
Columns(Cellcol_info & ":" & Cellcol_info).Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
Range(Cellcol_info & CellRow).value = CAMPO
Dim Query As String
Dim CelAtivaValue As String
For i = CellRow + 1 To CELLCOL_LROW
CelAtivaValue = UCase(Cells(i, CellCol).value)
Query = ReplaceChars(CelAtivaValue, "/.- ")
If Left(Query, 6) < 132714 Then
Cells(i, Cellcol_info).value = ConsultaBaseDeDadosELETRO("CGC", CAMPO, NOME_PLANILHA, db2, Query)
Else
Cells(i, Cellcol_info).value = ConsultaBaseDeDadosELETRO("CGC", CAMPO, NOME_PLANILHA & 2, db2, Query)
End If
Next i
wb.Close
End If
Else
MsgBox "Texto da Célula ativa não é CGC/CNPJ, impossível fazer pesquisa", vbCritical, "Valor célula ativa: " & ActiveCell.value
Application.ScreenUpdating = True
Exit Sub
End If
Cells.EntireColumn.AutoFit
MsgBox "Processo concluído com sucesso.", vbOKOnly, "Informativo do sistema"
Application.ScreenUpdating = True
End Sub
Older Excel formats (.xls) maintains a worksheet limit of 2^16 (65536) rows. Current Excel formats (.xlsx) maintains a worksheet limit of 2^20 (1,048,576) rows.
Likely, you have a more recent version of MS Office (2007+) (given the .xlsb in BaseEletro) but your DAO code was not updated. Consider adjusting the DAO.OpenDatabase option to the newer current format.
From
Set db2 = OpenDatabase(DiretorioBase, False, False, "Excel 8.0")
To
Set db2 = OpenDatabase(DiretorioBase, False, False, "Excel 12.0 Xml")

How can I prevent VBA code, used to draw up a list of unique words from a word document, from slowing down as the document get's longer

I have used some code from the internet, modified slightly for my specific use case, to draw up a list of unique words from a word document, the code works without a problem, but the time to execute the code seems to grow exponentially as the document length increases. Can anyone give me any suggestions to speed up the code when working with very long documents?
Sub UniqueWordList()
Dim wList As New Collection
Dim wrd
Dim chkwrd
Dim sTemp As String
Dim k As Long
Dim cWrd As Long
Dim tWrd As Long
Dim nWrd As String
Dim Flag As Boolean
Flag = False
tWrd = ActiveDocument.Range.Words.Count
cWrd = 0
For Each wrd In ActiveDocument.Range.Words
cWrd = cWrd + 1
If cWrd Mod 100 = 0 Then
Application.StatusBar = "Updating: " & (cWrd)
End If
If Flag Then
Flag = False
GoTo nw
End If
If cWrd < tWrd Then
nWrd = ActiveDocument.Words(cWrd + 1)
nWrd = Trim(LCase(nWrd))
End If
sTemp = Trim(LCase(wrd))
If sTemp = "‘" Then
sTemp = sTemp & nWrd
Flag = True
End If
If sTemp Like "*[a-zA-Z]*" Then
k = 0
For Each chkwrd In wList
k = k + 1
If chkwrd = sTemp Then GoTo nw
If chkwrd > sTemp Then
wList.Add Item:=sTemp, Before:=k
GoTo nw
End If
Next chkwrd
wList.Add Item:=sTemp
End If
nw:
Next wrd
sTemp = "There are " & ActiveDocument.Range.Words.Count & " words "
sTemp = sTemp & "in the document, before this summary, but there "
sTemp = sTemp & "are only " & wList.Count & " unique words."
ActiveDocument.Range.Select
Selection.Collapse Direction:=wdCollapseEnd
Selection.TypeText vbCrLf & sTemp & vbCrLf
For Each chkwrd In wList
Selection.TypeText chkwrd & vbCrLf
Next chkwrd
End Sub
After some suggestions I modified my code to use a scripting dictionary, this however does not seem to have solved the problem. Also to answer the concern regarding my message at the end, I understand that the wording is off, what I want is a list of words from the document but each word only once.
Sub UniqueWordListMi()
Dim wList() As String
Dim sTemp As String
Dim cWrd As Long
Dim tWrd As Long
Dim nWrd As String
Dim Flag As Boolean
Dim IsInArray As Boolean
Dim arrsize As Long
Dim rra2 As Variant
arrsize = 0
Flag = False
tWrd = ActiveDocument.Range.Words.Count
cWrd = 1
ReDim Preserve wList(0 To arrsize)
wList(arrsize) = "UNQ"
For Each wrd In ActiveDocument.Range.Words
If cWrd Mod 100 = 0 Then
Application.StatusBar = "Updating" & (cWrd)
End If
If Flag Then
Flag = False
GoTo nw
End If
If cWrd < tWrd Then
nWrd = ActiveDocument.Words(cWrd + 1)
nWrd = Trim(LCase(nWrd))
End If
sTemp = Trim(LCase(wrd))
If sTemp = "‘" Then
sTemp = sTemp & nWrd
Flag = True
End If
If sTemp Like "*[a-zA-Z]*" Then
ReDim Preserve wList(0 To arrsize)
wList(arrsize) = sTemp
arrsize = arrsize + 1
End If
nw:
cWrd = cWrd + 1
Next wrd
Set Dict = CreateObject("scripting.dictionary")
For i = 0 To UBound(wList)
If (Not Dict.Exists(CStr(wList(i)))) Then Dict.Add CStr(wList(i)), wList(i) 'Next i
Next i
rra2 = Dict.Items
sTemp = "There are " & ActiveDocument.Range.Words.Count & " words "
sTemp = sTemp & "in the document, before this summary, but there "
sTemp = sTemp & "are only " & UBound(wList) & " unique words."
ActiveDocument.Range.Select
Selection.Collapse Direction:=wdCollapseEnd
Selection.TypeText vbCrLf & sTemp & vbCrLf
For u = 0 To UBound(rra2)
Selection.TypeText vbCrLf & rra2(u) & vbCrLf
Next u
End Sub
#AlexK beat me to it with a comment on using a Scripting.Dictionary.
Something like this might help
Option Explicit
Public Function CountUniqueWords(ByRef ipRange As Word.Range) As Scripting.Dictionary
Dim myUniqueWords As Scripting.Dictionary
Set myUniqueWords = New Scripting.Dictionary
Dim myPara As Variant
For Each myPara In ipRange.Paragraphs
Dim myWord As Variant
For Each myWord In Split(myPara.Range.Text)
If myUniqueWords.Exists(myWord) Then
myUniqueWords.Item(myWord) = myUniqueWords.Item(myWord) + 1
Else
myUniqueWords.Add myWord, 1
End If
Next
Next
Set CountUniqueWords = myUniqueWords
End Function
Some polishing might be required to meet your specific requirements.
You can't help some increase in processing time as the document gets longer but as the access to the document is limited to paragraphs rather than words is should proceed somewhat faster.
Try the following code. It uses the dictionary directly with the rules of your code.
Note that this will only improve your code. But still, the longer the document will get, the more words need to be checked and the more time it will need. That fact will not change, you can just optimize it by using the dictionary directly but more words need more time to check.
Option Explicit
Sub UniqueWordListMi()
Dim wList As Object
Set wList = CreateObject("scripting.dictionary")
Dim sTemp As String
Dim cWrd As Long
Dim tWrd As Long
Dim nWrd As String
Dim Flag As Boolean
Dim IsInArray As Boolean
Dim arrsize As Long
Dim rra2 As Variant
arrsize = 0
Flag = False
tWrd = ActiveDocument.Range.Words.Count
cWrd = 1
Dim wrd As Variant
For Each wrd In ActiveDocument.Range.Words
If cWrd Mod 100 = 0 Then
Application.StatusBar = "Updating" & (cWrd)
End If
If Flag Then
Flag = False
GoTo nw
End If
If cWrd < tWrd Then
nWrd = ActiveDocument.Words(cWrd + 1)
nWrd = Trim(LCase(nWrd))
End If
sTemp = Trim(LCase(wrd))
If sTemp = "‘" Then
sTemp = sTemp & nWrd
Flag = True
End If
If sTemp Like "*[a-zA-Z]*" Then
If Not wList.Exists(sTemp) Then
wList.Add sTemp, 1
Else
wList.Item(sTemp) = wList.Item(sTemp) + 1
End If
cWrd = cWrd + 1
End If
nw:
Next wrd
sTemp = "There are " & (cWrd - 1) & " words "
sTemp = sTemp & "in the document, before this summary, but there "
sTemp = sTemp & "are only " & wList.Count & " distinct words."
ActiveDocument.Range.Select
Selection.Collapse Direction:=wdCollapseEnd
Selection.TypeText vbCrLf & sTemp & vbCrLf
Dim chkwrd As Variant
For Each chkwrd In wList
Selection.TypeText chkwrd & vbTab & wList.Item(chkwrd) & " times" & vbCrLf
Next chkwrd
End Sub
The following example:
This is an example test where every word is unique except one.
There are 12 words in the document, before this summary, but there are only 11 distinct words.
this 1 times
is 2 times
an 1 times
example 1 times
test 1 times
where 1 times
every 1 times
word 1 times
unique 1 times
except 1 times
one 1 times
With everyone's help and some additional reading, as well as some help from a reddit user this code work's perfectly:
Sub UniqueWordListFast()
Dim WordDictionary As Object
Dim SourceText As Document
Dim objWord As Object
Dim sTemp As String, strWord As String, nxtWord As String
Dim count As Long
count = 0
Set WordDictionary = CreateObject("Scripting.Dictionary")
Set SourceText = Application.ActiveDocument
For Each objWord In SourceText.Range.Words
count = count + 1
strWord = Trim(objWord.Text)
If strWord = nxtWord Then GoTo nw
If strWord Like "*[a-z]*" Then WordDictionary(strWord) = strWord
If strWord Like "‘" Then
nxtWord = Trim(SourceText.Words(count + 1))
strWord = strWord & nxtWord
WordDictionary(strWord) = strWord
End If
nw:
Next
sTemp = "[DOCUMENT] " & vbTab & SourceText.Name & vbCrLf & vbCrLf & _
"There are " & SourceText.Range.Words.count & " words in the document, " & _
"before this summary, but there are only " & WordDictionary.count & " unique words."
Dim NewDocument As Document
Set NewDocument = Documents.Add
NewDocument.Range.Text = sTemp & vbCrLf & Join(WordDictionary.Keys, vbCrLf)
End Sub
Extremely fast and efficient. Thank you everyone!

Label VBA code with line numbers

Is there a way to quickly label VBA code with line numbers such as shown below?
Sub sample()
Dim i As Long
10 Debug.Print "A"
20 Debug.Print "B"
30 Debug.Print "C"
40 Debug.Print "D"
50 MsgBox "Done."
End Sub
As you can see in this answer Log what line error occurs: vba, there are two ways to do that:
Either manually (not fast, so doesn't answer your question)
Or with some add-in, like the one mentioned in the linked answer
Or write your own add-in using VBA Extensibility to do that.
Edit: I never worked with VBE extensivbility library so I would suggest consulting other sources, for example here: http://www.cpearson.com/excel/vbe.aspx
Repurposing code written by Chip Pearson I would try the following, although this I haven't tested it:
With VBComp.CodeModule 'VBComp is VBIDE.VBComponent
For N = 1 To .CountOfLines
If Trim(.Lines(N, 1)) = vbNullString Then
' blank line, skip it
ElseIf Left(Trim(.Lines(N, 1)), 1) = "'" Then
' comment line, skip it
Else
.Lines(N, 1) = N & " " & .Lines(N,1)
End If
Next N
End With
Keep in mind, that you need to make sure that you check for various cases, for example for the lines that are already numbered.
If you want to provide all the modules in your workbook with (updated) line numbers (adding and removing) you can apply the steps below*^°°. Please take this warning into account.
Do Once:
Paste the large code from Module2 in your workbook.
Paste the code for Module3 in your workbook.
Paste the code for Module4 in your workbook.
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.
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).
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:
°Run the code for Module3 to remove line numbers to all the modules in your workbook.
°Run the code for Module4 to add line numbers to all the modules in your workbook.
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
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
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.
*Note this worked in excel 2016, I have not tried it in 2013 yet.
^It is a modified version of Hemced's answer here., which in turn, looks a lot like Arich's answer here.
°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.
°°Tested in Excel 2016.

VBA create log file

Hello can you help me please with code in VBA ? I would like create a log file from text in cells ("C2" and "C3 " + date and time ) when I press button "zadat" Thank you
My code for implementation is:
Module 1
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Sub zadat()
Dim reg, check As String
Dim i, j, done As Integer
reg = Cells(2, 3).Value
check = Cells(4, 3).Value
If check = "True" Then
i = 2
j = 1
done = 0
Do While Sheets("data").Cells(i, j) <> ""
If Sheets("data").Cells(i, j) = reg Then
vytisteno = ZkontrolovatAVytiskoutSoubor()
done = Sheets("data").Cells(i, j + 3)
done = done + 1
Sheets("data").Cells(i, j + 3) = done
Exit Do
End If
i = i + 1
Loop
Else
MsgBox ("Opravit, špatný štítek!!!")
End If
Cells(3, 3) = ""
Cells(3, 3).Select
ActiveWindow.ScrollRow = Cells(1, 1).row
End Sub
Module 2:
Option Explicit
Public Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" ( _
ByVal hwnd As Long, _
ByVal lpOperation As String, _
ByVal lpFile As String, _
ByVal lpParameters As String, _
ByVal lpDirectory As String, _
ByVal nShowCmd As Long) As Long
Public Function PrintThisDoc(formname As Long, FileName As String)
On Error Resume Next
Dim x As Long
x = ShellExecute(formname, "Print", FileName, 0&, 0&, 3)
End Function
Public Function ZkontrolovatAVytiskoutSoubor() As Boolean
Dim printThis
Dim strDir As String
Dim strFile As String
strDir = "W:\Etikety\Štítky\Krabice\Testy"
strFile = Range("C2").Value & ".lbe"
If Not FileExists(strDir & "\" & strFile) Then
MsgBox "soubor neexistuje!"
ZkontrolovatAVytiskoutSoubor = False
Else
printThis = PrintThisDoc(0, strDir & "\" & strFile)
ZkontrolovatAVytiskoutSoubor = True
End If
End Function
Private Function FileExists(fname) As Boolean
'Returns TRUE if the file exists
Dim x As String
x = Dir(fname)
If x <> "" Then FileExists = True _
Else FileExists = False
End Function
If you don't want to use FSO, there is a simple solution using only VBA statements: Open, Print # and Close:
Sub Log2File(Filename As String, Cell1, Cell2)
Dim f As Integer
f = FreeFile
Open Filename For Append Access Write Lock Write As #f
Print #f, Now, Cell1, Cell2
Close #f
End Sub
I've put the filename and the cells refs as arguments of the sub for re-usability purpose. I also use default (local) formatting, but this can be easily changed.
Note that you don't have to check for existence of the file, it will be created if it doesn't exist.
Try this. Below code will create a new log file every time
Public Function LogDetails()
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Dim logFile As Object
Dim logFilePath As String
Dim logFileName As String
'Replace 'TestLog' with your desired file name
logFileName = "TestLog" & ".txt"
myFilePath = "C:\Users\..\Desktop\" & logFileName 'Modify the path here
If fso.FileExists(myFilePath) Then
Set logFile = fso.OpenTextFile(myFilePath, 8)
Else
' create the file instead
Set logFile = fso.CreateTextFile(myFilePath, True)
End If
logFile.WriteLine "[" & Date & " " & Time & "] " & Worksheet("yoursheetnamehere").Cells(2, 3) & " " & Worksheet("yoursheetnamehere").Cells(3, 3)
logFile.Close ' close the file
End Function

Adding line numbers to VBA code (Microsoft Access 2016) [duplicate]

I want to have line numbers in my VBA code for debugging reasons. That will allow me to know where a particular error occurred.
Is there an automatic feature for this (such as an option in settings)? Or do I need to write my own macro?
If I need to write my own macro to accomplish this task, how would I go about doing such a thing?
You don't want line numbers.
Not for "debugging reasons", not for anything. Line numbers are deprecated for a reason: they're a relic of an ancient time before procedures even existed, and GOTO the only way to get anywhere.
Erl only returns the last encountered line number before an error was raised. This can mean misleading error logs, if you're logging errors:
Sub DoSomething()
10 On Error GoTo 50
Debug.Print 42 / 0
30 Exit Sub
50 Debug.Print "Error raised on line " & Erl 'returns 10
End Sub
Also, line numbers only have Integer resolution (a VBA module can have up to 65,535 lines, which is twice that resolution), and will silently fail and happily report wrong line numbers if you don't keep that in mind:
Sub DoSomething()
99997 On Error GoTo 99999
99998 Debug.Print 42 / 0
99999
Debug.Print Erl 'Prints 34462 - and which line is that?
End Sub
Any serious VBA application will use structured error handling instead.
Write small, specialized procedures (i.e. write code that follows modern-day best practices), and the line number becomes utterly meaningless.
Line numbers are a pain in the neck to maintain; they clutter up the code and make it overall harder to read (and therefore to debug).
That said, IIRC MZ-Tools 3 had such a functionality.
Keep in mind, that BASIC looked like this when line numbers were a thing:
10 GOSUB 100
20 GOSUB 1000
99 END
100 REM CLEAR SCREEN
110 PRINT CHR$(147)
120 RETURN
200 REM MODULO
210 LET MOD% = V%-INT(V%/FB%)*FB%
220 RETURN
1000 REM INIT VARIABLES
1010 LET FIZZ$ = "FIZZ"
1011 LET BUZZ$ = "BUZZ"
1020 LET FIZZ% = 3
1021 LET BUZZ% = 5
1030 LET MIN% = 1
1031 LET MAX% = 15
1100 PRINT FIZZ$ + ":" + STR$(FIZZ%)
1101 PRINT BUZZ$ + ":" + STR(BUZZ%)
1102 PRINT FIZZ$ + BUZZ$ + ":" + STR$(FIZZ%*BUZZ%)
1105 PRINT
2000 REM ACTUAL FIZZBUZZ LOOP
2010 FOR X = MIN% TO MAX%
2015 LET RESULT$ = STR$(X)
2020 LET FB% = FIZZ%*BUZZ%
2021 LET V% = X
2024 GOSUB 200
2025 IF MOD%=0 THEN LET RESULT$=FIZZ$+BUZZ$ : GOTO 2050
2030 LET FB% = FIZZ%
2031 GOSUB 200
2035 IF MOD%=0 THEN LET RESULT$=FIZZ$ : GOTO 2050
2040 LET FB% = BUZZ%
2041 GOSUB 200
2045 IF MOD%=0 THEN LET RESULT$=BUZZ$ : GOTO 2050
2050 PRINT RESULT$
2090 NEXT X
2099 RETURN
The above is a working Commodore 64 BASIC 2.0 fizzbuzz program. VBA has fabulous backward-compatibility. With only slight modifications, it runs in VBA:
Sub Main()
10 GoSub 100
20 GoSub 1000
99 End
100 Rem CLEAR SCREEN
110 'Debug.Print Chr$(147) 'Chr$(147) was a special character on C64
120 Return
200 Rem MODULO
210 Let Modulo% = V% - Int(V% / FB%) * FB%
220 Return
1000 Rem INIT VARIABLES
1010 Let FIZZ$ = "FIZZ"
1011 Let BUZZ$ = "BUZZ"
1020 Let FZZ% = 3
1021 Let BZZ% = 5
1030 Let Min% = 1
1031 Let Max% = 15
1100 Debug.Print FIZZ$ + ":" + Str$(FZZ%)
1101 Debug.Print BUZZ$ + ":" + Str(BZZ%)
1102 Debug.Print FIZZ$ + BUZZ$ + ":" + Str$(FZZ% * BZZ%)
1105 Debug.Print
2000 Rem ACTUAL FIZZBUZZ LOOP
2010 For X = Min% To Max%
2015 Let RESULT$ = Str$(X)
2020 Let FB% = FZZ% * BZZ%
2021 Let V% = X
2024 GoSub 200
2025 If Modulo% = 0 Then Let RESULT$ = FIZZ$ + BUZZ$: GoTo 2050
2030 Let FB% = FZZ%
2031 GoSub 200
2035 If Modulo% = 0 Then Let RESULT$ = FIZZ$: GoTo 2050
2040 Let FB% = BZZ%
2041 GoSub 200
2045 If Modulo% = 0 Then Let RESULT$ = BUZZ$: GoTo 2050
2050 Debug.Print RESULT$
2090 Next X
2099 Return
End Sub
Don't write 1980's code, we're 40 years later.
I use this code for adding line numbers to my Excel projects. I found it online a while back and I don't remember where I got it, so credit goes to whoever originally wrote this:
Sub AddLineNumbers(wbName As String, vbCompName As String)
'See MakeUF
Dim i As Long, j As Long, lineN As Long
Dim procName As String
Dim startOfProceedure As Long
Dim lengthOfProceedure As Long
Dim newLine As String
With Workbooks(wbName).VBProject.VBComponents(vbCompName).CodeModule
.CodePane.Window.Visible = False
For i = 1 To .CountOfLines
procName = .ProcOfLine(i, vbext_pk_Proc)
If procName <> vbNullString Then
startOfProceedure = .ProcStartLine(procName, vbext_pk_Proc)
lengthOfProceedure = .ProcCountLines(procName, vbext_pk_Proc)
If startOfProceedure + 1 < i And i < startOfProceedure + lengthOfProceedure - 1 Then
newLine = RemoveOneLineNumber(.Lines(i, 1))
If Not HasLabel(newLine) And Not (.Lines(i - 1, 1) Like "* _") Then
.ReplaceLine i, CStr(i) & ":" & newLine
End If
End If
End If
Next i
.CodePane.Window.Visible = True
End With
End Sub
Sub RemoveLineNumbers(wbName As String, vbCompName As String)
'See MakeUF
Dim i As Long
With Workbooks(wbName).VBProject.VBComponents(vbCompName).CodeModule
For i = 1 To .CountOfLines
.ReplaceLine i, RemoveOneLineNumber(.Lines(i, 1))
Next i
End With
End Sub
Function RemoveOneLineNumber(aString)
RemoveOneLineNumber = aString
If aString Like "#:*" Or aString Like "##:*" Or aString Like "###:*" Then
RemoveOneLineNumber = Mid(aString, 1 + InStr(1, aString, ":", vbTextCompare))
End If
End Function
Function HasLabel(ByVal aString As String) As Boolean
HasLabel = InStr(1, aString & ":", ":") < InStr(1, aString & " ", " ")
End Function
You'll have to modify it to suit your needs since you're working in Access, but I'm sure the main meat of it still applies. In Excel, there's a userform that is used to kick off the code for the module you specify, but you should be able to just pass in the module name (vbCompName) to specify the module. I'm not well-versed in Access VBA, so I'm not sure what you'd replace Workbooks(wbName) with in the code.
The VBA editor has a built in way to see a line number, under the 'Standard' toolbar:
When you select a line of code, the line number will be represented here next to 'Ln'.
MZ-Tools for VBA has functions to add and remove line numbers to single functions, modules, or the whole project.
See http://www.mztools.com/v8/onlinehelp/index.html?add_remove_line_numbers.htm
Note 1: I find it better to configure the line number increment to 1 instead of 10. You will never manually add line numbers inbetween - whenever you edit the code, you first remove the line numbers, then add them back when you are finished.
Note 2: Until a few years ago, there was a free version 3.0 of MZ-Tools, but it is surprisingly difficult to find a copy. But it is a good investment - there are lots of other useful features (e.g. the automatic adding of error handlers).
The answer of Arich works like a charm on an individual module. If you want to provide your entire workbook with (updated) line numbers you apply the following steps*^:
Do Once:
Paste the large code from Module2 in your workbook.
Paste the code for Module3 in your workbook.
Paste the code for Module4 in your workbook.
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.
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).
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:
°Run the code for Module3 to remove line numbers to all the modules in your workbook.
°Run the code for Module4 to add line numbers to all the modules in your workbook.
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
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
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.
*Note this worked in excel 2016, I have not tried it in 2013 yet.
^It is a modified version of Hemced's answer here., which in turn, looks a lot like Arich's answer.
°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.
This Works for me...Add this to its own module. Calling the code will toggle line numbers on or off. Adding Module titles and/or procedure titles in quotes will update only the module or procedure named.
Option Compare Database
Option Explicit
Sub AddLineNumbers(Optional vbCompName As String, Optional vbCompSubName As String)
On Error Resume Next
DoCmd.Hourglass True
Application.VBE.ActiveVBProject.References.AddFromGuid "{0002E157-0000-0000-C000-000000000046}", 5, 0
Call ExecuteAddLineNumbers(vbCompName, vbCompSubName)
DoCmd.Hourglass False
End Sub
Sub ExecuteAddLineNumbers(Optional vbCompName As String, Optional vbCompSubName As String)
On Error GoTo Err_Handler
'create a reference to the Microsoft Visual Basic for Applications Extensibility library
Dim i As Long, j As Long, lineN As Long
Dim procName As String
Dim startOfProceedure As Long
Dim lengthOfProceedure As Long
Dim newLine As String
Dim objComponent As Object
Dim lineNumber As Long
Dim HasLineNumbers As Boolean
For Each objComponent In Application.VBE.ActiveVBProject.VBComponents
If (vbCompName = vbNullString Or objComponent.Name = vbCompName) And objComponent.Name <> _
Application.VBE.ActiveCodePane.CodeModule.Name) Then
Debug.Print objComponent.Name
With objComponent.CodeModule
.CodePane.Window.Visible = False
For i = 1 To .CountOfLines
'Debug.Print .ProcOfLine(i, vbext_pk_Proc)
If procName = "" And .ProcOfLine(i, vbext_pk_Proc) <> "" Then
procName = .ProcOfLine(i, vbext_pk_Proc)
'vbext_pk_Get Specifies a procedure that returns the value of a property.
'vbext_pk_Let Specifies a procedure that assigns a value to a property.
'vbext_pk_Set Specifies a procedure that sets a reference to an object.
'vbext_pk_Proc Specifies all procedures other than property procedures.
'type=vbext_ct_ClassModule
'type=vbext_ct_StdModule
'type=vbext_ct_Document
If objComponent.Type = vbext_ct_ClassModule Then
If InStr(.Lines(i + 1, 1), " Let ") > 0 Then
startOfProceedure = .ProcStartLine(procName, vbext_pk_Let)
lengthOfProceedure = .ProcCountLines(procName, vbext_pk_Let)
ElseIf InStr(.Lines(i + 1, 1), " Get ") > 0 Then
startOfProceedure = .ProcStartLine(procName, vbext_pk_Get)
lengthOfProceedure = .ProcCountLines(procName, vbext_pk_Get)
ElseIf InStr(.Lines(i + 1, 1), " Set ") > 0 Then
startOfProceedure = .ProcStartLine(procName, vbext_pk_Set)
lengthOfProceedure = .ProcCountLines(procName, vbext_pk_Set)
Else
startOfProceedure = .ProcStartLine(procName, vbext_pk_Proc)
lengthOfProceedure = .ProcCountLines(procName, vbext_pk_Proc)
End If
Else
startOfProceedure = .ProcStartLine(procName, vbext_pk_Proc)
lengthOfProceedure = .ProcCountLines(procName, vbext_pk_Proc)
End If
lineNumber = 10
HasLineNumbers = .Find("## ", startOfProceedure + 1, 1, startOfProceedure + lengthOfProceedure - 1, 1, _
False, False, True)
End If
If (vbCompSubName = vbNullString And procName <> vbNullString) Or _
(vbCompSubName <> vbNullString And procName = vbCompSubName) Then
If startOfProceedure + 1 < i And i < startOfProceedure + lengthOfProceedure - 1 Then
newLine = RemoveOneLineNumber(.Lines(i, 1), HasLineNumbers)
If Trim(newLine) <> vbNullString Then
If Not HasLabel(newLine) And Not (.Lines(i - 1, 1) Like "* _") Then
If HasLineNumbers = False Then newLine = CStr(lineNumber) & vbTab & newLine
.ReplaceLine i, newLine
lineNumber = lineNumber + 10
ElseIf Not HasLineNumbers Then
.ReplaceLine i, vbTab & newLine
Else
.ReplaceLine i, newLine
End If
End If
ElseIf i = startOfProceedure + lengthOfProceedure - 1 Then
procName = ""
End If
Else
procName = ""
End If
Next i
.CodePane.Window.Visible = True
End With
End If
Next objComponent
Exit Sub
Err_Handler:
MsgBox (Err.Number & ": " & Err.Description)
End Sub
Function RemoveOneLineNumber(aString As String, HasLineNumbers As Boolean)
Dim i As Double
RemoveOneLineNumber = aString
i = ((Len(Trim(Str(Val(aString)))) / 4) - Int(Len(Trim(Str(Val(aString)))) / 4)) * 4
If aString Like "#*" Then
RemoveOneLineNumber = Space(i) & Mid(aString, InStr(1, aString, " ", vbTextCompare))
RemoveOneLineNumber = Right(aString, Len(aString) - 4)
ElseIf HasLineNumbers And aString Like " *" Then
RemoveOneLineNumber = Right(aString, Len(aString) - 4)
End If
End Function
Function HasLabel(ByVal aString As String) As Boolean
HasLabel = False
If Right(Trim(aString), 1) = ":" Or _
Left(Trim(aString), 3) = "Dim" Or _
Left(Trim(aString), 3) = "ReDim" Or _
Left(Trim(aString), 1) = "'" Or _
Left(Trim(aString), 6) = "Option" Or _
Left(Trim(aString), 5) = "Debug" Or _
Left(Trim(aString), 3) = "Sub" Or _
Left(Trim(aString), 11) = "Private Sub" Or _
Left(Trim(aString), 10) = "Public Sub" Or _
Left(Trim(aString), 8) = "Function" Or _
Left(Trim(aString), 12) = "End Function" Or _
Left(Trim(aString), 8) = "Property" Or _
Left(Trim(aString), 12) = "End Property" Or _
Left(Trim(aString), 7) = "End Sub" Then HasLabel = True
End Function
Any decent error handler will report more than just a line number. It will report the error ode, description and the module where it happened. Regardless whether ERL is repeating line numbers throughout your app, if you can't find the problem with the other clues reported, maybe you need a sabbatical. Or, hell, add a variable that increments a module level alpha code as an adjunct to the line number like "newERL = strProcLevel & ERL" to give you "A12345" as a line number.
This is not 100% tested, but using VBA extensibility you could do the following
Sub line_number(strModuleName As String)
Dim vbProj As VBProject
Dim vbComp As VBComponent
Dim cmCode As CodeModule
Dim intLine As Integer
Set vbProj = Application.VBE.ActiveVBProject
Set vbComp = vbProj.VBComponents(strModuleName)
Set cmCode = vbComp.CodeModule
For intLine = 2 To cmCode.CountOfLines - 1
cmCode.InsertLines intLine, intLine - 1 & cmCode.Lines(intLine, 1)
cmCode.DeleteLines intLine + 1, 1
Next intLine
End Sub
This gives the results before and after as below, altering in this way is not recommended though.