I'm trying to populate a form from another table. I have an identifier (formNumber). The loop's purpose is the find all the rows in the table with the same formNumber, then list the details in a form.
Problem encountered is in the fields using startTableRow, startSubdesc1, startSubdesc2, startRemark. I dont know when they are all repeating the same values, that have already been inputted. An item should only appear once.
Dim wsCurrent As Worksheet, _
loTable1 As ListObject, _
lcColumns As ListColumns, _
lrCurrent As ListRow
Set wsCurrent = Worksheets("Expenses")
Set loTable1 = wsCurrent.ListObjects("Expenses")
Set lcColumns = loTable1.ListColumns
'Loop through and find new entries which haven't been form'd yet
For x = 1 To loTable1.ListRows.Count
Set lrCurrent = loTable1.ListRows(x)
If lrCurrent.Range(1, lcColumns("form sent?").Index) = "" And _
lrCurrent.Range(1, lcColumns("form #").Index) <> "" Then
formNumber = lrCurrent.Range(1, lcColumns("form #").Index).Value
'Set first lines on the form
Worksheets("form").Cells(10, 10).Value = formNumber
'Loop through the Expense sheet and as long as the form number doesn't _
'change, write it to the table on the form
startTableRow = 20
startSubdesc1 = 21
startSubdesc2 = 22
startRemark = 54
Do While lrCurrent.Range(1, lcColumns("form #").Index).Value = formNumber
expensesDate = lrCurrent.Range(1, lcColumns("Date").Index).Value
expensesItem = lrCurrent.Range(1, lcColumns("Description").Index).Value
expensesSubdesc1 = lrCurrent.Range(1, lcColumns("Sub-description 1").Index).Value
expensesSubdesc2 = lrCurrent.Range(1, lcColumns("Sub-description 2").Index).Value
expensesRemarks = lrCurrent.Range(1, lcColumns("Remarks").Index).Value
**Worksheets("form").Cells(startTableRow, 5) = expensesItem
Worksheets("form").Cells(startSubdesc1, 5) = expensesSubdesc1
Worksheets("form").Cells(startSubdesc2, 5) = expensesSubdesc2
Worksheets("form").Cells(startRemark, 3) = expensesRemarks
Worksheets("form").Cells(12, 10) = expensesDate**
lrCurrent.Range(1, lcColumns("form sent?").Index).Value = "Yes"
x = x + 1
startTableRow = startTableRow + 3
startSubdesc1 = startSubdesc1 + 3
startSubdesc2 = startSubdesc2 + 3
startRemark = startRemark + 1
Loop
'Need to subtract one from x to loop through the row again
x = x - 1
'Clear data in table on form
For t = 20 To 45
Worksheets("form").Cells(t, 3).Value = ""
Worksheets("form").Cells(t, 5).Value = ""
Next t
'Clear data in REMARK on form
For r = 54 To 57
Worksheets("form").Cells(r, 3).Value = ""
Next r
End If
Next x
End Sub
End Sub
The problem with your code is in the while loop the lrCurrent does not change. after x = x +1 you need to set
lrCurrent = loTable1.ListRows(x) IF x <= loTable1.ListRows.Count
Also then need to protect against running past the end of table by adding another condition
And x <= loTable1.ListRows.Count
to the Do While line at the start.
Here is an example with fewer variables by using .offset
Sub FillForm()
Dim wb As Workbook, ws As Worksheet, wsForm As Worksheet
Set wb = ThisWorkbook
Set ws = wb.Sheets("Expenses")
Set wsForm = wb.Sheets("form")
Dim tbl As ListObject
Set tbl = ws.ListObjects("Expenses")
' create look up for column names
Dim ColNum As New Collection
Dim cell As Range, ix As Integer
For Each cell In tbl.HeaderRowRange
ix = ix + 1
ColNum.add ix, cell.Value
Debug.Print cell.Value
Next
' scan table for not sent items
Dim sFormNo As String, rec As Range
Dim iCount As Integer ' count of lnes with same form no
Dim bSearch As Boolean, iSearch As Integer
Dim iRow As Integer
bSearch = False ' search for matching form no
With tbl
For iRow = 1 To .ListRows.Count
Set rec = .ListRows(iRow).Range
If rec(ColNum("form #")) <> "" _
And rec(ColNum("form sent?")) = "" Then
sFormNo = rec(1)
wsForm.Range("J10") = rec(ColNum("form #"))
wsForm.Range("J12") = rec(ColNum("Date"))
bSearch = True
End If
' search rest of table for more records
If bSearch Then
'Clear data in table on form
'wsForm.Range("C20:C45").ClearContents ' required ?
wsForm.Range("E20:C45").ClearContents
wsForm.Range("C54:C57").ClearContents
iCount = 0
' search from existing row down to end
For iSearch = iRow To .ListRows.Count
Set rec = .ListRows(iSearch).Range
' check match
If rec(ColNum("form #")) = sFormNo _
And rec(ColNum("form sent?")) = "" Then
' fill in form
With wsForm.Range("E20").Offset(3 * iCount, 0)
.Offset(0, 0) = rec(ColNum("Description"))
.Offset(1, 0) = rec(ColNum("Sub-description 1"))
.Offset(2, 0) = rec(ColNum("Sub-Description 2"))
End With
wsForm.Range("C54").Offset(iCount, 0) = rec(ColNum("Remarks"))
' update form sent column
rec(ColNum("form sent?")) = "Yes"
iCount = iCount + 1
Debug.Print "Search for " & sFormNo, rec(ColNum("form #")), iCount, iSearch
End If
Next
wsForm.Activate
wsForm.Range("A20").Select
MsgBox iCount & " lines added", vbInformation, "Completed " & sFormNo
bSearch = False
End If
Next
End With
MsgBox "Ended", vbInformation
End Sub
Related
I need the expert help in VBA Excel code. I need to find the number of duplicate record (AlertToString) for particular device serial number from the source sheet serial number and paste it to the other newly created output sheet by using VBA Macro.
Example (Source sheet):
Expected (Output Sheet with repeat Alert count) :
Source code as below :
Sub Alert700Count()
Dim AlertSource_Sh As Worksheet
Dim AlertOutput_Sh As Worksheet
'Insert a New Blank Worksheet
On Error Resume Next
Application.DisplayAlerts = False
Worksheets("AlertOutput").Delete
Sheets.Add.Name = "AlertOutput"
Application.DisplayAlerts = True
Set AlertSource_Sh = ThisWorkbook.Sheets("SourceSheet")
Set AlertOutput_Sh = ThisWorkbook.Sheets("AlertOutput")
AlertOutput_Sh.Cells(1, 1) = "Serial No"
AlertOutput_Sh.Cells(1, 2) = "A92"
AlertOutput_Sh.Cells(1, 3) = "A95"
AlertOutput_Sh.Cells(1, 4) = "A98"
For Each sh In ActiveWorkbook.Worksheets
With sh.Range("A1:D1")
.Font.Bold = True
.WrapText = True
.CellWidth = 35
.Selection.Font.ColorIndex = 49
.Weight = xlMedium
.LineStyle = xlDash
End With
Next sh
AlertOutput_Sh.Range("A1:D1").Borders.Color = RGB(10, 201, 88)
AlertOutput_Sh.Columns("A:D").ColumnWidth = 12
AlertOutput_Sh.Range("A1:D1").Font.Color = rgbBlueViolet
AlertOutput_Sh.Range("A1:D1").Interior.Color = vbYellow
AlertOutput_Sh.Range("A1:D1").HorizontalAlignment = xlCenter
AlertOutput_Sh.Range("A1:D1").VerticalAlignment = xlTop
' Search the duplicate record and paste in output sheet
Dim A92Count As Long
A92Count = Application.CountIf(AlertSource_Sh.Range("D:D"), "A92")
AlertOutput_Sh.Cells(2, 2) = A92Count
Dim A95Count As Long
A95Count = Application.CountIf(AlertSource_Sh.Range("D:D"), "A95")
AlertOutput_Sh.Cells(2, 3) = A92Count
Dim A98Count As Long
A98Count = Application.CountIf(AlertSource_Sh.Range("D:D"), "A98")
AlertOutput_Sh.Cells(2, 4) = A98Count
End Sub
Current Output :
Use Dictionaries to build lists of unique values and an array to hold the counts.
Option Explicit
Sub Alert700Count()
Dim wsData As Worksheet, wsOut As Worksheet
Dim dictSerNo As Object, dictAlert As Object
Dim arData, arOut, k, rngOut As Range
Dim lastrow As Long, i As Long
Dim serNo As String, alert As String
Dim r As Long, c As Long, t0 As Single: t0 = Timer
Set dictSerNo = CreateObject("Scripting.Dictionary")
Set dictAlert = CreateObject("Scripting.Dictionary")
On Error Resume Next
Application.DisplayAlerts = False
Sheets("AlertOutput").Delete
Application.DisplayAlerts = True
On Error GoTo 0
Sheets.Add.Name = "AlertOutput"
Set wsOut = Sheets("AlertOutput")
Set wsData = Sheets("SourceSheet")
r = 1: c = 1
With wsData
lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
arData = .Range("A1:D" & lastrow).Value2
' get unique serno and alert
For i = 2 To lastrow
serNo = arData(i, 1)
alert = arData(i, 4)
If dictSerNo.exists(serNo) Then
ElseIf Len(serNo) > 0 Then
r = r + 1
dictSerNo.Add serNo, r
End If
If dictAlert.exists(alert) Then
ElseIf Len(alert) > 0 Then
c = c + 1
dictAlert.Add alert, c
End If
Next
' rescan for counts
ReDim arOut(1 To r, 1 To c)
For i = 2 To lastrow
r = dictSerNo(CStr(arData(i, 1)))
c = dictAlert(CStr(arData(i, 4)))
arOut(r, c) = arOut(r, c) + 1
Next
End With
' add headers
arOut(1, 1) = "Serial No"
' sernos and alerts
For Each k In dictSerNo
arOut(dictSerNo(k), 1) = k
Next
For Each k In dictAlert
arOut(1, dictAlert(k)) = k
Next
' output counts
With wsOut
Set rngOut = .Range("A1").Resize(UBound(arOut), UBound(arOut, 2))
rngOut.Value2 = arOut
rngOut.Replace "", 0
.ListObjects.Add(xlSrcRange, rngOut, , xlYes).Name = "Table1"
.Range("A1").AutoFilter
.Range("A1").Select
End With
MsgBox "Done", vbInformation, Format(Timer - t0, "0.0 secs")
End Sub
I am trying to make a Bill of Material list from my CAD software(Creo), this is exported out as a text file and its almost working perferct. The problem is that the text file spits out all parts in model, and I would like to count up duplicates instead of list them out after each other i.e quanties larger then 1.
My code does this nicely if the duplicate is listed in the row above, however if its nested(sub assy) this does not work. In picture below 00151564.asm(level 2) should be listed as quantity=3 but the parts on level 3 should be listed as quantity=1
Below is the original textfile creo spitts out, groups and patterns makes extra unwanteded indents, and material is just sometimes given..
Here is my code:
Sub simen2(Optional myFile As String = "Z:\Prosjekt\33907\Equipment and
materials\Structure\treetool2.txt")
Dim text As String
Dim textline As String
Dim textlineTemp As String
Dim foo As String
Dim output As String
Dim parent As String
Dim grandma As String
Dim greatgrandma As String
Dim greatgreatgrandma As String
Dim partNumber As String
Dim quantity As Integer
Dim material As String
Dim wsOut As Worksheet
Dim i, k As Long
Dim level, levelOld, levelTemp, levelTempOld, subtractLevel As Integer
Dim duplicate As Boolean
Dim levelDictionary As Object
'Init variables
Set wsOut = ThisWorkbook.Worksheets("Output")
subtractLevel = 0
quantity = 1
duplicate = True
partNumberOld = ""
commonNameOld = ""
levelOld = 0
levelTemp = 0
levelTempOld = 0
materialOld = ""
textlineOld = ""
material = "NA"
materialOld = "NA"
text = wsOut.Cells(1, 1).Value
wsOut.Cells.ClearContents
wsOut.Cells(1, 1).Value = text
wsOut.Cells(1, 2).Value = Now
wsOut.Cells(1, 3).Value = myFile
Call write2ExcelHeader(wsOut)
Set levelDictionary = CreateObject("Scripting.Dictionary")
i = 0
k = 1
FileNum = FreeFile()
Open myFile For Input As #FileNum
Line Input #FileNum, foo
Line Input #FileNum, foo
Do Until EOF(FileNum)
k = k + 1
' read in
Line Input #FileNum, textline
' Get level, however group and pattern fuck things up
If InStr(10, textline, "<HTML>") > 0 Or InStr(textline, "Pattern") > 0 Or InStr(textline, "Group") > 0 Then
levelTemp = getLevel(textline)
If levelTemp < levelTempOld Then
subtractLevel = 0
End If
If InStr(textline, "Pattern") > 0 Or InStr(textline, "Group") > 0 Then
subtractLevel = subtractLevel + 1
k = 1
End If
End If
' Grab material
If InStr(textline, "Materials") > 0 Then
Line Input #FileNum, textline
material = Trim(Replace(textline, "<curr>", ""))
End If
'we need to find out if the line has number as first item, i.e trim away spaces, it prints out previous item here...
If InStr(10, textline, "<HTML>") > 0 Then
'textlineTemp = RemoveHTML(textline)
'textlineTemp = Replace(textlineTemp, "Ã", "Ø")
partNumber = getPartNumber(textline)
commonName = getCommonName(textline)
partType = getType(partNumber)
material = "NA"
' add part to dictionary, this is unique parts
If levelDictionary.exists(partNumber) Then
levelDictionary(partNumber) = levelDictionary(partNumber) + 1
Else
levelDictionary.Add partNumber, 1
End If
' Remove duplicates ......
If partNumberOld = partNumber And levelTempOld = levelTemp Then
duplicate = True
quantity = quantity + 1
Else
quantity = 1
duplicate = False
End If
' get family history
level = levelTemp - subtractLevel
LevelArray(level) = partNumber
'lets present result
If Not duplicate Then
If level > 1 Then
parent = LevelArray(level - 1)
Else
parent = "NA"
End If
If level > 2 Then
grandma = LevelArray(level - 2)
Else
grandma = "NA"
End If
If level > 3 Then
greatgrandma = LevelArray(level - 3)
Else
greatgrandma = "NA"
End If
If level > 4 Then
greatgreatgrandma = LevelArray(level - 4)
Else
greatgreatgrandma = "NA"
End If
If i > 0 Then
Call write2Excel(wsOut, i + 2, partNumberOld, commonNameOld, quantityOld, materialOld, levelOld, partTypeOld, parentOld, grandmaOld, greatgrandmaOld, greatgreatgrandmaOld)
End If
i = i + 1
End If
End If
'we always uses previous values for print out
partNumberOld = partNumber
commonNameOld = commonName
levelOld = level
levelTempOld = levelTemp
partTypeOld = partType
quantityOld = quantity
materialOld = material
textlineOld = textline
parentOld = parent
grandmaOld = grandma
greatgrandmaOld = greatgrandma
greatgreatgrandmaOld = greatgreatgrandma
Loop
Close #FileNum
Debug.Print "How many parts " & i
Call DeList(wsOut)
Call CreateList(wsOut, "FilterOutput")
Call totalBOM(levelDictionary)
End Sub
An alternative OO approach using a class module. Output is to a sheet named "Output2".
Update 1 - Added debugging log, creo.log in same folder as workbook.
Option Explicit
Sub ProcessTextFile()
Const TXTFILE = "treetool_Rextroth.txt" '"treetool20210503.txt"
Const MAX_LEVEL = 10
Dim tree() As clsItem, item As clsItem
Dim ruler() As Integer, level As Integer, rs
Dim FileNum As Integer, textline As String, text As String
Dim start_name As Integer, width_name As Integer, n As Long
Dim t0 As Single: t0 = Timer
ReDim tree(MAX_LEVEL)
ReDim ruler(MAX_LEVEL)
FileNum = FreeFile()
Open ThisWorkbook.Path & "\" & TXTFILE For Input As #FileNum
' use first header line to get common name column position
Line Input #FileNum, textline
start_name = InStr(1, textline, "PTC_COMMON_NAME")
width_name = InStr(1, textline, "PRO_MP_") - start_name
' skip
Line Input #FileNum, textline
' set start level and indent
Set tree(0) = New clsItem
tree(0).level = 0
tree(0).id = "NA"
level = 1
ruler(1) = 1
' open log file
Dim fso, ts
Set fso = CreateObject("Scripting.Filesystemobject")
Set ts = fso.createtextfile("creo.log")
' scan text file
n = 2
Do Until EOF(FileNum)
n = n + 1
Line Input #FileNum, textline
rs = ParseLine(textline, n, ts)
If rs(0) = "ASM" Or rs(0) = "PRT" Then
' determine level from indent
level = GetLevel(ruler, rs(2), level, ts)
' create new item
Set item = New clsItem
With item
.itemtype = rs(0)
.id = rs(1)
.name = Mid(textline, start_name, width_name)
.qu = 1
.level = level
.parent = tree(level - 1).id
.creo = n
End With
' build tree
Set tree(level) = item
tree(level - 1).addItem item
' groups or patterns
ElseIf rs(0) = "GRP" Or rs(0) = "PTN" Then
' increase ruler for current level by 2
ruler(level) = ruler(level) + 2
ts.writeline n & " " & rs(0) & " change ruler(" & level & ")=" & ruler(level)
' materials
ElseIf rs(0) = "MTL" Then
If item.itemtype = "PRT" Then
' get material from next line
n = n + 1
Line Input #FileNum, textline
item.material = Trim(Replace(textline, "<curr>", ""))
End If
End If
Loop
' output tree
Application.ScreenUpdating = False
With Sheets("Output2")
text = .Range("A1")
.Cells.ClearContents
.Cells.Clear
.Range("A1") = text
.Range("B1") = Now
.Range("C1") = TXTFILE
With .Range("A2:H2")
.Value2 = Array("Part No", "Common Name", _
"Qu.", "Material", "Level", "Type", "Parent", "Creo Lines")
.Interior.Color = RGB(255, 200, 0)
.Font.Bold = True
End With
' save objects to sheet
tree(0).SaveToWorksheet .Range("A3")
' prettify
.Range("C:C,E:E").HorizontalAlignment = xlCenter
.Columns("H:H").HorizontalAlignment = xlRight
.Columns("A:H").AutoFit
.ListObjects.add(xlSrcRange, .UsedRange.Offset(1), , xlYes).name = "Table2"
.ListObjects("Table2").TableStyle = "TableStyleLight1"
.Activate
.Range("A1").Select
End With
Application.ScreenUpdating = True
MsgBox Format(n, "#,###") & " lines parsed in " & _
Format(Timer - t0, "0.00") & " seconds"
End Sub
' determine level from indent using ruler
Function GetLevel(ByRef ruler, indent, level, ts) As Integer
Dim n As Integer
n = level ' current level
' is this an increase on previous
If indent > ruler(n) Then
ts.writeline "GetLevel before ruler(" & n & ")=" & ruler(n)
n = n + 1
Else
' find previous level
n = 0
Do
n = n + 1
Loop While indent > ruler(n)
End If
ruler(n) = indent ' update
ts.writeline "Level now " & n & " ruler(" & n & ")=" & indent
GetLevel = n
End Function
' determine linetype, partno, indent
Function ParseLine(s As String, n, ts) As Variant
Dim indent As Integer, partno As String
Dim tmp As String, linetype As String
indent = Len(s) - Len(LTrim(s)) ' no of spaces
If InStr(1, s, "<HTML>") Then
tmp = WorksheetFunction.Trim(s)
partno = Split(tmp, " ")(0)
linetype = Right(partno, 3)
ts.writeline vbCrLf & n & " INDENT=" & indent & " '" & s
ElseIf InStr(1, s, "Materials") Then
linetype = "MTL"
ElseIf InStr(1, s, "Group") Then
linetype = "GRP"
ElseIf InStr(1, s, "Pattern") Then
linetype = "PTN"
End If
ParseLine = Array(linetype, partno, indent)
End Function
class module clsItem
========================
Option Explicit
' this class represent a part or assembly
Public id As String ' partno
Public name As String ' common name
Public itemtype As String ' PRT or ASM
Public parent As String
Public level As Integer
Public qu As Integer
Public material As String
Public creo As String ' source line nos in Creo file
Public items As New Collection
' add items
Public Function addItem(obj As clsItem)
' check if exists, if so increment quantity
Dim item As clsItem, bExists As Boolean
For Each item In items
If item.id = obj.id Then
item.qu = item.qu + obj.qu
item.creo = item.creo & " " & obj.creo
bExists = True
Exit For
End If
Next
' does not exist so add new
If Not bExists Then items.add obj, obj.id
End Function
' save object and all children
Public Sub SaveToWorksheet(rng As Range)
Const SP = 5 ' no of spaces to indent at each level
Dim item As clsItem
If level > 0 Then
rng = Space(level * SP) & id
rng.Offset(0, 1) = name
rng.Offset(0, 2) = qu
rng.Offset(0, 3) = material
rng.Offset(0, 4) = level
rng.Offset(0, 5) = itemtype
rng.Offset(0, 6) = parent
rng.Offset(0, 7) = creo
Set rng = rng.Offset(1)
End If
' recurse
For Each item In Me.items
item.SaveToWorksheet rng
Next
End Sub
This scans the Output sheet and creates a new sheet called "No Duplicates". However, with the bugs in your data I suspect this won't work for more complex cases e.g. where an assembly appears in other assemblies at different levels.
Update 1 - dictionary at each level
Sub RemoveDuplicates()
Const START_ROW = 3 ' skip headers
Const COL_PARTNO = 1 ' A
Const COL_QU = 3 ' C
Const COL_LEVEL = 5 ' E
Const COL_TYPE = 6 ' F
Dim wb As Workbook, ws As Worksheet, wsNew As Worksheet
Dim iLastRow As Long, i As Long, r As Long
Dim PartNo As String, isASM As Boolean
Dim dictLevel As Integer, level As Integer
' dictionary at each level
Dim dictASM(10) As Object, arQu As Variant
Set wb = ThisWorkbook
Set ws = wb.Sheets("Output")
iLastRow = ws.Cells(Rows.Count, "A").End(xlUp).Row
ReDim arQu(iLastRow)
For i = 0 To 10
Set dictASM(i) = CreateObject("Scripting.Dictionary")
Next
r = START_ROW
dictLevel = 0
Do
PartNo = Trim(ws.Cells(r, COL_PARTNO))
isASM = Trim(ws.Cells(r, COL_TYPE) = "ASM")
arQu(r) = ws.Cells(r, COL_QU)
level = ws.Cells(r, COL_LEVEL)
If isASM Then
' is this a new assembly clear lower level dictionaries
If level < dictLevel Then
For i = dictLevel To UBound(dictASM)
Set dictASM(i) = CreateObject("Scripting.Dictionary")
Next
dictLevel = level
Else
If dictASM(dictLevel).exists(PartNo) Then
' duplicate assembly
' increment first occurence
i = dictASM(dictLevel)(PartNo)
arQu(i) = arQu(i) + arQu(r)
arQu(r) = 0
level = ws.Cells(r, COL_LEVEL)
' delete lower level items
Do While ws.Cells(r + 1, COL_LEVEL) > level
r = r + 1
arQu(r) = 0 ' delete later
Loop
Else
' first occurence
dictASM(dictLevel).add PartNo, r
End If
End If
End If
r = r + 1
Loop While r < iLastRow
' create new sheet without duplicates
Set wsNew = Sheets.add
wsNew.Name = "No Duplicates"
ws.Range("A2:G2").Copy wsNew.Range("A2") ' header
i = START_ROW
For r = START_ROW To iLastRow
If arQu(r) > 0 Then
ws.Cells(r, 1).Resize(1, 8).Copy wsNew.Cells(i, 1)
wsNew.Cells(i, COL_QU) = arQu(r)
i = i + 1
End If
Next
wsNew.Columns("A:G").AutoFit
MsgBox Format(iLastRow - 1, "#,###") & " rows scanned", vbInformation
End Sub
I am trying to work out the looping on my script but have found it difficult to figure out. I am using this script to find matching data from different sources and reference them together. I would use the built-in functions in excel but it doesn't care about finding the same data more than once.
Read the titles of all the spreadsheets in the book. #Works
Make an array with those titles #Works
Filter out the "current" sheet #Works
Reference each cell in column A on "current" sheet against all the cells on all the pages in column H #Works
If it matches one, take the data from the page it was found on and the data in column G then set that as the value on "current" page in column E #Works
Make the next page in the main sheet array the "current" page and do it all over again #Doesn't Work
I didn't think this would be as complicated as it is, and maybe I'm not helping by not using functions. Got any idea on how to advance inspectSheet correctly?
Sub listsheets()
Dim ws As Worksheet
Dim i As Integer
Dim x As Integer
Dim y As Integer
Dim sheetArray() As Variant
x = 0
y = 0
i = 0
For Each ws In Worksheets
ReDim Preserve sheetArray(i)
sheetArray(i) = ws.Name
i = i + 1
Next ws
Do Until i = 1
i = i - 1
inspectSheet = sheetArray(x)
column = Sheets(inspectSheet).Cells(Rows.Count, "A").End(xlUp).Row
matchArray = Filter(sheetArray, inspectSheet, False, vbTextCompare)
HOLDER = Join(matchArray)
matchSheet = matchArray(y)
Do Until column = 1
currentCell = Sheets(inspectSheet).Cells(column, 1).Value
checkListLength = Sheets(matchSheet).Cells(Rows.Count, "H").End(xlUp).Row
Do Until checkListLength = 1
matchCell = Sheets(matchSheet).Cells(checkListLength, 8).Value
Debug.Print "Checking: " + currentCell + " on " + inspectSheet + " against " + matchCell + " from page " + matchSheet
If currentCell = matchCell Then
Sheets(inspectSheet).Cells(column, 5).Value = matchSheet + " on " + Sheets(matchSheet).Cells(checkListLength, 7).Value
End If
checkListLength = checkListLength - 1
Loop
column = column - 1
Loop
y = y + 1
Loop
x = x + 1
End Sub
I see you already answered your own question, but here's a slightly different approach with fewer counters to track:
Sub listsheets()
Dim wsMatch As Worksheet, wsInspect As Worksheet
Dim currVal
Dim cInspect As Range, cMatch As Range, rngMatch As Range, rngInspect As Range
For Each wsInspect In ThisWorkbook.Worksheets
Set rngInspect = wsInspect.Range("A1:A" & wsInspect.Cells(Rows.Count, "A").End(xlUp).Row)
For Each wsMatch In ThisWorkbook.Worksheets
If wsMatch.Name <> wsInspect.Name Then 'filter out same-name pairs...
Set rngMatch = wsMatch.Range("H1:H" & wsMatch.Cells(Rows.Count, "H").End(xlUp).Row)
For Each cInspect In rngInspect.Cells
currVal = cInspect.Value
For Each cMatch In rngMatch.Cells
If cMatch.Value = currVal Then
cInspect.EntireRow.Columns("E").Value = _
wsMatch.Name & " on " & cMatch.Offset(0, -1).Value
End If
Next cMatch
Next cInspect
End If 'checking these sheets
Next wsMatch
Next wsInspect
End Sub
I got it, I was not resetting my counter variables and needed one more external loop to advance. The finished code is:
Sub listsheets()
Dim ws As Worksheet
Dim i As Integer
Dim x As Integer
Dim y As Integer
Dim limit As Integer
Dim sheetArray() As Variant
x = 0
y = 0
i = 0
For Each ws In Worksheets
ReDim Preserve sheetArray(i)
sheetArray(i) = ws.Name
i = i + 1
Next ws
limit = UBound(sheetArray)
Do Until x = limit
Do Until i = 1
i = i - 1
inspectSheet = sheetArray(x)
Column = Sheets(inspectSheet).Cells(Rows.Count, "A").End(xlUp).Row
matchArray = Filter(sheetArray, inspectSheet, False, vbTextCompare)
HOLDER = Join(matchArray)
matchSheet = matchArray(y)
Do Until Column = 1
currentCell = Sheets(inspectSheet).Cells(Column, 1).Value
checkListLength = Sheets(matchSheet).Cells(Rows.Count, "H").End(xlUp).Row
Do Until checkListLength = 1
matchCell = Sheets(matchSheet).Cells(checkListLength, 8).Value
Debug.Print "Checking: " + currentCell + " on " + inspectSheet + " against " + matchCell + " from page " + matchSheet
If currentCell = matchCell Then
Sheets(inspectSheet).Cells(Column, 5).Value = matchSheet + " on " + Sheets(matchSheet).Cells(checkListLength, 7).Value
End If
checkListLength = checkListLength - 1
Loop
Column = Column - 1
Loop
y = y + 1
Loop
i = UBound(sheetArray)
y = 0
x = x + 1
Loop
End Sub
I have multiple worksheets and each sheet contains skill details.
Sample data
Skills Name
Programs(C#, VB, Python) C#
OS(Windows, Linux)
DB(Oracle, SQL) Oracle
My requirement is, if user put skills as "Programs(C#, VB, Python)", then return next cell value i.e. "C#"
My code.
Private Sub BtnReport_Click()
Dim SkillName As String
Dim SkillRng As Range
Dim rng As Range
'Dim nextblankrow As Long
'Dim lastrow As Long
Dim x As Long
Dim y As Long
Dim val As String
SkillName = ActiveWorkbook.Worksheets("Admin").Range("L4")
If SkillName = "" Then
MsgBox "Select a skill name"
Exit Sub
End If
'Sheets(2).Select
'Set rng = Sheets(2).Range("B14:B100").Find(What:=SkillName)
'MsgBox rng.Value
Sheets("Report").Select
Sheets("Report").Cells.ClearContents
Sheets("Report").Cells(1, 1) = "Skill Name"
Sheets("Report").Cells(1, 2) = "Resource"
'''''For i = 1 To Sheets.Count - 1
''''' Set rng = Sheets(i).Range("B14:C100")
''''' Set SkillRng = rng.Find(What:=SkillName)
''''' If Not SkillRng Is Nothing Then
''''' Sheets("Report").Cells(i + 1, 1) = SkillRng.Value
''''' MsgBox rng.Cells.Value
'''''' Sheets("Report").Cells(i + 1, 2) = rng.Offset(RowoffSet = 1, Columnoffset = 2).Value
'''''' Sheets("Report").Cells(i + 1, 2) = SkillRng.Cells(Selection.Row, Selection.Column + 1).Value
''''' Sheets("Report").Cells(i + 1, 3) = ActiveWorkbook.Worksheets(i).name
''''' End If
'''''Next i
Set rng = Sheets(2).Range("B14:C100")
Set SkillRng = rng.Find(What:=SkillName)
For x = 1 To rng.Rows.Count
For y = 1 To rng.Columns.Count
If rng.Cells(x, y) = SkillRng Then
' Sheets("Report").Cells(2, 2) = rng.Cells(x, y + 1)
MsgBox SkillRng
MsgBox x
MsgBox y
val = Cells(x, y).Value
' val = SkillRng
MsgBox val
End If
Next y
Next x
I believe this will give you everything with little editing for sheet numbers and intended Report cell values
There is difference between sheets and worksheets. Sheets also include charts. So for your purpose use worksheets. Also, you have to skip the Report and Admin Worksheets for evaluation in the loop. If your report and Admin worksheets are (1) and (2) then start loop from 3. If you loop also evaluates these sheets and if it finds skillname in range to find (rng) in these sheets values from these sheets will also appear in your report.
Private Sub BtnReport_Click()
Dim SkillName As String
Dim SkillRng As Range
Dim rng As Range
SkillName = ActiveWorkbook.Worksheets("Admin").Range("L4")
If SkillName = "" Then
MsgBox "Select a skill name"
Exit Sub
End If
Sheets("Report").Select
Sheets("Report").Cells.ClearContents
Sheets("Report").Cells(1, 1) = "Skill Name"
Sheets("Report").Cells(1, 2) = "Resource"
For i = 1 To Worksheets.Count
Set rng = Worksheets(i).Range("B14:C100")
Set SkillRng = rng.Find(What:=SkillName)
k = Sheets("Report").Range("A1").CurrentRegion.Rows.Count + 1
If Not SkillRng Is Nothing Then
Sheets("Report").Cells(k, 1) = SkillName
Sheets("Report").Cells(k, 2) = SkillRng.Value
Sheets("Report").Cells(k, 3) = SkillRng.offset(0,1)
Sheets("Report").Cells(k, 4) = Worksheets(i).name
End If
Next i
End sub
I have a filter in an Excel file (in pivot table), and I have selected quite a lot of items for filtering.
I need to make a reports and list all the seleted items from the filter. However, I do not want to write manually the name of every selected item.
Is there a way to simply copy the selected items in filter and paste them in a different place?
The following VBA subroutine will export the selection criteria of the Autofilter of a given worksheet to a new Worksheet in the Workbook:
Public Sub ExportFilter(ByRef ws As Worksheet)
Dim hsFilter As Worksheet
Dim rFilter As Range, rHeader As Range, rCell As Range, lFilter As Long, lMin As Long, lMax As Long, lStep As Long
Dim bFilterOn As Boolean, lFilterOperator As Long, vFilterCriteria1 As Variant, vFilterCriteria2 As Variant
On Error Resume Next
If Not (ws.AutoFilterMode) Then Exit Sub
Set rFilter = ws.AutoFilter.Range
If rFilter Is Nothing Then Exit Sub
Set rHeader = rFilter.Rows(2)
If hsFilter Is Nothing Then
With ActiveSheet
Set hsFilter = ThisWorkbook.Worksheets.Add
'hsFilter.Visible = xlSheetVeryHidden
.Activate
End With
Else
hsFilter.Rows.Delete
End If
For Each rCell In rHeader.Cells
lFilter = 1 + rCell.Column - rHeader.Cells(1, 1).Column
bFilterOn = ws.AutoFilter.Filters(lFilter).On
hsFilter.Cells(1, lFilter).Value = bFilterOn
If bFilterOn Then
lFilterOperator = ws.AutoFilter.Filters(lFilter).Operator
hsFilter.Cells(2, lFilter).Value = lFilterOperator
If lFilterOperator = xlFilterValues Then '7
vFilterCriteria1 = ws.AutoFilter.Filters(lFilter).Criteria1
Set vFilterCriteria2 = Nothing
lMin = LBound(vFilterCriteria1)
lMax = UBound(vFilterCriteria1)
For lStep = lMin To lMax
hsFilter.Cells(3 + lStep, lFilter).NumberFormat = "#"
vFilterCriteria2 = vFilterCriteria1(lStep)
If Len(CStr(vFilterCriteria2)) > 1 And Left(CStr(vFilterCriteria2), 1) = "=" Then
vFilterCriteria2 = Mid(vFilterCriteria2, 2, Len(vFilterCriteria2) - 1)
End If
hsFilter.Cells(3 + lStep - lMin, lFilter).Value = vFilterCriteria2
Next lStep
ElseIf (lFilterOperator = 0) Or (lFilterOperator = xlTop10Items) Or (lFilterOperator = xlTop10Percent) Or (lFilterOperator = xlFilterDynamic) Then 'One Filter
vFilterCriteria1 = ws.AutoFilter.Filters(lFilter).Criteria1
Set vFilterCriteria2 = Nothing
hsFilter.Cells(3, lFilter).NumberFormat = "#"
If Len(CStr(vFilterCriteria1)) > 1 And Left(CStr(vFilterCriteria1), 1) = "=" Then
vFilterCriteria1 = Mid(vFilterCriteria1, 2, Len(vFilterCriteria1) - 1)
End If
hsFilter.Cells(3, lFilter).Value = vFilterCriteria1
Else
vFilterCriteria1 = ws.AutoFilter.Filters(lFilter).Criteria1
vFilterCriteria2 = ws.AutoFilter.Filters(lFilter).Criteria2
hsFilter.Cells(3, lFilter).NumberFormat = "#"
If Len(CStr(vFilterCriteria1)) > 1 And Left(CStr(vFilterCriteria1), 1) = "=" Then
vFilterCriteria1 = Mid(vFilterCriteria1, 2, Len(vFilterCriteria1) - 1)
End If
hsFilter.Cells(3, lFilter).Value = vFilterCriteria1
hsFilter.Cells(4, lFilter).NumberFormat = "#"
If Len(CStr(vFilterCriteria2)) > 1 And Left(CStr(vFilterCriteria2), 1) = "=" Then
vFilterCriteria2 = Mid(vFilterCriteria2, 2, Len(vFilterCriteria2) - 1)
End If
hsFilter.Cells(4, lFilter).Value = vFilterCriteria2
End If
End If
Next rCell
Set rFilter = Nothing
Set rHeader = Nothing
Set vFilterCriteria1 = Nothing
Set vFilterCriteria2 = Nothing
End Sub
You can use the advanced filter by criteria and use the Copy to option.
Data - Advanced (on the sort & filter section)
Here is how https://support.office.com/en-us/article/filter-by-using-advanced-criteria-4c9222fe-8529-4cd7-a898-3f16abdff32b?NS=EXCEL&Version=90&SysLcid=1033&UiLcid=1033&AppVer=ZXL900&HelpId=21102&ui=en-US&rs=en-US&ad=US