SaveAs failing despite not changing the code - excel

I have a function in a larger macro that helps reformat and clean up some data from a specific measurement file so that it can be used by the rest of the macro. I recently needed to update some of the data clean-up & reformatting that the function does based on guidance from the department that uses the macro. Those changes work fine, but now the .SaveAs is failing on Error 1004 "SaveAs method of object '_Workbook' failed".
I compared the old & new versions in a text comparison program (UltraCompare) and the changes definitely shouldn't impact the SaveAs. If I stop the macro at the point just before SaveAs and manually save that works successfully, so nothing in the file itself is blocking the save, nor is it a permissions change I didn't know about (which is extra not likely since the test folder is a child of my Desktop).
To go through some of the answers I've seen to other questions on this error
I don't use ActiveWorkbook to save, I create the workbook when setting a workbook variable
I don't use a date in the save as file name
There are no hyperlinks in the workbook
Excel doesn't throw a prompt, and alerts are left on prior to the SaveAs line
As mentioned above, the save is to a folder off my Desktop, so network drive mapping involved
Some additional things I've tried:
During debug, creating a new variable immediately prior to the .SaveAs line & populating it with a new file name in the same folder, and using that in the .SaveAs in place of the replace
Again with a new variable prior to .SaveAs that specifies a different folder
Specifying FileFormat:=51
All that said, here's the code, if anyone has ideas I'm game:
Function MergeCDC(sw As StatWin, fpath As String, BadDateRef As Range, Optional FromComb As Boolean = False) As Boolean
'StatWin is a custom form with a text box for printing status text to the user & a progress bar. fpath is the full file path of the file to be used as a string (folder path & file name including file extension)
'BadDateRef is a cell in the workbook that holds this function that holds the date 1/1/1900 which is used by the file being processed to indicate no date (i.e. the field should be null, but the DBAs
'decided to be weird so we have to deal with it)
'FromComb is a way to know if this function was called by a specific other function, so that run time tracking can be handled correctly
'Check if we're blocked on CDC (this prevents the function from trying to run again if it's called a second (or greater) time after failing)
If sw.CDCBlock Then
MergeCDC = False
Exit Function
End If 'else continue
Dim src As Workbook
Set src = Workbooks.Open(fpath) 'No need to check if the CDC workbook is present as that was done prior to this function being invoked
Dim ry As Worksheet
Dim ytd As Worksheet
Dim m As Workbook
Set m = Workbooks.Add
Dim ms As Worksheet
Set ms = m.Worksheets(1)
Dim ret As Boolean
ret = False
Dim c As Long
Dim r As Long
Dim ryc As Long
Dim temp() As Long
Dim msc As Long
Dim z As Integer
Dim yfnd As Boolean
Dim rfnd As Boolean
'Update the RunStat sheet such that we track CDC data merge as it's own item
If FromComb Then
sw.RStat.Range("A" & sw.StatRow + 2).Value = sw.RStat.Range("A" & sw.StatRow + 1).Value
sw.RStat.Range("B" & sw.StatRow + 2).Value = sw.RStat.Range("B" & sw.StatRow + 1).Value 'Bump start time for combined list being created
sw.RStat.Range("A" & sw.StatRow + 1).Value = sw.RStat.Range("A" & sw.StatRow).Value 'bump start for creation of combined source file
sw.RStat.Range("B" & sw.StatRow + 1).Value = sw.RStat.Range("B" & sw.StatRow).Value
Else
sw.RStat.Range("A" & sw.StatRow + 1).Value = sw.RStat.Range("A" & sw.StatRow).Value 'bump start for creation of CDC list
sw.RStat.Range("B" & sw.StatRow + 1).Value = sw.RStat.Range("B" & sw.StatRow).Value
End If
sw.RStat.Range("A" & sw.StatRow).Value = "CDC Merge"
sw.RStat.Range("B" & sw.StatRow).Value = Now()
'Determine which sheet is which
z = 1
yfnd = True
rfnd = True
Do While z <= src.Worksheets.Count And (yfnd Or rfnd)
If InStr(1, UCase(src.Worksheets(z).Name), "YTD") > 0 Then
yfnd = False
Set ytd = src.Worksheets(z)
ElseIf InStr(1, UCase(src.Worksheets(z).Name), "RY") > 0 Then
rfnd = False
Set ry = src.Worksheets(z)
End If
z = z + 1
Loop
'Check we found both sheets
If rfnd Or yfnd Then
Call Err("Unable to locate the RY and/or YTD worksheets in the Unedited CDC file. Please update the file such that the YTD worksheet includes 'YTD' in its name, and the RY" _
& " worksheet includes 'RY' in its name. This error prevents any list utilizing CDC data from being completed.", sw)
MergeCDC = False
sw.CDCBlock = True
Exit Function
End If 'else continue as normal
'Prep the two worksheets
temp = CDCPrep(ry, True, sw)
ryc = temp(0)
r = temp(1) 'CDCPrep returns the first BLANK row so we will use r as the row to paste to when pasting YTD data
'Prep of RY successful?
If temp(0) <> -1 Then
temp = CDCPrep(ytd, False, sw)
Else
'Close the new workbook without saving
m.Close SaveChanges:=False
End If
'Continue?
If temp(0) <> -1 Then
'Copy the entirety of Rolling Year data
ry.Range("A1:" & ColNumToStr(ryc) & r - 1).Copy
ms.Range("A1").PasteSpecial xlPasteAll
'Start merging in the YTD data. Since we can't assume the columns are in the same order we'll start iterating through the RY columns and copying one column at a time from YTD
c = 0
Do While ms.Range("A1").Offset(0, c).Value <> ""
'Find the matching column in YTD
msc = 0
Dim fnd As Boolean
fnd = False
Do While ytd.Range("A1").Offset(0, msc).Value <> "" And fnd = False
If ytd.Range("A1").Offset(0, msc).Value = ms.Range("A1").Offset(0, c).Value Then
'Found the column. Copy it's data
fnd = True
ytd.Range(ColNumToStr(msc + 1) & "2:" & ColNumToStr(msc + 1) & temp(1)).Copy
Else
msc = msc + 1
End If
Loop
'Did we find a match?
If fnd Then
'Paste the data
ms.Range("A" & r).Offset(0, c).PasteSpecial xlPasteAll
Else
Call Err("Unable to locate the " & ms.Range("A1").Offset(0, c).Value & " column in the Yr To Date data. The list will be generated, but will be missing these values for items found only" _
& " in the Yr To Date data.", sw)
End If
c = c + 1
Loop
'Get the last row of data so we can sort the merged data
r = r + temp(1)
'Check that is the last row
Do While ms.Range("A" & r).Value <> "" And r < 600000 'ridiculously high value, but serves as a fail-safe to keep from hitting end of sheet and not having found data end
r = r + 1
Loop
'Sort the data and remove duplicates according to the current month (Jan - Jun: RY rows preferred to YTD; Jul - Dec: YTD preferred)
If Month(sw.CurDate) < 7 Then
'RY preferred
ms.Sort.SortFields.Clear
ms.Sort.SortFields.Add Key:=Range _
("A2:A" & r), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:= _
xlSortNormal
ms.Sort.SortFields.Add Key:=Range _
("B2:B" & r), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:= _
xlSortNormal
With ms.Sort
.SetRange Range("A1:" & ColNumToStr(c + 1) & r + temp(1))
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
Else
'YTD preferred
ms.Sort.SortFields.Clear
ms.Sort.SortFields.Add Key:=Range _
("A2:A" & r), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:= _
xlSortNormal
ms.Sort.SortFields.Add Key:=Range _
("B2:B" & r), SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:= _
xlSortNormal
With ms.Sort
.SetRange Range("A1:" & ColNumToStr(c + 1) & r + temp(1))
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End If
ms.Range("A1:" & ColNumToStr(c + 1) & r + temp(1)).RemoveDuplicates Columns:=1, Header:=xlYes
'Delete the MergeKey & Source columns
ms.Range("A:B").Delete Shift:=xlLeft
'In order to be processed correctly by other functions later certain target values (Last Test Date, Last Test Value) need to be inserted as new SubMeasures (i.e. new rows)
Dim i As Long
Dim ik As String
Dim sm As String
Dim nc As String
Dim ltd As String
Dim ltv As String
Dim td As String
i = 0
fnd = True
'To add the rows we need to be able to tell when we're on the first row of data for a particular item. Meaning we need to know the column holding ItemKey
Do While ms.Range("A1").Offset(0, i).Value <> "" And fnd
Select Case LCase(ms.Range("A1").Offset(0, i).Value)
Case "itemkey"
mk = ColNumToStr(i + 1)
Case "submeasure"
sm = ColNumToStr(i + 1)
Case "numercnt"
nc = ColNumToStr(i + 1)
Case "date1"
ltd = ColNumToStr(i + 1)
Case "last_val"
ltv = ColNumToStr(i + 1)
Case "terminationdate"
td = ColNumToStr(i + 1)
End Select
i = i + 1
If sm <> "" And ik <> "" And td <> "" And ltd <> "" And nc <> "" And ltv <> "" Then
fnd = False
End If
Loop
If fnd Then
'Couldn't find the needed columns. Report the error
Call Err("Unable to locate the one or more of the following columns in the MergedCDC file: ItemKey, SubMeasure, NumerCnt, TerminationDate, Last Test Date, Last Test Value. This will prevent adding" _
& " rows for Last Test Value & Last Test Date, which will in turn mean those columns will not be correctly populated in any list based on CDC data. All other values from" _
& " the CDC data should be correct though.", sw)
Else
'Add the rows
Dim PM As String
i = 2
Do While ms.Range(mk & i).Value <> ""
If InStr(1, PM, "|" & ms.Range(mk & i).Value & "|") = 0 Then
'First row for this item set all Term Date values are set to the MAX Term Date value for the item. Also determine if they're non-compliant on any measure
Dim y As Integer
Dim tdv As Date
Dim ncv As Integer
y = 0
tdv = DateSerial(1900, 1, 1)
ncv = 1 'Start # 1 so that if any row is non-compliant we can change ncv then (as opposed to having to make sure ALL rows are compliant before setting ncv to 1)
Do While ms.Range(mk & i + y).Value = ms.Range(mk & i).Value
If ms.Range(td & i + y).Value > tdv Then
tdv = ms.Range(td & i + y).Value
End If 'else the term date is older than tdv, and we want to standardize to the max term date, so leave tdv alone
If ms.Range(nc & i + y).Value < ncv Then
ncv = 0
ElseIf ms.Range(sm & i + y).Value = "Tested" Then
'Check if the Test Value = 0 and if the Last Test Date is valid
If (ms.Range(ltd & i + y).Value = DateSerial(1900, 1, 1) Or ms.Range(ltd & i + y).Value = "" Or ms.Range(ltd & i + y).Value = BadDateRef.Value) _
And ms.Range(lbg & i + y).Value = 0 Then
'The value is 0 and the date isn't valid, that means the item wasn't actually tested (in effect if not actuality). Set this row to not tested & update ncv
ms.Range(nc & i + y).Value = 0
ncv = 0
End If 'else the item was tested, the compliance value stays the same, which means ncv doesn't need changed
End If 'Else row indicates item is compliant, which is the default, so no action needed
y = y + 1
Loop
'Replace Term Date values that aren't TDV with TDV (technically, we also replace the row that set TDV, but with the same value)
If tdv <> DateSerial(1900, 1, 1) Then
y = 0
Do While ms.Range(mk & i + y).Value = ms.Range(mk & i).Value
ms.Range(td & i + y).Value = tdv
y = y + 1
Loop
Else
'No actual date found for TDV, just clear the cells setting the format to General so that Excel doesn't re-fill in 1/1/1900
y = 0
Do While ms.Range(mk & i + y).Value = ms.Range(mk & i).Value
ms.Range(td & i + y).NumberFormat = "General"
ms.Range(td & i + y).ClearContents
y = y + 1
Loop
End If
'Copy the current row & insert two copies of it below the current row
ms.Range(i & ":" & i).Copy
ms.Range(i + 1 & ":" & i + 1).Insert Shift:=xlDown
ms.Range(i & ":" & i).Copy
ms.Range(i + 1 & ":" & i + 1).Insert Shift:=xlDown
'Change the SubMeasure cells appropriately
ms.Range(sm & i + 1).Value = "Last Test Date"
ms.Range(sm & i + 2).Value = "Last Test Val"
'Set the compliance cnt value. If the item's last value is 0 AND there is no Last Test Date value, the numercnt value for the two added rows should be 0 so that date & value
' appear (as even though they're compliant, they probably shouldn't be marked as such due to lack of proof). If the value is non-0 then set based on ncv
If ms.Range(lbg & i).Value = 0 & ms.Range(ltd & i + y).Value = "" Then
ms.Range(nc & i + 1).Value = 0
ms.Range(nc & i + 2).Value = 0
Else
ms.Range(nc & i + 1).Value = ncv
ms.Range(nc & i + 2).Value = ncv
End If
'Add the item to PM, a delimited string of ItemKeys for processed items that lets us check if we've already seen a row for this item
PM = PM & "|" & ms.Range(mk & i).Value & "|"
'Add 2 to i (this way the additional incrementing of i below results in us looking at row i + 3, which was the row that had been immediately below row i before we added the two new rows)
i = i + 2
End If 'else proceed to the next row, which happens anyway
i = i + 1
Loop
End If
'Clear out compliant rows so that MergedCDC processes through MFPRocessor (a seperate function that we're setting up the CDC data to go through) like any other source file
'(submeasure present = item non-compliant on measure)
i = 2
Do While ms.Range(mk & i).Value <> ""
If ms.Range(nc & i).Value = 1 Then
ms.Range(i & ":" & i).Delete Shift:=xlUp
Else
i = i + 1
End If
Loop
'Remove 1/1/1900 values from Last Test Date & Term Date
i = 2
Do While ms.Range(mk & i).Value <> ""
If ms.Range(ltd & i).Value = "1/1/1900" Or ms.Range(ltd & i).Value = BadDateRef.Value Then
ms.Range(ltd & i).NumberFormat = "General"
ms.Range(ltd & i).ClearContents
End If
If ms.Range(td & i).Value = "1/1/1900" Or ms.Range(td & i).Value = BadDateRef.Value Then
ms.Range(td & i).NumberFormat = "General"
ms.Range(td & i).ClearContents
End If
i = i + 1
Loop
ret = True
'Save the workbook
m.SaveAs (Replace(fpath, "CDC", "MergedCDC")) 'This code HAD worked, despite none of the changes being anything that should impact this line, this line
Application.DisplayAlerts = False
m.Close SaveChanges:=False
Application.DisplayAlerts = True
Else
'Close the new workbook without saving
m.Close SaveChanges:=False
End If
'Close the original CDC workbook
Application.DisplayAlerts = False
src.Close
Application.DisplayAlerts = True
'Capture completion of CDC merge
sw.RStat.Range("C" & sw.StatRow).Value = Now()
sw.StatRow = sw.StatRow + 1
MergeCDC = ret
End Function

