I have code that looks for the header "CUTTING TOOL" using a .Find method. It loops through multiple files and multiple worksheets in the opening files.
I have run into the problem that when it goes through multiple worksheets in one open file and the column is empty under the header, it will print out the header, "CUTTING TOOL". It does not do this in the initial worksheet or in workbooks that do not contain multiple worksheets. Any ideas how to fix it?
'(3)
'find CUTTING TOOL on the source sheet'
If Not ws.Range("A1:M15").Find(What:="CUTTING TOOL", LookAt:=xlWhole, LookIn:=xlValues) Is Nothing Then
Set hc = ws.Range("A1:M15").Find(What:="CUTTING TOOL", LookAt:=xlWhole, LookIn:=xlValues)
Set dict = GetValues(hc.Offset(1, 0), "SplitMe")
If dict.count > 0 Then
'add the values to the master list, column 3
Set d = StartSht.Cells(Rows.count, hc2.Column).End(xlUp).Offset(1, 0)
d.Resize(dict.count, 1).Value = Application.Transpose(dict.items)
ElseIf dict = "" Then
StartSht.Cells(Rows.count, hc2.Column).End(xlUp).Offset(1, 0) = "NO CUTTING TOOLS PRESENT"
End If
ElseIf Not ws.Range("A1:M15").Find(What:="TOOL CUTTER", LookAt:=xlWhole, LookIn:=xlValues) Is Nothing Then ' find TOOL CUTTER on sheet
Set hc = ws.Range("A1:M15").Find(What:="TOOL CUTTER", LookAt:=xlWhole, LookIn:=xlValues)
Set dict = GetValues(hc.Offset(1, 0), "SplitMe")
If dict.count > 0 Then
'add the values to the master list, column 3
Set d = StartSht.Cells(Rows.count, hc2.Column).End(xlUp).Offset(1, 0)
d.Resize(dict.count, 1).Value = Application.Transpose(dict.items)
Else
End If
Else
If hc3 Is Nothing Then
StartSht.Range(StartSht.Cells(i, 3), StartSht.Cells(GetLastRowInColumn(StartSht, "C"), 1)) = "NO CUTTING TOOLS PRESENT!"
End If
End If
...
...
End Sub
...
...
'(8)
'get all unique column values starting at cell c
Function GetValues(ch As Range, Optional vSplit As Variant) As Object
Dim dict As Object
Dim rng As Range, c As Range
Dim v
Dim spl As Variant
Set dict = CreateObject("scripting.dictionary")
For Each c In ch.Parent.Range(ch, ch.Parent.Cells(Rows.count, ch.Column).End(xlUp)).Cells
v = Trim(c.Value)
If Not dict.exists(v) Then
If Len(v) > 0 Then
'exclude any info after ";"
If Not IsMissing(vSplit) Then
spl = Split(v, ";")
v = spl(0)
End If
'exclude any info after ","
If Not IsMissing(vSplit) Then
spl = Split(v, ",")
v = spl(0)
End If
End If
dict.Add c.Address, v
End If
If Len(v) = 0 Then
v = "none"
End If
Next c
Set GetValues = dict
End Function
'(9)
'find a header on a row: returns Nothing if not found
Function HeaderCell(rng As Range, sHeader As String) As Range
Dim rv As Range, c As Range
For Each c In rng.Parent.Range(rng, rng.Parent.Cells(rng.Row, Columns.count).End(xlToLeft)).Cells
'copy cell value if it contains some string "holder" or "cutting tool"
If Trim(c.Value) = sHeader Then
Set rv = c
Exit For
End If
Next c
Set HeaderCell = rv
End Function
The problem lies in the GetValue function. When there is no value below the header, the range selection ends up selecting the empty cell plus the heading above it.
You have also not properly implemented the If Len(v) = 0 Then from a previous post. You have added it in a region of the code where the value of v will never get used.
As mentioned in another answer, you should really use early binding for the Dictionary so that the function can return a Dictionary rather than an Object. In the code that uses the GetValue function you are using this:
Set dict = GetValues(hc.Offset(1, 0), "SplitMe")
If dict.Count > 0 Then
' do something...
ElseIf dict = "" Then
' do something else...
End If
This is a problem because your code cannot be sure if it has a dictionary or an empty string. But if you always return a dictionary, even if empty, then you can use:
Set dict = GetValues(hc.Offset(1, 0), "SplitMe")
If dict.Count > 0 Then
' do something...
Else Then
' do something else...
End If
Which is more consistent. If the code uses GetValue, it always gets a Dictionary but it might not contain any values.
There is another problem with your version of GetValues. You are putting the cell address into the dictionary as the key but you are testing the value of the cell against the dictionary to see if it already exists. From yuor code, it looks like you want a dictionary of the unique values. Rather than break your other code that uses d.Items I will change the GetValue function so it stores the cell value in both key and value in the dictionary.
Function GetValues(ch As Range, Optional vSplit As Variant) As Scripting.Dictionary
Dim dict As Scripting.Dictionary
Dim dataRange As Range
Dim cell As Range
Dim theValue As String
Dim splitValues As Variant
Set dict = New Scripting.Dictionary
Set dataRange = ch.Parent.Range(ch, ch.Parent.Cells(Rows.Count, ch.Column).End(xlUp)).Cells
' If there are no values in this column then return an empty dictionary
' If there are no values in this column, the dataRange will start at the row
' *above* ch and end at ch
If (dataRange.Row = (ch.Row - 1)) And (dataRange.Rows.Count = 2) And (Trim(ch.Value) = "") Then
GoTo Exit_Function
End If
For Each cell In dataRange.Cells
theValue = Trim(cell.Value)
If Len(theValue) = 0 Then
theValue = "none"
End If
If Not dict.exists(theValue) Then
'exclude any info after ";"
If Not IsMissing(vSplit) Then
splitValues = Split(theValue, ";")
theValue = splitValues(0)
End If
'exclude any info after ","
If Not IsMissing(vSplit) Then
splitValues = Split(theValue, ",")
theValue = splitValues(0)
End If
dict.Add theValue, theValue
End If
Next cell
Exit_Function:
Set GetValues = dict
End Function
Related
I'm having a bit of trouble with this and I'm not sure why...
My code (such that it is, a work in progress) is getting stuck on this line:
Set starRange = .Range(Cells(title), Cells(LR, 3))
Can I not use a range variable to set a new range in this way?
Sub cellPainter()
Dim ws As Worksheet
Dim starRange, titleRange, found As Range
Dim errorList() As String
Dim i, LR As Integer
i = 0
ReDim errorList(i)
errorList(i) = ""
For Each ws In ActiveWorkbook.Worksheets
With ws
LR = .Cells(.Rows.Count, "C").End(xlUp).Row
Set titleRange = .Range("C4")
If InStr(1, titleRange, "Title", vbBinaryCompare) < 1 Then
Set found = .Range("C:C").Find("Title", LookIn:=xlValues)
If Not found Is Nothing Then
titleRange = found
Else
errorList(i) = ws.Name
i = i + 1
ReDim Preserve errorList(i)
End If
End If
Set starRange = .Range(Cells(titleRange), Cells(LR, 3))
For Each cell In starRange
If InStr(1, cell, "*", vbTextCompare) > 0 Then Range(cell, cell.Offset(0, 2)).Interior.ColorIndex = 40
If InStr(1, cell, "*", vbTextCompare) = 0 Then Range(cell, cell.Offset(0, 2)).Interior.ColorIndex = 0
Next cell
End With
Next ws
If errorList(0) <> "" Then
txt = MsgBox("The following worksheets were missing the Title row, and no colour changes could be made:" & vbNewLine)
For j = 0 To i
txt = txt & vbCrLf & errorList(j)
Next j
MsgBox txt
End If
End Sub
Edit:
Rory cracked it!
When using a variable inside Range, the Cells property is not required:
Set starRange = .Range(titleRange, .Cells(LR, 3))
Sub listClean()
For Each cellA In Range("A:A")
If cellA.Value <> "" Then
For Each cellB In Range("B:B")
If cellB.Value <> "" Then
If StrComp(cellA.Value, cellB.Value) = 0 Then
cellA.Value = ""
End If
End If
Next
End If
Next
MsgBox "Macro Finished"
End Sub
The code basically removes from Range A:A whatever is in range B:B.
Is there anything I can do to speed up this macro? I was thinking VBA could have a way to make ranges into arrays, and then clean the arrays.
This should be very quick.
It uses arrays instead of looping through the ranges.
Sub listClean()
Dim i As Long, t As Long, mtch As Long
Dim aClm() As Variant, bClm() As Variant
Dim outArr() As Variant
ReDim outArr(1 To 1) As Variant
With ActiveSheet
'Load the arrays
aClm = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Value
bClm = .Range(.Cells(1, 2), .Cells(.Rows.Count, 2).End(xlUp)).Value
t = 0
For i = 1 To UBound(aClm, 1)
mtch = 0
'Search for match. If no match found it will error and stay at 0
On Error Resume Next
mtch = Application.WorksheetFunction.Match(aClm(i, 1), bClm, 0)
On Error GoTo 0
'Test whether match was found.
If mtch = 0 Then
t = t + 1
'make output array bigger.
ReDim Preserve outArr(1 To t) As Variant
'Load value into last spot in output array
outArr(t) = aClm(i, 1)
End If
Next i
'Assign values to range from array.
.Range("C1").Resize(UBound(outArr, 1), 1).Value = Application.Transpose(outArr)
End With
MsgBox "Macro Finished"
End Sub
It does put the output in column C. If you want to put it in column A then change,
.Range("C1").Resize(UBound(outArr, 1), 1).Value = Application.Transpose(outArr)
to:
.Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).ClearContents
.Range("A1").Resize(UBound(outArr, 1), 1).Value = Application.Transpose(outArr)
I'm using the below code to remove invalid instances of text, in this case statements starting with colons. I know all of the steps I need to take, but I'm having issues after Autofitering. I've tried iterating through the visible cells using
for x=1 to currentFilter.rows.count
and
for each x in currentFilter.rows
But regardless of how I've tried I keep receiving some sort of error when trying to get rid of the the first character (the colon) by using (basic gist):
Cell Value = Right(Cell Value, Len(Cell Value) - InStr(Cell Value, ",", vbTextCompare))
My full code is as follows:
Sub PRTCheck()
'Column AN is Production Time & Column AP is Rush Time
Dim endRange As Integer, ShipandRush As Range, CommaColons As Collection, cell, i
endRange = ActiveSheet.Cells(Rows.count, "AN").End(xlUp).Row
Set ShipandRush = Union(ActiveSheet.Range("AN2:AN" & endRange), ActiveSheet.Range("AP2:AP" & endRange))
ShipandRush.NumberFormat = "#"
Set CommaColons = FindAllMatches(ShipandRush, ",:")
If Not CommaColons Is Nothing Then
Dim times() As String
For Each cell In CommaColons
times = Split(cell.Value, ",")
For i = LBound(times) To UBound(times)
If InStr(times(i), ":") = 1 Then times(i) = ""
Next
cell.Value = Join(times, ",")
Do While InStr(cell.Value, ",,") <> 0
cell.Value = Replace(cell.Value, ",,", ",", vbTextCompare)
Loop
If InStr(cell.Value, ",") = 1 Then
cell.Value = Right(cell.Value, Len(cell.Value) - 1)
End If
If InStr(Len(cell.Value), cell.Value, ",") = Len(cell.Value) Then
cell.Value = Left(cell.Value, Len(cell.Value) - 1)
End If
Next cell
End If
Set ShipandRush = ActiveSheet.Range("AN1:AN" & endRange)
Dim currentFilter As Range, r
ShipandRush.AutoFilter Field:=1, Criteria1:=":*" 'Starts with colon
Set currentFilter = ShipandRush.Offset(1).SpecialCells(xlCellTypeVisible)
If currentFilter.Rows.count > 0 Then
For r = 1 To currentFilter.Rows.count
'-------Error occurs on the next line-------
currentFilter.Cells(r).Value = Right(currentFilter.Cells(r).Value, Len(currentFilter.Cells(r).Value) - InStr(currentFilter.Cells(r).Value, ",", vbTextCompare))
Next
End If
ActiveSheet.AutoFilterMode = False
End Sub
'Custom find and replace that circumvents 255 character find limitation
Function FindAllMatches(rng As Range, txt As String) As Collection
Dim rv As New Collection, f As Range, addr As String, txtSrch As String
Dim IsLong As Boolean
IsLong = Len(txt) > 250
txtSrch = IIf(IsLong, Left(txt, 250), txt)
Set f = rng.Find(what:=txtSrch, lookat:=xlPart, LookIn:=xlValues, MatchCase:=False)
Do While Not f Is Nothing
If f.Address(False, False) = addr Then Exit Do
If Len(addr) = 0 Then addr = f.Address(False, False)
'check for the *full* value (case-insensitive)
If InStr(1, f.Value, txt, vbTextCompare) > 0 Then rv.Add f
Set f = rng.FindNext(After:=f)
Loop
Set FindAllMatches = rv
End Function
My Question:
What am I doing wrong? How can I iterate through each value in the visible cells and perform the formula I noted above successfully?
You are really only dealing with a single column but I will try to stick with your method of looping through the rows instead of the cells which in this instance are essentially the same thing (although Range.Rows is not the same thing as Range.Cells).
Discontiguous ranges need to be cycled through by their Range.Areas property first and then the Range.Rows property within each area.
dim a as long, r as long
with currentFilter
If .Rows.count > 0 Then
for a = 1 to .Areas.count
For r = 1 To .Areas(a).Rows.count
.Areas(a).Rows(r).Cells(1).Value = _
Right(.Areas(a).Rows(r).Cells(1).Value, _
Len(.Areas(a).Rows(r).Cells(1).Value) - _
InStr(1, .Areas(a).Rows(r).Cells(1).Value, ","))
Next r
Next a
End If
end with
It may be simpler to just use a For Each ... Next.
dim cfr as range
with currentFilter
for each cfr in .Cells
cfr = Right(cfr.Value, Len(cfr.Value) - InStr(1, cfr.Value, ","))
Next cfr
end with
The code works very well but before I added sections (13) and (14), it ran in 6 minutes and now runs in 16 minutes. If there is a way to streamline this to cut down the runtime, that would be extraordinary.
Main part of code grabs values from under the header 'CUTTING TOOL' in various opening files in a designated folder. They are then printed to the workbook with code where all the information is printed to, StartSht, and the function alters the output information so that TL- has exactly 6 numbers following it and CT- has 4, plus an extra 2 if there is a "-" after the four numbers (ie CT-0081-01). If less than the specified length, 0s are added immediately after the "-". If greater than the specific length, 0s are deleted immediately after the "-".
Any suggestions on how to potentially streamline this code or general tips would be great. I have tried implementing the tips at this website but not much changed.
Main Code:
With WB
For Each ws In .Worksheets
'(3)
'find CUTTING TOOL on the source sheet
If Not ws.Range("A1:M15").Find(What:="CUTTING TOOL", LookAt:=xlWhole, LookIn:=xlValues) Is Nothing Then
Set hc = ws.Range("A1:M15").Find(What:="CUTTING TOOL", LookAt:=xlWhole, LookIn:=xlValues)
Set dict = GetValues(hc.Offset(1, 0), "SplitMe")
If dict.count > 0 Then
'add the values to the master list, column 3
Set d = StartSht.Cells(Rows.count, hc2.Column).End(xlUp).Offset(1, 0)
d.Resize(dict.count, 1).Value = Application.Transpose(dict.items)
Else
'if no items are under the CUTTING TOOL header
StartSht.Cells(Rows.count, hc2.Column).End(xlUp).Offset(1, 0) = " "
End If
For k = 2 To StartSht.Range("C2").End(xlDown).Row
ret = ""
str = StartSht.Range("C" & k).Value
ret = ExtractNumberWithLeadingZeroes(str, "TL", 6)
If ret <> "" Then
StartSht.Range("C" & k).Value = "TL-" & ret
Else
'for CT numbers
ret = ExtractNumberWithLeadingZeroes(str, "CT", 4)
If ret <> "" Then
StartSht.Range("C" & k).Value = "CT-" & ret
End If
End If
Next k
...
...
...
Functions:
'(8)
'Get the Values from columns with specified headers
Function GetValues(ch As Range, Optional vSplit As Variant) As Scripting.Dictionary
Dim dict As Scripting.Dictionary
Dim dataRange As Range
Dim cell As Range
Dim theValue As String
Dim splitValues As Variant
Dim counter As Long
Set dict = New Scripting.Dictionary
Set dataRange = ch.Parent.Range(ch, ch.Parent.Cells(Rows.count, ch.Column).End(xlUp)).Cells
' If there are no values in this column then return an empty dictionary
' If there are no values in this column, the dataRange will start at the row
' *above* ch and end at ch
If (dataRange.Row = (ch.Row - 1)) And (dataRange.Rows.count = 2) And (Trim(ch.Value) = "") Then
GoTo Exit_Function
End If
For Each cell In dataRange.Cells
counter = counter + 1
theValue = Trim(cell.Value)
If Len(theValue) = 0 Then
theValue = " "
End If
'exclude any info after ";"
If Not IsMissing(vSplit) Then
splitValues = Split(theValue, ";")
theValue = splitValues(0)
End If
'exclude any info after ","
If Not IsMissing(vSplit) Then
splitValues = Split(theValue, ",")
theValue = splitValues(0)
End If
If Not dict.exists(theValue) Then
dict.Add counter, theValue
End If
Next cell
Exit_Function:
Set GetValues = dict
End Function
'(9)
'find a header on a row: returns Nothing if not found
Function HeaderCell(rng As Range, sHeader As String) As Range
Dim rv As Range, c As Range
For Each c In rng.Parent.Range(rng, rng.Parent.Cells(rng.Row, Columns.count).End(xlToLeft)).Cells
'copy cell value if it contains some string "holder" or "cutting tool"
If Trim(c.Value) = sHeader Then
'If InStr(c.Value, sHeader) <> 0 Then
Set rv = c
Exit For
End If
Next c
Set HeaderCell = rv
End Function
'(10)
'gets the last row in designated column
Function GetLastRowInColumn(theWorksheet As Worksheet, col As String)
With theWorksheet
GetLastRowInColumn = .Range(col & .Rows.count).End(xlUp).Row
End With
End Function
'(11)
'gets the last row in designated sheet
Function GetLastRowInSheet(theWorksheet As Worksheet)
Dim ret
With theWorksheet
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
ret = .Cells.Find(What:="*", _
After:=.Range("A1"), _
LookAt:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
ret = 1
End If
End With
GetLastRowInSheet = ret
End Function
'(12)
'get the file name without the extension
Function GetFilenameWithoutExtension(ByVal FileName)
Dim Result, i
Result = FileName
i = InStrRev(FileName, ".")
If (i > 0) Then
Result = Mid(FileName, 1, i - 1)
End If
GetFilenameWithoutExtension = Result
End Function
'(13)
Public Function ExtractNumberWithLeadingZeroes(ByRef theWholeText As String, ByRef idText As String, ByRef numCharsRequired As Integer) As String
' Finds the first entry of idText, TL/CT, in theWholeText
' Returns the first number found after idText formatted with leading zeroes
Dim returnValue As String
Dim extraValue As String
Dim tmpText As String
Dim firstPosn As Integer
Dim secondPosn As Integer
Dim ctNumberPosn As Integer
returnValue = ""
firstPosn = InStr(1, theWholeText, idText)
If firstPosn > 0 Then
' remove any text before first idText, also remove the first idText
tmpText = Mid(theWholeText, firstPosn + Len(idText))
'if more than one idText value, delete everything after (and including) the second idText
secondPosn = InStr(1, tmpText, idText)
If secondPosn > 0 Then
tmpText = Mid(tmpText, 1, secondPosn)
End If
returnValue = ExtractTheFirstNumericValues(tmpText, 1)
If idText = "CT" Then
ctNumberPosn = InStr(1, tmpText, returnValue)
' Is the next char a dash? If so, must include more numbers
If Mid(tmpText, ctNumberPosn + Len(returnValue), 1) = "-" Then
' There are some more numbers, after the dash, to extract
extraValue = ExtractTheFirstNumericValues(tmpText, ctNumberPosn + Len(returnValue))
End If
End If
'force to numCharsRequired numbers if too short; add 0s immediately after idText
'force to numCharsRequired numbers if too long; eliminate 0s immediately after idText
' The CLng gets rid of leading zeroes and the Format$ adds any required up to numCharsRequired chars
If returnValue <> "" Then
returnValue = Format$(CLng(returnValue), String(numCharsRequired, "0"))
If extraValue <> "" Then
returnValue = returnValue & "-" & extraValue
End If
End If
End If
ExtractNumberWithLeadingZeroes = returnValue
End Function
'(14)
Private Function ExtractTheFirstNumericValues(ByRef theText As String, ByRef theStartingPosition As Integer) As String
Dim i As Integer
Dim j As Integer
Dim tmpText As String
Dim thisChar As String
' Find first number
For i = theStartingPosition To Len(theText)
If IsNumeric(Mid(theText, i, 1)) Then
tmpText = Mid(theText, i)
Exit For
End If
Next i
' Find where the numbers end
For j = 1 To Len(tmpText)
thisChar = Mid(tmpText, j, 1)
If Not IsNumeric(thisChar) Then
tmpText = Mid(tmpText, 1, j - 1)
Exit For
End If
Next j
ExtractTheFirstNumericValues = tmpText
End Function
have you put in break point to see which parts are taking the time ? For example is the For loop in the first part taking very much time ? The easiest way i can see you could speed things up is any time you do a Loop, For Each Cell instead set a variable equal to that range and loop through the variable. This can insanely increase speed especially if your touch a lot of cells. In my experience basically anything to do with cells is the slowest thing in excel. I often convert everything to variables, do all my work, then drop it back down when i am done. I have cut things for 2 hours to 2 minutes doing this.
Make it faster?
A large time saver was moving the section of code that the two functions are called from outside of the looping through files. That way, it would not stop after every file to fix it but rather fix all of the final output at the end. Cut the runtime in half!
I have working code here.
In section (3) it grabs values from a cell under a specific header and prints them to a masterfile. These values typically look like
TL-18273982; 10MM
TL-288762; 76DK
CT-576
N/A
I would like to grab just the information that is before the first semicolon. Not all the cells have a semi colon in them so it would probably need an if statement along the lines of if ; then print everything in front of it.
I have been trying to utilize a split function to do this but I am not very experienced with VBA so I am having some trouble. Any suggestions?
Option Explicit
Sub LoopThroughDirectory()
Const ROW_HEADER As Long = 10
Dim objFSO As Object
Dim objFolder As Object
Dim objFile As Object
Dim MyFolder As String
Dim StartSht As Worksheet, ws As Worksheet
Dim WB As Workbook
Dim i As Integer
Dim LastRow As Integer, erow As Integer
Dim Height As Integer
Dim RowLast As Long
Dim f As String
Dim dict As Object
Dim hc As Range, hc1 As Range, hc2 As Range, hc3 As Range, d As Range
Set StartSht = Workbooks("masterfile.xlsm").Sheets("Sheet1")
'turn screen updating off - makes program faster
Application.ScreenUpdating = False
'location of the folder in which the desired TDS files are
MyFolder = "C:\Users\trembos\Documents\TDS\progress\"
'find the headers on the sheet
Set hc1 = HeaderCell(StartSht.Range("B1"), "HOLDER")
Set hc2 = HeaderCell(StartSht.Range("C1"), "CUTTING TOOL")
'create an instance of the FileSystemObject
Set objFSO = CreateObject("Scripting.FileSystemObject")
'get the folder object
Set objFolder = objFSO.GetFolder(MyFolder)
i = 2
'loop through directory file and print names
'(1)
For Each objFile In objFolder.Files
If LCase(Right(objFile.Name, 3)) = "xls" Or LCase(Left(Right(objFile.Name, 4), 3)) = "xls" Then
'(2)
'Open folder and file name, do not update links
Set WB = Workbooks.Open(fileName:=MyFolder & objFile.Name, UpdateLinks:=0)
Set ws = WB.ActiveSheet
'(3)
'find CUTTING TOOL on the source sheet
Set hc = HeaderCell(ws.Cells(ROW_HEADER, 1), "CUTTING TOOL")
If Not hc Is Nothing Then
Set dict = GetValues(hc.Offset(1, 0))
If dict.count > 0 Then
Set d = StartSht.Cells(Rows.count, hc2.Column).End(xlUp).Offset(1, 0)
'add the values to the masterfile, column 3
d.Resize(dict.count, 1).Value = Application.Transpose(dict.items)
End If
Else
'header not found on source worksheet
End If
'(4)
'find HOLDER on the source sheet
Set hc3 = HeaderCell(ws.Cells(ROW_HEADER, 1), "HOLDER")
If Not hc3 Is Nothing Then
Set dict = GetValues(hc3.Offset(1, 0))
If dict.count > 0 Then
Set d = StartSht.Cells(Rows.count, hc1.Column).End(xlUp).Offset(1, 0)
'add the values to the master list, column 2
d.Resize(dict.count, 1).Value = Application.Transpose(dict.items)
End If
Else
'header not found on source worksheet
End If
'(5)
With WB
'print TDS information
For Each ws In .Worksheets
'print the file name to Column 1
StartSht.Cells(i, 1) = objFile.Name
'print TDS name from J1 cell to Column 4
With ws
.Range("J1").Copy StartSht.Cells(i, 4)
End With
i = GetLastRowInSheet(StartSht) + 1
'move to next file
Next ws
'(6)
'close, do not save any changes to the opened files
.Close SaveChanges:=False
End With
End If
'move to next file
Next objFile
'turn screen updating back on
Application.ScreenUpdating = True
ActiveWindow.ScrollRow = 1
'(7)
End Sub
'(8)
'get all unique column values starting at cell c
Function GetValues(ch As Range) As Object
Dim dict As Object, rng As Range, c As Range, v
Set dict = CreateObject("scripting.dictionary")
For Each c In ch.Parent.Range(ch, ch.Parent.Cells(Rows.count, ch.Column).End(xlUp)).Cells
v = Trim(c.Value)
If Len(v) > 0 And Not dict.exists(v) Then
dict.Add c.Address, v
End If
Next c
Set GetValues = dict
End Function
'(9)
'find a header on a row: returns Nothing if not found
Function HeaderCell(rng As Range, sHeader As String) As Range
Dim rv As Range, c As Range
For Each c In rng.Parent.Range(rng, rng.Parent.Cells(rng.Row, Columns.count).End(xlToLeft)).Cells
If Trim(c.Value) = sHeader Then
Set rv = c
Exit For
End If
Next c
Set HeaderCell = rv
End Function
'(10)
Function GetLastRowInColumn(theWorksheet As Worksheet, col As String)
With theWorksheet
GetLastRowInColumn = .Range(col & .Rows.count).End(xlUp).Row
End With
End Function
'(11)
Function GetLastRowInSheet(theWorksheet As Worksheet)
Dim ret
With theWorksheet
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
ret = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
ret = 1
End If
End With
GetLastRowInSheet = ret
End Function
If you one use Split function, you will love it -> https://msdn.microsoft.com/en-us/library/6x627e5f%28v=vs.90%29.aspx
Look for this example:
Sub TestSplit()
Dim String1 As String
Dim Arr1 As Variant
String1 = "TL-18273982; 10MM"
Arr1 = Split(String1, ";")
Debug.Print "TEST1: String1=" & String1
Debug.Print "TEST1: Arr1(0)=" & Arr1(0)
Debug.Print "TEST1: Arr1(1)=" & Arr1(1)
String1 = "CT-576"
Arr1 = Split(String1, ";")
Debug.Print "TEST2: String1=" & String1
Debug.Print "TEST2: Arr1(0)=" & Arr1(0)
String1 = "N/A"
Arr1 = Split(String1, ";")
Debug.Print "TEST3: String1=" & String1
Debug.Print "TEST3: Arr1(0)=" & Arr1(0)
End Sub
Results:
TEST1: String1=TL-18273982; 10MM
TEST1: Arr1(0)=TL-18273982
TEST1: Arr1(1)= 10MM
TEST2: String1=CT-576
TEST2: Arr1(0)=CT-576
TEST3: String1=N/A
TEST3: Arr1(0)=N/A
Edit:
Maybe simple modification GetValues will resolve problem?
Change function call to:
Set dict = GetValues(hc.Offset(1, 0), "SplitMe")
And change function like this:
'(8)
'get all unique column values starting at cell c
Function GetValues(ch As Range, Optional vSplit As Variant) As Object
Dim dict As Object, rng As Range, c As Range, v
Dim spl As Variant
Set dict = CreateObject("scripting.dictionary")
For Each c In ch.Parent.Range(ch, ch.Parent.Cells(Rows.Count, ch.Column).End(xlUp)).Cells
v = Trim(c.Value)
If Len(v) > 0 And Not dict.exists(v) Then
If Not IsMissing(vSplit) Then
spl = Split(v, ";")
v = spl(0)
End If
dict.Add c.Address, v
End If
Next c
Set GetValues = dict
End Function
Consider:
Public Function PreSemicolon(sIN As String) As String
If InStr(sIN, ";") = 0 Then
PreSemicolon = ""
Exit Function
Else
PreSemicolon = Split(sIN, ";")(0)
End If
End Function
The following VBA code snippet demonstrates the possible solution assuming the text is entered in "A1" cell (Note: it does not need Split() Function):
Sub GetSubstringDemo()
Dim position As Integer
Dim substring As String
position = InStr(Cells(1, 1), ";")
If (position > 0) Then
substring = Left(Cells(1, 1), position - 1)
'or use the following one to exclude "["
'substring = Replace(Left(Cells(1, 1), position - 1), "[", "")
Debug.Print substring
End If
End Sub
The same Sub can be extended to loop through the range of cells (e.g. A1 to A10):
Sub GetSubstringDemo()
Dim position As Integer
Dim substring As String
For i = 1 To 10
position = InStr(Cells(i, 1), ";")
If (position > 0) Then
substring = Replace(Left(Cells(i, 1), position - 1), "[", "")
Debug.Print substring
End If
Next i
End Sub
Hope this may help.
PS. Pertinent to you additional question in comments: business logic is a bit unclear, but following that sample code, it could be modified as :
Set dict = GetValues(hc.Offset(1, 0))
If dict.count > 0 Then
Set d = StartSht.Cells(Rows.count, hc2.Column).End(xlUp).Offset(1, 0)
position = InStr(d.Value, ";")
substring = Replace(Left(d.Value, position - 1), ";", "")
d.Resize(dict.count, 1).Value = Application.Transpose(dict.items)
End If
Best Regards,
Try something like that.I'm not sure about your variables, you might have to adapt them.
You can use instr to locate a character within the string (returning the position of '[' for example). You can then use mid to extract a substing, using the positions of ']' and '['.
openPos = instr (hc , "[") closePos = instr (hc , ";")
if closePos = 0 then
closePos = instr (hc , "]")
end if
dict = mid (hc , openPos+1, closePos - openPos - 1)