I am a novice in VBA but recently following #Marcucciboy2, #Cindy Meister and #Mathieu Guindon for their excellent contribution in VBA. In the topic Export VBA Procedures (Sub/Function) Separately I tried a little in guidelines of #Mathieu Guindon and come across a few new problems.
'Public Const vbext_pk_Get As Long = 3
'Public Const vbext_pk_Let As Long = 1
'Public Const vbext_pk_Set As Long = 2
'Public Const vbext_pk_Proc As Long = 0
'sub function
'sub function
Sub test3()
Rw = 15
Dim Vbc As VBComponent
Dim Lno, StLine, LineCnt, CmntPos, ParenthPos As Long
Dim Line, Pname1, Pname2, SubOrFun As String, Pk As vbext_ProcKind
Pk = vbext_pk_Proc
For Each Vbc In ThisWorkbook.VBProject.VBComponents
Lno = 1
Pname1 = ""
For Lno = 1 To Vbc.CodeModule.CountOfLines
Line = Vbc.CodeModule.Lines(Lno, 1)
'For Pk = 0 To 3 **'Activating this For loop cuasing Excel to come to a halt (not responding)'**
Pname2 = Vbc.CodeModule.ProcOfLine(Lno, Pk)
'Filter the line only up to the 1st comment character or 1st parenthesis
'(due to possibility of some parameter name may ends with "sub " or "function ")
CmntPos = InStr(1, Line, "'")
ParenthPos = InStr(1, Line, "(")
If CmntPos > 0 Then Line = Trim(Left(Line, CmntPos - 1))
If ParenthPos > 0 Then Line = Left(Line, ParenthPos - 1)
If Line <> "" And Pname1 <> Pname2 Then
Line = LCase(Replace(Line, Pname2, "")) 'In some cases function name can also contain "sub" like "Batch_subtraction" and vice verse some of the procedures name can be "functionality_View"
SubOrFun = IIf(InStr(1, Line, "function") > 0, "Function", "Sub")
StLine = 0
LineCnt = 0
StLine = Vbc.CodeModule.ProcStartLine(Pname2, Pk) 'Startline including comment lines
LineCnt = Vbc.CodeModule.ProcCountLines(Pname2, Pk) 'line Count including comment lines
Pname1 = Pname2
' sub function
Rw = Rw + 1
' following lines are only for trial/debugging purpose, the results being stored in excel cells
' in actual case here should be the lines of the procedure can be processed by StLine and LineCnt
' Or added to a collection for further processing
ThisWorkbook.Sheets(3).Cells(Rw, 1).Value = Vbc.Name
ThisWorkbook.Sheets(3).Cells(Rw, 2).Value = Pname2
ThisWorkbook.Sheets(3).Cells(Rw, 3).Value = SubOrFun
ThisWorkbook.Sheets(3).Cells(Rw, 4).Value = StLine
ThisWorkbook.Sheets(3).Cells(Rw, 5).Value = LineCnt
ThisWorkbook.Sheets(3).Cells(Rw, 6).Value = Lno
ThisWorkbook.Sheets(3).Cells(Rw, 7).Value = Line
ThisWorkbook.Sheets(3).Cells(Rw, 8).Value = Pk
End If
'Next
Next
Next
End Sub
'sub function
It is working fine.
Now, When I am trying iterate through Pk= 0 to 3 excel is coming to a halt (failing to respond). Also whatever is the value of Pk finally in Excel it is showing as 0. What may be the catch? This is purely due to academic purpose.
Pname2 = Vbc.CodeModule.ProcOfLine(Lno, Pk)
Usage of ProcOfLine is awkward, because the ProcKind parameter isn't just passed ByRef because ByRef is the default in VBA: the ProcKind parameter is passed ByRef because the ProcKind enum value is an output of the function!
Dim Pk As vbext_ProcKind
Pname2 = Vbc.CodeModule.ProcOfLine(Lno, Pk)
Debug.Print Pk ' <~ that's the ProcKind of procedure Pname2 which Lno belongs to
The signature looks like this:
Property Get ProcOfLine(Line As Long, ProcKind As vbext_ProcKind) As String
Its usage might have been clearer if it were like this:
Property Get ProcOfLine(ByVal Line As Long, ByRef outProcKind As vbext_ProcKind) As String
Related
I am trying to run an excel vba form to search through the lines, but for some unknown reason I get the error:
Method Range of object Global failed
Private Sub CommandButton3_Click()
Dim last, i As Integer
Dim ref, lote As String
'Sheets("analisegeral").Visible = True
Sheets("analisegeral").Select
last = Range("analisegeral").End(xlUp).Row + 1
For i = 2 To last ref = Cells(i, 8)
lote = Cells(i, 13)
If TextBox1.Text = ref Then
TextBox2.Text = lote
GoTo fim
End If
Next i
If TextBox1.Text <> ref Then
TextBox2.Text = ""
MsgBox "Referência não encontrada!", vbInformation
TextBox1.Text = ""
TextBox2.Text = ""
GoTo fim
End If
fim:
End Sub
There are few issues with your code.
Invalid declaration
Dim last, i As Integer
Dim ref, lote As String
Note that last and ref are declared as Variant type here, unless it was your intent, change it to following:
Dim last As Integer, i As Integer
Dim ref As String, lote As String
Failing to activate worksheet where range is located
'Sheets("analisegeral").Visible = True
Sheets("analisegeral").Select
The fact that your sheet is hidden (or very hidden) disallows it's selection.
Probably this is the case of your error.
Wrong method of calculating last row number
last = Range("analisegeral").End(xlUp).Row + 1
Given you will actualy select analisegeral sheet, this still doesn't make sense:
Range("NamedRange") is a construction that allows to refer to previously named range (either with VBA or manualy). Unless you have one, this will raise another error. Perhaps you meant something like this?
last = Range("A" & Rows.Count).End(xlUp).Row
This will give you a number of column A last row.
Final advice: avoid using Select
Have a piece of code that looks for matches between 2 sheets (sheet1 is customer list and rData is copied pdf with invoices). It usually is exact match but in some cases I'm looking for 6 first characters that matches rData
Dim rData As Variant
Dim r As Variant
Dim r20 As Variant
Dim result As Variant
Dim i As Long
rData = ActiveWorkbook.Sheets(2).Range("A1:A60000")
r20 = ActiveWorkbook.Sheets(1).Range("C2:C33")
For Each r In r20
result = Application.Match(r, rData, 0)
If Not IsError(result) Then
For i = 1 To 5
If (result - i) > 0 Then
If (Left(Trim(rData(result - i, 1)), 3) = "418") Then
MsgBox "customer: " & r & ". invoice: " & rData(result - i, 1)
End If
End If
Next
For i = 1 To 15
If (result + i) > 0 Then
If (Left(Trim(rData(result + i, 1)), 3) = "418") Then
MsgBox "customer: " & r & ". invoice: " & rData(result + i, 1)
End If
End If
Next
End If
Next r
End Sub
Only part of this that is giving me a headache is this part result = Application.Match(r, rData, 0). How do it get match for not exact match?
Sample of Sheet1
This is what more or less looks like. Matching after CustomerNumber# is easy because they are the same every invoice. BUT sometimes invoice does not have it so I'm searching after CustomerName and sometimes they have uppercase letters, sometimes there is extra stuff behind it and therefore it cannot find exact match.
Hope it makes sense.
To match the customer name from your customer list to the customer name in the invoice even if it has extra characters appended, you can use the wildcard * in Match().
You also have a typo in the Match() function. r20 should be rData.
This is your code with the fixes applied:
Sub Test()
'v4
Dim rData As Variant
Dim r As Variant
Dim r20 As Variant
Dim result As Variant
Dim i As Long
rData = ActiveWorkbook.Sheets(2).Range("A1:A60000")
r20 = ActiveWorkbook.Sheets(1).Range("C2:C33")
For Each r In r20
result = Application.Match(r & "*", rData, 0) ' <~ Fixed here
If Not IsError(result) Then
For i = 1 To 5
If (result - i) > 0 Then
If (Left(Trim(rData(result - i, 1)), 3) = "418") Then
MsgBox "customer: " & r & ". invoice: " & rData(result - i, 1)
End If
End If
Next
For i = 1 To 15
If (result + i) > 0 Then
If (Left(Trim(rData(result + i, 1)), 3) = "418") Then
MsgBox "customer: " & r & ". invoice: " & rData(result + i, 1)
End If
End If
Next
End If
Next r
End Sub
Notes:
Match() is case insensitive, so it works with different capitalisations.
The data in Sheets(2) must all be text for Match() to work correctly with wildcards.
EDIT1: New better version
EDIT2: Refactored constants and made data ranges dynamic
EDIT3: Allows for any prefix to an invoice number of a fixed length
The following is a better, rewritten version of your code:
Sub MuchBetter()
'v3
Const s_InvoiceDataWorksheet As String = "Sheet2"
Const s_InvoiceDataColumn As String = "A:A"
Const s_CustomerWorksheet As String = "Sheet1"
Const s_CustomerStartCell As String = "C2"
Const s_InvoiceNumPrefix As String = "418"
Const n_InvoiceNumLength As Long = 8
Const n_InvScanStartOffset As Long = -5
Const n_InvScanEndOffset As Long = 15
Dim ƒ As Excel.WorksheetFunction: Set ƒ = Excel.WorksheetFunction ' Shortcut
With Worksheets(s_InvoiceDataWorksheet).Range(s_InvoiceDataColumn)
With .Parent.Range(.Cells(1), .Cells(Cells.Rows.Count).End(xlUp))
Dim varInvoiceDataArray As Variant
varInvoiceDataArray = ƒ.Transpose(.Cells.Value2)
End With
End With
With Worksheets(s_CustomerWorksheet).Range(s_CustomerStartCell)
With .Parent.Range(.Cells(1), .EntireColumn.Cells(Cells.Rows.Count).End(xlUp))
Dim varCustomerArray As Variant
varCustomerArray = ƒ.Transpose(.Cells.Value2)
End With
End With
Dim varCustomer As Variant
For Each varCustomer In varCustomerArray
Dim dblCustomerIndex As Double
dblCustomerIndex = Application.Match(varCustomer & "*", varInvoiceDataArray, 0)
If Not IsError(dblCustomerIndex) _
And varCustomer <> vbNullString _
Then
Dim i As Long
For i = ƒ.Max(dblCustomerIndex + n_InvScanStartOffset, 1) _
To ƒ.Min(dblCustomerIndex + n_InvScanEndOffset, UBound(varInvoiceDataArray))
Dim strInvoiceNum As String
strInvoiceNum = Right$(Trim$(varInvoiceDataArray(i)), n_InvoiceNumLength)
If (Left$(strInvoiceNum, Len(s_InvoiceNumPrefix)) = s_InvoiceNumPrefix) Then
MsgBox "customer: " & varCustomer & ". invoice: " & strInvoiceNum
End If
Next
End If
Next varCustomer
End Sub
Notes:
It is a good idea to use constants so all literal values are typed once only and kept grouped together.
Using the RVBA naming convention greatly increases the readability of the code, and reduces the likelihood of bugs.
Using long, appropriately named variables makes the code essentially self-documenting.
Using .Value2 whenever reading cell values is highly recommended (it avoids implicit casting, making it slightly faster as well as eliminating certain issues caused by the casting ).
Surprisingly, in VBA there are good reasons to put a variable declaration as close as possible to the first use of the variable. Two such reasons are 1) it improves readability, and 2) it simplifies future refactoring. Just remember that the variable is not reinitialised every time the Dim is encountered. Initialisation only occurs the first time.
The twin loops have been rolled into one according to the DRY principle.
Whilst the check for an empty customer name/number is not strictly necessary if you can guarantee it will never be so, it is good defensive programming as an empty value will cause erroneous results.
The negative index check inside the loop has been removed and replaced with the one-time use of the Max() worksheet function in the For statement.
The Min() worksheet function is also used in the For statement to avoid trying to read past the end of the array.
Always use worksheet functions on the WorksheetFunction object unless you are explicitly checking for errors, in which case use the Application object.
We have an SQL database that exports to excel. Each record in the database is the evaluation of a project. Each project has a project identifier (string). Since a project can be evaluated two times, each project can have two records. The difference between the two records is the id number (number), with the most recent record having a higher id number. The records are exported to excel with each record as a row in the spread sheet. I am trying to write a sub that compares the two project identifiers and deletes the row with the lower id number. I keep getting an object required error for SameP.
Dim Pident1 As String
Dim Pident2 As String
Dim IdNumb1 As Variant
Dim IdNumb2 As Variant
Dim i As Integer
Dim SameP As Integer
i = 2
For i = 2 To 100
Pident1 = ActiveSheet.Cells(i, 2).Text
Pident2 = ActiveSheet.Cells(i + 1, 2).Text
IdNumb1 = ActiveSheet.Cells(i, 1).Value
IdNumb2 = ActiveSheet.Cells(i + 1, 1).Value
Set SameP = StrComp(Pident1, Pident2, CompareMethod.Text)
If SameP = 0 And IdNumb1> Idnumb2 Then Data.Rows(i).EntireRow.Delete
Next i
End Sub
Any help would be greatly appreciated. I'm not a programmer, i just try when I can. Thanks in advance.
You need to change this
SameP = StrComp(Pident1, Pident2, CompareMethod.Text)
to this:
SameP = StrComp(Pident1, Pident2)
The error is this line also
Data.Rows(i).EntireRow.Delete
Change this to
Sheets("Name_of_sheet_here").Rows(i).EntireRow.Delete
Alternatively this would work too
Activeworksheet.Rows(i).EntireRow.Delete
Change this:
Set SameP = StrComp(Pident1, Pident2, CompareMethod.Text)
If SameP = 0 And IdNumb1> Idnumb2 Then Data.Rows(i).EntireRow.Delete
to
SameP = StrComp(Pident1, Pident2, vbTextCompare )
If SameP = 0 And IdNumb1> Idnumb2 Then ActiveSheet.Rows(i).EntireRow.Delete
I need to reformat a XML file to .CSV.
I already opened the XML in Excel and did a little formating but now I really need to write a macro to get the data into shape. I already started bu I really have issues with the loop logic.
the List has a couple thousand Articles with a variable amount of subarticles.
each subarticle as a the same amount of properties but not every article has the same properties.
https://picload.org/image/ipialic/now.jpg
https://picload.org/image/ipialip/then.jpg
My Code up till now looks like this:
Option Explicit
Dim rowCount As Long, articleCount As Long, propertyCount As Integer, name As String
Sub Sortfunction()
rowCount = 1
articleCount = 0
propertyCount = 0
Do While Sheets("Test").Cells(rowCount, 1).Value <> "end"
If Cells(rowCount, 1).Value = "Reference" Then
rowCount = rowCount + 1
Do While Cells(rowCount, 3).Value = ""
If Cells(rowCount, 3).Value = "4" Then
End If
articleCount = articleCount + 1
Loop
articleCount = articleCount + 1
End If
rowCount = rowCount + 1
Loop
Sheets("result").Cells(1, 1).Value = rowCount
Sheets("result").Cells(2, 1).Value = articleCount
End Sub
At the end of the document i wrote the "end" to have a hook to stop the loop.
Can anyone provide some help? I'm really not the best programmer :-/
I'd really appreciate any help I can get :-)
here he's a translation into algorithm and some tips on functions
update: it was more tricky than I thought... I had to rewrite the code.
The main problem is "how to decide when change column".
I choose this solution "Each product in reference must have the same amount of properties".
If it's not the case, please indicate "how you decide when you have to create a new Column" (you can explain it in plain words)
Here the code rewrited. I tried it on your exemple, it work
Public Sub test()
' Set the range to navigate in your first sheet
Dim cell As Range: Set cell = Sheets("Feuil1").Range("A1")
' set the range to navigate in your result sheet
Dim res As Range: Set res = Nothing
' pos will be used to know the position of a product
Dim lastProperties As Range, posProperties As Range
' While the cell value is not "end"
Do While cell <> "end"
' if the cell is a reference
If cell = "Reference" Then
' Set the range of res
If res Is Nothing Then
Set res = Sheets("Feuil2").Range("A1")
Else
Set res = Sheets("Feuil2").Range("A" & lastProperties.offset(2).Row)
End If
' I set Offset(2) so you will have an empty line between 2 references
' Set the text of the new reference in the result
res = cell.offset(, 1) ' The reference is the cell 1 offset the right of the cell "Reference"
' WARNING : here no writing of titles anymore. It'll be done in the "Else".
' Here you just write "new reference" and reinit var
Else
' Here we have a property
' If the property alreay exist, consider it a new product in the reference
' When we are on a new property, the column of the product if the next to the right
If GetProperties(cell.offset(, 3), res, posProperties) Then
Set lastProperties = posProperties
End If
posProperties = cell.offset(, 4)
End If
' BIG FORGET: you have to get the next cell
Set cell = cell.offset(1)
Loop
End Sub
And the function to search / create your properties
Private Function GetProperties(ByVal propValues As String, ByVal start As Range, ByRef position As Range) As Boolean
Set position = start.offset(1)
' Is the cell below the properties ? Return the row below
' Search for the first "empty row" on the line
If position = propValues Then
Do
Set position = position.offset(, 1)
Loop While Trim(position) <> ""
' Indicate it's an existing value
GetProperties = True
Exit Function
End If
' Is the range empty ?
If Trim(position) = "" Then
' Create the new properties
position = propValues
Set position = position.offset(, 1)
GetProperties = False
Exit Function
End If
' Search the properties in the row below
GetProperties = GetProperties(propValues, position, position)
End Function
It should do the work. If you have any question on understanding some part, don't hesitate
if you don't know about Offset, some reading : https://msdn.microsoft.com/en-us/library/office/ff840060.aspx
Here my example of thing that i will use.
On the left side is the patch it will use NAME BASE REVISE to check the version of package.
Can you convert the script here in to VBA code. I will study about it and integrate to my real work:
if (Patch name = Pack name) then **** searching for same Name on patch column to reference for patch base and revise number
if (base(c column) > base(h column)) ***checknumber[cellbycell]
display "yes" in J cell
or if (base(C column) = base(h column)) then
check if revise(D column) > revise(I column)
display "yes" in J cell
else display No
So if you can give me example code ; if you have sometime please explain to me that what each line of code is meaning.
You don't need vba for this
=IF($A2=$G2,IF($C2>$H2,"Yes",IF($C2=$H2,IF($D2>$I2,"Yes","No"),"No")),"No")
That goes in column J
something like this should work:
Option Explicit
Sub variousconditions()
Dim i As Integer, x As Integer
x = 0
For i = 2 To 10
With Excel.ThisWorkbook.ActiveSheet
If .Cells(i, 1) = .Cells(i, 7) Then '****searching for same Name on patch
Select Case .Cells(i, 3) '***checknumber[cellbycell]
Case Is > .Cells(i, 8)
.Cells(i, 10) = "yes"
Case Is = .Cells(i, 8)
If .Cells(i, 4) > .Cells(i, 9) Then
.Cells(i, 10) = "yes"
End If
End Select
End If
End With
Next i
End Sub
I have to re-iterate Siddharth's reference as that will tell you where you need to save this code etc. : http://msdn.microsoft.com/en-us/library/office/ee814737%28v=office.14%29.aspx
Here is a function to compare two dot-notation version numbers which you'd need to paste into a new module in the VBA editor.
Option Explicit
Public Function VersionCompare(CurrentVersion As Range, _
TargetVersion As Range)
Dim result As Integer
result = CompareDotStrings(CurrentVersion.Cells(1, 1).Value, _
TargetVersion.Cells(1, 1).Value)
If result = 1 Then
VersionCompare = True
Else
VersionCompare = False
End If
End Function
Private Function CompareDotStrings(LeftValue As String, _
RightValue As String) _
As Integer
Dim CompareLeft() As String, CompareRight() As String, CompareLength As Integer
CompareLeft = Split(LeftValue, ".")
CompareRight = Split(RightValue, ".")
CompareLength = UBound(CompareLeft)
If UBound(CompareRight) < CompareLength Then CompareLength = UBound(CompareRight)
Dim ElementLeft As Integer, ElementRight As Integer, Comparison As Integer
Dim ElementNumber As Integer
For ElementNumber = 0 To CompareLength
ElementLeft = CInt(CompareLeft(ElementNumber))
ElementRight = CInt(CompareRight(ElementNumber))
Comparison = ElementRight - ElementLeft
If Comparison <> 0 Then
CompareDotStrings = Sgn(Comparison)
Exit Function
End If
Next ElementNumber
CompareDotStrings = 0
End Function
With this you can use =VersionCompare(H2, C2) to compare two version numbers and everything else you want to do (like splitting apart the dashed versions) can be done with formulas in the worksheet.