If you haven't changed your code then here's a few things to check which could be causing the error:
Workbook object is out of context - ensure that you are using only one instance of Excel, if your data and workbook are in different instances then they wont be able to reach each other. When your code breaks at the error, add the workbook to your watch list to see if it reachable.
Filepath is unreachable - when the code breaks at this error, take the value of Replace(fpath, "CDC", "MergedCDC") without the filename at the end, and paste it into windows explorer and check that it is reachable.

Related

Excel VBA script not working when grouping multiple levels

I have an excel document that runs a VBA script that I use user forms to input data. The script works fine, except for the grouping. There are 2 groups. The first is at the Customer Name, which works fine. The second is at the Effort Name, which does not. It groups the effort, but when grouped it still displays the last row. The developer I hired to write the script said that this error appears to be a bug in Excel or for some reason by design when two groups have the same last row.
Does anyone have a solution?
Images show the macros script and grouping Image of marcos
Image of grouping
Below is the VBA script that was written for creating the effort via user form.
Private Sub ButtonAddEffort_Click()
Dim c As Object
Dim sht As Worksheet
Dim foundrow As Long
Dim blassign As Boolean
Dim x As Long
Dim rowstart As Long
Dim rowend As Long
Dim i As Long
Dim rowstarteffort As Long
If IsNull(Me.txtProjectNumberLocate) Or Me.txtProjectNumberLocate = "" Then
MsgBox "Please enter a project number."
Me.txtProjectNumberLocate.SetFocus
Exit Sub
End If
If IsNull(Me.txtEffortName) Or Me.txtEffortName = "" Then
MsgBox "Please enter an effort name."
Me.txtEffortName.SetFocus
Exit Sub
End If
If Not IsNull(Me.txtStartDate) And Me.txtStartDate <> "" Then
If Not IsDate(Me.txtStartDate) Then
MsgBox "Please enter a valid start date in 'mm/dd/yyyy' format."
Me.txtStartDate.SetFocus
Exit Sub
End If
End If
If Not IsNull(Me.txtFinishDate) And Me.txtFinishDate <> "" Then
If Not IsDate(Me.txtFinishDate) Then
MsgBox "Please enter a valid finish date in 'mm/dd/yyyy' format."
Me.txtFinishDate.SetFocus
Exit Sub
End If
End If
Set sht = Sheets("Sheet1")
Set c = sht.Range("F:F").Find(what:=Me.txtProjectNumberLocate, after:=sht.Cells(1, 6), LookIn:=xlValues, lookat:=xlWhole, searchorder:=xlByRows, searchdirection:=xlPrevious, MatchCase:=False)
If Not c Is Nothing Then
foundrow = c.Row
rowstart = foundrow
rowstarteffort = foundrow
Else
foundrow = 0
End If
If foundrow = 0 Then
MsgBox "Could not find project # " & Me.txtProjectNumberLocate
Exit Sub
End If
''any efforts exist1
Set c = sht.Range("A:A").Find(what:="*", after:=sht.Cells(foundrow, 1), LookIn:=xlValues, lookat:=xlPart, searchorder:=xlByRows, searchdirection:=xlNext, MatchCase:=False)
If Not c Is Nothing Then
foundrownext = c.Row
Else
foundrownext = 0
End If
If foundrownext > foundrow Then
foundrow = foundrownext - 1
End If
'check work order format
For x = 1 To 8
If Not IsNull(Me("txtworkorder" & x)) And Me("Txtworkorder" & x) <> "" Then
If Me("CheckBox" & x) = True Then
If Len(Me("txtWorkOrder" & x)) <> 8 Then
MsgBox "Work order numbers must be in 'xxxx-xxx' format."
Me("txtWorkOrder" & x).SetFocus
Exit Sub
End If
If InStr(1, Me("txtWorkOrder" & x), "-") = 0 Then
MsgBox "Work order numbers must be in 'xxxx-xxx' format."
Me("txtWorkOrder" & x).SetFocus
Exit Sub
End If
If Mid(Me("txtworkorder" & x), 5, 1) <> "-" Then
MsgBox "Work order numbers must be in 'xxxx-xxx' format."
Me("txtWorkOrder" & x).SetFocus
Exit Sub
End If
If InStr(1, Left(Me("txtWorkOrder" & x), 4), "-") <> 0 Then
MsgBox "Work order numbers must be in 'xxxx-xxx' format."
Me("txtWorkOrder" & x).SetFocus
Exit Sub
End If
If InStr(1, Right(Me("txtWorkOrder" & x), 3), "-") <> 0 Then
MsgBox "Work order numbers must be in 'xxxx-xxx' format."
Me("txtWorkOrder" & x).SetFocus
Exit Sub
End If
End If
End If
Next x
i = 0
If foundrownext > 1 Then
sht.Rows(rowstart + 1 & ":" & foundrownext - 1).Select
On Error Resume Next
Selection.Rows.Ungroup
On Error GoTo 0
End If
blassign = False
For x = 8 To 1 Step -1
If Me("CheckBox" & x) = True Then
blassign = True
End If
Next x
If blassign = False Then
sht.Range(foundrow + 1 & ":" & foundrow + 1).EntireRow.Insert shift:=xlDown
sht.Range("B" & foundrow + 1) = Me.txtEffortName
sht.Range("B" & foundrow + 1).Font.Color = 13998939
sht.Range("B" & foundrow + 1).Font.Underline = True
sht.Range("I" & foundrow + 1) = Me.txtStartDate
sht.Range("J" & foundrow + 1) = Me.txtFinishDate
i = 1
Else
sht.Range(foundrow + 1 & ":" & foundrow + 1).EntireRow.Insert shift:=xlDown
sht.Range("B" & foundrow + 1) = Me.txtEffortName
sht.Range("B" & foundrow + 1).Font.Color = 13998939
sht.Range("B" & foundrow + 1).Font.Underline = True
sht.Range("I" & foundrow + 1) = Me.txtStartDate
sht.Range("J" & foundrow + 1) = Me.txtFinishDate
For x = 8 To 1 Step -1
If Me("CheckBox" & x) = True Then
sht.Range(foundrow + 2 & ":" & foundrow + 2).EntireRow.Insert shift:=xlDown
sht.Range("F" & foundrow + 2) = Me("txtWorkOrder" & x)
sht.Range("G" & foundrow + 2) = Me("cmbAssign" & x)
i = i + 1
End If
Next x
End If
''group new efforts
If foundrownext <= 1 Then
foundrownext = rowstart + 1
End If
sht.Rows(foundrow + 2 & ":" & foundrownext + i).Select
Selection.Rows.Group
''ungroup and group old project data
rowend = foundrownext + i - 1
sht.Rows(rowstart + 1 & ":" & rowend).Select
Selection.Rows.Group
''
MsgBox "Done!"
End Sub
Private Sub ButtonClose_Click()
Unload Me
End Sub
Private Sub ComboBox1_Change()
End Sub
Private Sub ComboBox2_Change()
End Sub
Private Sub ComboBox3_Change()
End Sub
Private Sub ComboBox4_Change()
End Sub
Private Sub TextBox9_Change()
End Sub
Private Sub UserForm_Click()
End Sub
Outline (group) in Excel requires a summary row, that depending on the settings you have in your computer, should be placed below (default) or above each outline level.
Your situation
What's happening in your spreadsheet is that you currently have the default settings, i.e. summary row should be below the current outline level. And you're grouping the rows 9,10 and 13.
My guess here is that the developer tried to group effort 1 and effort 2 and it didn't work, because to group effort 2 without leaving an additional row would just look like this:
Note: See the 4 dots on the right of rows 13 to 16
The Excel solution
In this case, you need to toggle the settings so the summary rows are above the detail
How to adjust the settings
Outline settings:
Current configuration:
Adjusted configuration
This would allow to have the summary row above details like this:
And when collapsed:
The VBA solution
Now, about the VBA code you have, although it can certainly be improved, I understand it accomplishes your requirements.
I suggest to specially check these two blocks:
Block # 1:
''group new efforts
If foundrownext <= 1 Then
foundrownext = rowstart + 1
End If
sht.Rows(foundrow + 2 & ":" & foundrownext + i).Select
Selection.Rows.Group
Block #2
''ungroup and group old project data
rowend = foundrownext + i - 1
sht.Rows(rowstart + 1 & ":" & rowend).Select
Selection.Rows.Group
I'd suggest the developer to read this article on how and why to avoid select in Excel VBA.
Please let me know if the solution works and remember to mark the answer (tick the check mark at the left) if it does.

Remove duplicates and alert user with VBA

I am important data using a soap call, my problem is that under certain circumstances I may end up with duplicate data.
I can easily remove this data using
WS.Range("A6:O200").RemoveDuplicates Columns:=(2)
However Id like to alert the user when this happens via a MsgBox. Currently I am trying to get this working with some code adapted from another post on here.
Dim dict As Object
' Let Col be the column which warnDupes operates on.
Dim Col As String
Col = "B"
Set dict = CreateObject("scripting.dictionary")
dupeRow = Range(Col & Rows.Count).End(xlUp).Row
On Error Resume Next
For i = dupeRow To 1 Step -1
If dict.Exists(UCase$(Range(Col & i).Value)) = True Then
'range("Y" & i).EntireRow.Delete
WS.Range("A6:O200").RemoveDuplicates Columns:=(2)
'MsgBox ("Hmm...Seems to be a duplicate of " & Range(Col & i).Value & _
" in Cell " & Col & i)
End If
dict.Add UCase$(Range(Col & i).Value), 1
Next
MsgBox ("Duplicate unfullfilled requests where removed")
The problem is of course that this either displays the message for every duplicate value deleted in the loop or even if there are no duplicates (as it does now). Ideally what I want is the remove duplicates to run completely and then alert the user via a message.
Regards
Sam
Dim dict As Object
' Let Col be the column which warnDupes operates on.
Dim Col As String
Dim bCount as Boolean
Col = "B"
Set dict = CreateObject("scripting.dictionary")
dupeRow = Range(Col & Rows.Count).End(xlUp).Row
On Error Resume Next
For i = dupeRow To 1 Step -1
If dict.Exists(UCase$(Range(Col & i).Value)) = True Then
'range("Y" & i).EntireRow.Delete
WS.Range("A6:O200").RemoveDuplicates Columns:=(2)
bCount = True
'MsgBox ("Hmm...Seems to be a duplicate of " & Range(Col & i).Value & _
" in Cell " & Col & i)
End If
dict.Add UCase$(Range(Col & i).Value), 1
Next
If bCount Then
MsgBox ("Duplicate unfullfilled requests where removed")
End If

VBA excel - excel creating empty csv files when using application.ontime

I got a macro that runs every 30 seconds using Application.Ontime. Every iteration creates a new csv file containing 8 columns and between 50 to 100 rows. The Application.Ontime normally runs from 8am to 5pm.
The problem is that sometimes during the day the macro just stop storing data in the csv files but still creates csv files. Hence, it still creates csv files but without any data in it.
EDIT:
The files that are created contain headers (StdTenorArray(0))
The variable StdXXXXXX is defined in another macro (button) and is a global variable
Here is the code:
Sub RunOnTime()
Application.CutCopyMode = False
Set ThisWkb = ThisWorkbook
dTime = Now + TimeSerial(0, 0, 30)
Application.OnTime dTime, "RunOnTime"
Call csvFileArray
Set ThisWkb = Nothing
End Sub
The above code calls this macro:
Sub csvFileArray()
Dim StdTenorArray(), ArkArr(5) As Variant
Dim FileName As String
Dim StdTenorCnt, h, j As Long
Set ThisWkb = ThisWorkbook
Application.CutCopyMode = False
ArkArr(1) = "XXXXXXctb"
ArkArr(2) = "XXXXXctb"
ArkArr(3) = "XXXXctb"
ArkArr(4) = "XXXctb"
ArkArr(5) = "XXctb"
ReDim StdTenorArray(ThisWkb.Sheets(ArkArr(1)).Range("B" & Rows.Count).End(xlUp).Row + ThisWkb.Sheets(ArkArr(2)).Range("B" & Rows.Count).End(xlUp).Row + ThisWkb.Sheets(ArkArr(3)).Range("B" & Rows.Count).End(xlUp).Row + ThisWkb.Sheets(ArkArr(4)).Range("B" & Rows.Count).End(xlUp).Row + ThisWkb.Sheets(ArkArr(5)).Range("B" & Rows.Count).End(xlUp).Row)
'Standard tenors
StdTenorCnt = 1
If StdXXXXXX = True Then
For j = 5 To 29
If UCase(ThisWkb.Sheets(ArkArr(1)).Cells(j, 4)) = True _
And WorksheetFunction.IsNumber(ThisWkb.Sheets(ArkArr(1)).Cells(j, 10)) = True _
And WorksheetFunction.IsNumber(ThisWkb.Sheets(ArkArr(1)).Cells(j, 11)) = True _
And IsDate(ThisWkb.Sheets(ArkArr(1)).Cells(j, 7)) = True _
And IsDate(ThisWkb.Sheets(ArkArr(1)).Cells(j, 8)) = True Then
StdTenorArray(StdTenorCnt) = "" & ThisWkb.Sheets(ArkArr(1)).Cells(j, 5) & ";" & Format(ThisWkb.Sheets(ArkArr(1)).Cells(j, 7), "yyyymmdd") & ";" & Format(ThisWkb.Sheets(ArkArr(1)).Cells(j, 8), "yyyymmdd") & ";" & ThisWkb.Sheets(ArkArr(1)).Cells(j, 9) & ";" & Replace(Format(ThisWkb.Sheets(ArkArr(1)).Cells(j, 10), "##0.00"), ",", ".") & ";" & Replace(Format(ThisWkb.Sheets(ArkArr(1)).Cells(j, 11), "##0.00"), ",", ".") & ";" & ThisWkb.Sheets(ArkArr(1)).Cells(j, 6) & ";JPD"
StdTenorCnt = StdTenorCnt + 1
End If
Next j
End I.
.
.
'more storing in the array like the part above
.
.
StdTenorArray(0) = "Symbol;SpotDate;ValueDate;Removed;Bid;Offer;Tenor;Channel"
Set fs = CreateObject("Scripting.FileSystemObject")
'test
app_path = ThisWkb.Path
Set a = fs.CreateTextFile("" & app_path & "\Test\" & Strings.Format(Now(), "dd.mm.yyyy") & " " & Strings.Format(Now(), "hh.mm.ss") & "." & Strings.Right(Strings.Format(Timer(), "#0.00"), 2) & ".csv", True)
For j = 0 To StdTenorCnt - 1
a.WriteLine ("" & StdTenorArray(j) & "")
Next j
a.Close
ThisWkb.Sheets("DKK").Cells(1, 27) = "" & Strings.Format(Now(), "hh:mm:ss") & ":" & Strings.Right(Strings.Format(Timer(), "#0.00"), 2) & ""
ReDim StdTenorArray(0)
Set fs = Nothing
Set a = Nothing
Set ThisWkb = Nothing
Application.CutCopyMode = False
End Sub
Hope someone have a solution to this problem or maybe can point me in the right direction.
Have a nice weekend.
\Kristian

excel vba step thru rows faster

the code below works 100%. It scans for a match in Column B and copies and renames a group of cells when a match is found. However the is a line For lRow = Sheets("HR-Calc").Cells(Cells.Rows.count, "b").End(xlUp).Row To 7 Step -1
Where the step -1 will scan row by row from the bottom of the sheet until a match is found. It would be much easier if the step was set to End.(xlUp) instead of -1. searching every row is overkill because of how the data is set up End.(xlUp) would massive cut down the run time.
Is something like this possible?
Sub Fill_CB_Calc()
M_Start:
Application.ScreenUpdating = True
Sheets("summary").Activate
d_input = Application.InputBox("select first cell in data column", "Column Data Check", Default:="", Type:=8).Address(ReferenceStyle:=xlA1, RowAbsolute:=True, ColumnAbsolute:=False)
data_col = Left(d_input, InStr(2, d_input, "$") - 1)
data_row = Right(d_input, Len(d_input) - InStr(2, d_input, "$"))
Application.ScreenUpdating = False
Sheets("summary").Activate
Range(d_input).End(xlDown).Select
data_last = ActiveCell.Row
If IsEmpty(Range(data_col & data_row + 1)) = True Then
data_last = data_row
Else
End If
For j = data_row To data_last
CBtype = Sheets("summary").Range(data_col & j)
Sheets("HR-Calc").Activate
For lRow = Sheets("HR-Calc").Cells(Cells.Rows.count, "b").End(xlUp).Row To 7 Step -1
If Sheets("HR-Calc").Cells(lRow, "b") = CBtype Then
CBend = Sheets("HR-Calc").Range("C" & lRow).End(xlDown).Row + 1
Sheets("HR-Calc").Rows(lRow & ":" & CBend).Copy
CBstart = Sheets("HR-Calc").Range("c50000").End(xlUp).Row + 2
ActiveWindow.ScrollRow = CBstart - 8
Sheets("HR-Calc").Range("A" & CBstart).Insert Shift:=xlDown
CBold = Right(Range("c" & CBstart), Len(Range("C" & CBstart)) - 2)
box_name = Sheets("summary").Range(data_col & j).Offset(0, -10)
CBnew = Right(box_name, Len(box_name) - 2) & "-" ' <--this is custom and can be changed based on CB naming structure
If CBnew = "" Or vbCancel Then
End If
CBend2 = Range("c50000").End(xlUp).Row - 2
Range("C" & CBstart + 1 & ":" & "C" & CBend2).Select
Selection.Replace What:=CBold & "-", Replacement:=CBnew, LookAt:=xlPart, _
SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
Range("C" & CBstart).FormulaR1C1 = "CB" & Left(CBnew, Len(CBnew) - 1)
GoTo M_Start2
Else
End If
Next lRow
M_Start2:
Next j
YN_result = MsgBox("Fill info for another block/inverter?", vbYesNo + vbExclamation)
If YN_result = vbYes Then GoTo M_Start
If YN_result = vbNo Then GoTo jumpout
jumpout:
' Sheets("summary").Range(d_input).Select
Application.ScreenUpdating = True
End Sub
I'm not sure if this will help but I've had a great performance increase with pulling the entire range you need to loop through into a variant array and then looping through the array. If I need to loop through large data sets, this method has worked out well.
Dim varArray as Variant
varArray = Range(....) 'set varArray to the range you're looping through
For y = 1 to uBound(varArray,1) 'loops through rows of the array
'code for each row here
'to loop through individual columns in that row, throw in another loop
For x = 1 to uBound(varArray, 2) 'loop through columns of array
'code here
Next x
Next y
You can also define the column indexes prior to executing the loop. Then you only need to execute the you need to pull those directly in the loop.
'prior to executing the loop, define the column index of what you need to look at
Dim colRevenue as Integer
colRevenue = 5 'or a find function that searches for a header named "Revenue"
Dim varArray as Variant
varArray = Range(....) 'set varArray to the range you're looping through
For y = 1 to uBound(varArray,1) 'loops through rows of the array
tmpRevenue = CDbl(varArray(y, colRevenue))
Next y
Hope this helps.
Look at doing a .find from the bottom up.
Perform a FIND, within vba, from the bottom of a range up
That will eliminate the need to do the for loop from the last row to the first occurrence of the value you want to locate.

How to optimize Macro Code That Looks into 2 Worksheets

Problem:
I have 1 Excel Sheet with 2 tabs
Tab 1 = Shipment Package
Tab 2 = Mass Update Steps
I want to go through all the values in column B of Tab 2 one by one.
As I go through each row in Tab 2, I will select and copy the values in column C and D of Tab 2.
After selecting and copying, I want to find Tab 2-column B's corresponding values in Tab 1 column G.
If a match is found, I will select column E of Tab 1 (in row where the match was found), and paste there the values copied from Tab 2.
So far this is the code I have which works. However the values from being searched are hard coded. With the values growing in number in Tab 2, the code is hard to maintain. I would like to optimize it. I have googled several possible solutions. But I keep on getting these run-time errors when declaring or setting the range for the 2 sheets. Here is my code.
Private Sub btn_Updt_Steps_Click()
Dim lastRow As Long
With Sheets("Shipment Package")
.Activate
lastRow = .Range("G65000").End(xlUp).Row
For i = 1 To lastRow
If (InStr(1, .Range("G" & i).Value, "Code 001", vbTextCompare) > 0) Then
Sheets("Mass Update Steps").Activate
ActiveSheet.Range("C4:D4").Select
Selection.Copy
Sheets("Shipment Package").Activate
.Range("E" & i).Select
ActiveSheet.Paste
ElseIf (InStr(1, .Range("G" & i).Value, "Code 002", vbTextCompare) > 0) Then
Sheets("Mass Update Steps").Activate
ActiveSheet.Range("C5:D5").Select
Selection.Copy
Sheets("Shipment Package").Activate
.Range("E" & i).Select
ActiveSheet.Paste
ElseIf (InStr(1, .Range("G" & i).Value, "Code 003", vbTextCompare) > 0) Then
Sheets("Mass Update Steps").Activate
ActiveSheet.Range("C6:D6").Select
Selection.Copy
Sheets("Shipment Package").Activate
.Range("E" & i).Select
ActiveSheet.Paste
End If
Next
End With
NotFoundErr:
Debug.Print "value not found"
End Sub
Solution:
Private Sub btn_Updt_Steps_Click()
Dim i As Long
Dim j As Long
Dim Tab2ColC As String
Dim Tab2ColD As String
Dim Tab1ColE As String
Dim Tab1ColF As String
Tab1 = "Shipment Package"
Tab2 = "Mass Update Steps"
With Worksheets(Tab1)
LastRowTab1 = .Cells(.Rows.Count, "G").End(xlUp).Row 'LastRowInColumn(2, Tab1)
End With
With Worksheets(Tab2)
LastRowTab2 = .Cells(.Rows.Count, "B").End(xlUp).Row 'LastRowInColumn(2, Tab2)
End With
For i = 4 To LastRowTab2
Tab2ColumnB = Trim(Sheets(Tab2).Range("B" & i).Value)
Sheets(Tab2).Activate
If Tab2ColumnB <> "" Then
Tab2ColC = "C" & i
Tab2ColD = "D" & i
ActiveSheet.Range(Tab2ColC, Tab2ColD).Copy
For j = 16 To LastRowTab1
Tab1ColumnG = Trim(Sheets(Tab1).Range("G" & j).Value)
If Tab1ColumnG = Tab2ColumnB Then
Sheets(Tab1).Activate
Tab1ColE = "E" & j
Tab1ColF = "F" & j
Sheets(Tab1).Range(Tab1ColE, Tab1ColF).Select
ActiveSheet.Paste
End If
Next
End If
Next
End Sub
For optimization, you can avoid select statements, activate statements etc. Check the code below.
For i = 1 To lastRow
Application.ScreenUpdating = False
If YourCondn1 Then
Sheets("Mass Update Steps").Range("C4:D4").Copy
Sheets("Shipment Package").Range("E" & i).PasteSpecial xlPasteAll
ElseIf YourCondn2 Then
Sheets("Mass Update Steps").Range("C5:D5").Copy
Sheets("Shipment Package").Range("E" & i).PasteSpecial xlPasteAll
ElseIf YourCondn3 Then
Sheets("Mass Update Steps").Range("C6:D6").Copy
Sheets("Shipment Package").Range("E" & i).PasteSpecial xlPasteAll
End If
Application.ScreenUpdating = True
Next
Adding the code that you require. Hope this will work. I haven't tested it. Please check.
Private Sub btn_Updt_Steps_Click()
'Finding LastRow in Tab 2
Tab1 = "Shipment Package"
Tab2 = "Mass Update Steps"
With Worksheets(Tab2)
LastRowTab2 = .Cells(.Rows.Count, 2).End(xlUp).Row 'LastRowInColumn(2, Tab2)
End With
MatchFound = 0
For i = 1 To LastRowTab2
'checking whether value in tab2 column b is same as tab1 column g
Tab2ColumnB = Trim(Sheets(Tab2).Range("B" & i).Value)
Tab1ColumnG = Trim(Sheets(Tab1).Range("G" & i).Value)
If Tab2ColumnB = Tab1ColumnG Then
Tab2ColumnC = Trim(Sheets(Tab2).Range("C" & i).Value)
Tab2ColumnD = Trim(Sheets(Tab2).Range("D" & i).Value)
Sheets(Tab1).Range("E" & i).Value = Tab2ColumnC
Sheets(Tab1).Range("F" & i).Value = Tab2ColumnD
MatchFound = MatchFound + 1
End If
Next
If MatchFound = 0 Then
MsgBox "No matches found"
ElseIf MatchFound > 0 Then
MsgBox MatchFound & " matches were found."
End If
End Sub
I think you can achieve what you want with simple Excel formulas.
In Shipment Package, type the following into E1 and F1 and then drag formula down:
E1 = VLOOKUP(G1,'Mass Update Steps'!$B$1:$D$20,2,0)
F1 = VLOOKUP(G1,'Mass Update Steps'!$B$1:$D$20,3,0)
NB - you'll need to amend $B$1:$D$20 depending on how much data you have in Mass Update
Finally, this assumes that there is always a match. If not, and you want to get rid of those pesky #N/A values, then update the formuals with ISNA e.g.
E1 = IF(ISNA(VLOOKUP(G1,'Mass Update Steps'!$B$1:$D$4,2,0)),"",VLOOKUP(G1,'Mass Update Steps'!$B$1:$D$4,2,0))
Hope that helps.

Resources