Bypassing hyperlink/url time out with error handler - excel

I am writing some code that opens up a number of files via a url. This all works fine, however after a while the server I am pulling this data from blocks me, which is throwing up an error message.
What I have tried to do is create an error handler that resets the error and then continues from the top after waiting 5 seconds. I have tried two things
On error resume next, to skip that line. This doesn't seem to do anything as the code still times out.
Go to error handler, wait 5 seconds, reset the error and then continue where the code already was.
Any ideas what I am doing wrong. example file paths below;
https://query1.finance.yahoo.com/v7/finance/download/GBPUSD=X?period1=946684800&period2=9999999999&interval=1d&events=history
https://query1.finance.yahoo.com/v7/finance/download/GBPCNY=X?period1=946684800&period2=9999999999&interval=1d&events=history
https://query1.finance.yahoo.com/v7/finance/download/^NZ50?period1=946684800&period2=9999999999&interval=1d&events=histor
Sub TESTING()
Call START
Dim i As Integer
Workbooks("SHARE PRICE CREATOR.xlsb").Sheets("links").Activate
For i = 2 To Application.WorksheetFunction.CountA(Range("E:E"))
xtable = Cells(i, 5)
xURL = Cells(i, 4).Value
CONTINUE:
On Error GoTo Errhandle
Workbooks.Open xURL, FORMAT:=6, DELIMITER:=","
Workbooks("SHARE PRICE CREATOR.xlsb").Sheets("links").Activate
Cells(i, 6) = "OK"
Next
Errhandle:
On Error Resume Next
If Err.Number > 0 Then
Cells(i, 6) = Err.Number
End If
On Error GoTo 0
Application.Wait (Now + TimeValue("0:00:5"))
GoTo CONTINUE
Call ENDING
End Sub
Thanks
Scott

Some pointers:
I don't think the On Error Resume Next serves any purpose in your ErrHandle
Put Workbooks("SHARE PRICE CREATOR.xlsb").Sheets("links") into a variable and qualify your range calls with that
Avoid implicit Activesheet references
Use Err.Clear to clear error
You will need an Exit Sub for successful completion of all tasks before running into your error handler
You need an exit strategy to avoid potential for infinite loop. I personally would go with a max retries strategy before moving
onto next url and also have a wait every x number of requests to be a good netizen
Generally avoid the spaghetti code effect of GoTo
Declare all your variables with their type. Remove if not used. Use Option Explicit to enforce
Generally:
I don't like GoTos as makes code hard to read and debug. See a possible re-write, with further comments, below:
TODO:
Refactor out code to be less nested with use of helper functions/subs i.e. be more modular.
Code:
Option Explicit 'Use Option Explicit
Public Sub RetrieveYahooData()
Const MAX_RETRIES As Long = 3
Dim i As Long, ws As Worksheet, lastRow As Long 'use Long
Dim wbMain As Workbook, wb As Workbook, xUrl As String 'declare xUrl
Dim xtable As String 'temp assignment.
Start 'what subs are these?
Set wbMain = Workbooks("SHARE PRICE CREATOR.xlsb") ''Put in a variable. This assumes is open.
Set ws = wbMain.Worksheets("links")
lastRow = ws.Cells(ws.Rows.Count, "D").End(xlUp).Row 'You want to count from row 2 I think
If lastRow >= 2 Then
For i = 2 To lastRow
If i Mod 100 = 0 Then Application.Wait Now + TimeSerial(0, 0, 5) 'every n e.g. 100 requests have a pause
numberOfTries = 0
With ws
xtable = .Cells(i, 5).Value '?What is xTable and its datatype? _
Declare it and use Option Explicit at top of code. _
Also, where will it be used?
xUrl = .Cells(i, 4).Value
If xUrl <> vbNullString Then
Do
DoEvents
On Error Resume Next
Set wb = Workbooks.Open(xUrl, Format:=6, DELIMITER:=",") 'add other tests for valid url?
On Error GoTo 0
If Not wb Is Nothing Then 'remember to save and exit do
wb.SaveAs wbMain.Path & "\" & wb.Name, AccessMode:=xlExclusive, ConflictResolution:=Excel.XlSaveConflictResolution.xlLocalSessionChanges 'Credit to #Sorceri https://stackoverflow.com/a/14634781/6241235
wb.Close True
Exit Do
Else
Application.Wait Now + TimeSerial(0, 0, 5)
End If
Loop While numberOfTries < MAX_RETRIES
End If
End With
ws.Cells(i, 6) = IIf(wb Is Nothing, "FAIL", "OK")
Set wb = Nothing
Next
End If
ENDING
End Sub

Related

Error handling in a loop using Resume Next

as a newcomer to VBA any help would be appreciated. The basic point of my program is to loop through columns of the spreadsheet and count the number of non-blank cells in each column, within a specified range.
Here is an example of what my spreadsheet looks like.
1
2
3
1
thing
2
thing
3
thing
When all the cells in the column are blank, VBA throws out a 1004 error, no cells found. What I want to do is say, if a 1004 error occurs, set the count of the non-blank cells (nonBlank = 0) equal to zero, and if no error occurs, count normally. In something like Python, I'd use try/except. Here is my attempt.
For i = 1 To 3
On Error Resume Next
Set selec_cells = Sheet1.Range(Sheet1.Cells(FirstRow, i), Sheet1.Cells(LastRow, i)).SpecialCells(xlCellTypeVisible).Cells.SpecialCells(xlCellTypeConstants)
If Err.Number <> 1004 Then
nonBlank = 0
Else
nonBlank = selec_cells.Count
End If
On Error GoTo -1
Next i
My issue is, when I run this code, it spits out 0 every time, even though column 2 should return 3. Thank you!
Edit: selec_cells is what throws out the error.
Error Handling
There is no On Error Goto -1 in VBA, it's a VB thing (those are links to different pages). A tip would be if you google VBA stuff, just put VBA in front of what you're looking for.
When using On Error Resume Next (defer error trapping), you should 'apply' it on a line or two maximally and 'close' with On Error Goto 0 (disable error trapping) or with another error handler.
Your usage of On Error Resume Next is unacceptable because in this particular case we can test the range: 1. defer error handling, 2. try to set the range, 3. disable error handling. If there was an error the range will not be set hence If Not rg Is Nothing Then which could be translated to something like 'If rg Is Something Then' (double negation) or If a reference to a range has been created Then.
The second solution illustrates a case where the main error handler is handling all errors except the SpecialCells error which has its own error handler. Resume Next means continue with the line after the line where the error occurred. Note the Exit Sub line and note Resume ProcExit where the code is redirected to a label.
The following illustrates two ways how you could handle this. At this stage, I would suggest you use the first one and remember to use the 'closing' On Error Goto 0 whenever you use On Error Resume Next (a line or two).
The Code
Option Explicit
Sub testOnErrorResumeNext()
Const FirstRow As Long = 2
Const LastRow As Long = 11
Dim rg As Range ' ... additionally means 'Set rg = Nothing'.
Dim nonBlank As Long ' ... additionally means 'nonBlank = 0'.
Dim j As Long
For j = 1 To 3 ' Since it's a column counter, 'j' or 'c' seems preferred.
' Since you're in a loop, you need the following line.
Set rg = Nothing
On Error Resume Next
Set rg = Sheet1.Range(Sheet1.Cells(FirstRow, j), _
Sheet1.Cells(LastRow, j)).SpecialCells(xlCellTypeVisible) _
.Cells.SpecialCells(xlCellTypeConstants)
On Error GoTo 0
If Not rg Is Nothing Then
nonBlank = rg.Cells.Count
Else
' Since you're in a loop, you need the following line.
nonBlank = 0
End If
Debug.Print nonBlank
Next j
End Sub
Sub testOnError()
On Error GoTo clearError
Const FirstRow As Long = 2
Const LastRow As Long = 11
Dim rg As Range ' ... additionally means 'Set rg = Nothing'.
Dim nonBlank As Long ' ... additionally means 'nonBlank = 0'.
Dim j As Long
For j = 1 To 3 ' Since it's a column counter, 'j' or 'c' seems preferred.
' Since you're in a loop, you need the following line.
Set rg = Nothing
On Error GoTo SpecialCellsHandler
Set rg = Sheet1.Range(Sheet1.Cells(FirstRow, j), _
Sheet1.Cells(LastRow, j)).SpecialCells(xlCellTypeVisible) _
.Cells.SpecialCells(xlCellTypeConstants)
On Error GoTo clearError
If Not rg Is Nothing Then
nonBlank = rg.Cells.Count
End If
Debug.Print nonBlank
Next j
ProcExit:
Exit Sub ' Note this.
SpecialCellsHandler:
' Since you're in a loop, you need the following line.
nonBlank = 0
Resume Next
clearError:
MsgBox "Run-time error '" & Err.Number & "': " & Err.Description
Resume ProcExit
End Sub
My preference is, wherever possible, to encapsulate the line of code that may cause an error in its own function. The function returns true or false to indicate whether or not there is an error and an out parameter is used to return the value that you want.
This keeps the error testing confined within a very short well defined function.
Sub ttest()
Dim mySheet As Excel.Worksheet
Set mySheet = ThisWorkbook.Sheet1
Dim myIndex As Long
Dim myNonBlank as long
For myIndex = 1 To 3
If AllCellsAreBlank(mySheet.Range(ThisWorkbook.Sheet1.Cells(myFirstRow, myIndex), mySheet.Cells(myLastRow, myIndex)), myIndex, mySelectCells) Then
myNonBlank = 0
Else
myNonBlank = mySelectCells.Count
End If
Next
End Sub
Public Function AllCellsAreBlank(ByRef ipRange As Excel.Range, ByVal ipIndex As Long, ByRef opSelectCells As Range) As Boolean
On Error Resume Next
set opSelectCells = ipRange.SpecialCells(xlCellTypeVisible).Cells.SpecialCells(xlCellTypeConstants)
AllCellsAreBlank = Err.Number <> 0
On Error GoTo 0
End Function
For reference the prefixes I use are
ip: for an input only parameter
iop: for an input parameters that will be changed by the method
op: for a parameter only used to return a value
my: any variable declared within a Method.
I's also suggest you acquire the habit of meaningful descriptive names, myRow, myCol are much more meaningful than i,j, and of ensuring you use fully qualified references rather than the implicit use of the activesheet.

New issue - runtime error - Out of Memory

I am getting an error as Runtime error 7 while running the cleanup names utility as mentioned in code below. I am using 512 GB HDD, 8 GB RAM, I7 processor so should not be a memory issue and still the issue pops up.
My workbook has 123188 defined names which I want to delete using the code below. Is there a way to be more efficient with the code / does someone has a code / built in addin which I can incorporate in a master addin?
The function breaks at
For Each objName In ActiveWorkbook.Names
Any help will be appreciated.
Thanks in advance
Option Explicit
Sub Cleanup_names123()
'
'Deletes all names except for Print_Area, Database, and DB
'Declare variables
Dim objName As Name
Dim strAnswer As String
'Display instructions
strAnswer = MsgBox("This function will delete all named ranges except Print_Area, DB, and Database. If you are not ready to proceed click Cancel to exit.", vbOKCancel)
'If cancelled - exit function
If strAnswer = vbCancel Then End
'If no names found, exit
If ActiveWorkbook.Names.Count = 0 Then
MsgBox "No names found. Macro complete."
End
End If
MsgBox ActiveWorkbook.Names.Count & " name(s) found. It may take a few minutes for the cleanup."
'Delete names
For Each objName In ActiveWorkbook.Names
On Error Resume Next
If InStr(objName.Name, "Database") <> 0 Then
'If Database - no action
ElseIf InStr(objName.Name, "database") <> 0 Then
'If database - no action
ElseIf InStr(objName.Name, "DB") <> 0 Then
'If database - no action
Else
objName.Delete
ThisWorkbook.Names(objName.Name).Delete
End If
Next
On Error GoTo 0
End Sub
If iterating the collection is taking up too much memory you can manually select each item one by one. When deleting items it's important to work backwards from the end because when you delete item 1 then item 2 becomes item 1. So we use Step -1 to work backwards.
To make your guard clause read plainly and avoid empty Ifs I changed the logic to If Not And. I find this more clear. Don't use underscore _ in method names because that is reserved for Event methods.
Option Explicit
Public Sub CleanupNames()
'
'Deletes all names except for Print_Area, Database, and DB
'Declare variables
Dim strAnswer As String
'Display instructions
strAnswer = MsgBox("This function will delete all named ranges except Print_Area, DB, and Database. If you are not ready to proceed click Cancel to exit.", vbOKCancel)
'If cancelled - exit function
If strAnswer = vbCancel Then Exit Sub
Dim NamesCount As Long
NamesCount = ActiveWorkbook.Names.Count
'If no names found, exit
If NamesCount = 0 Then
MsgBox "No names found. Macro complete."
Exit Sub
End If
MsgBox NamesCount & " name(s) found. It may take a few minutes for the cleanup."
'Delete names
Dim iter As Long
For iter = NamesCount To 1 Step -1
Dim objName As String
objName = ActiveWorkbook.Names.Item(iter).Name
On Error Resume Next
If Not InStr(objName, "Database") <> 0 And _
Not InStr(objName, "database") <> 0 And _
Not InStr(objName, "DB") <> 0 Then
ActiveWorkbook.Names(objName).Delete
End If
If iter Mod 5000 = 0 Then ActiveWorkbook.Save
Next iter
End Sub
UPDATE: Added the save code and changed the delete behavior.
Try this. The approach I took was to run a loop backwards from the bottom so that excel doesn't move the items up each time one is deleted and deleting via Index number rather than name. I also made the test a little more efficient I think.
Option Explicit
Sub DeleteNames()
Dim NameCount As Long
Dim Cntr As Long
Dim WkBk As Workbook
Dim TestName As String
Set WkBk = ThisWorkbook
NameCount = ActiveWorkbook.Names.Count
'Delete names
With WkBk
For Cntr = NameCount To 1 Step -1
On Error Resume Next 'not sure you need this but can't hurt
TestName = UCase(.Names(Cntr).NameLocal)
If InStr(TestName, "DATABASE") > 0 Or _
InStr(TestName, "DB") > 0 Then
'If database - no action
Else
.Names(Cntr).Delete
End If
Next Cntr
End With 'WkBk
End Sub
HTH
It may be as simple as turning off calculation and screen updates... this should increase stability and greatly increase speed.
So, before your For...Next loop -
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Then after your For...Next loop -
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
You should also consider running those last two lines after trapping any errors so that you don't leave the settings inactive.
Here is the extra bit about saving you asked for. I haven't coded up your solution as you should be able to modify this accordingly. This counts backwards from 50 and saves every 5 times through the loop with a debug statement so you can see that it works.
Sub quickSaveDemo()
Dim counter As Integer
counter = 50
For i = counter To 1 Step -1
Debug.Print "Loop count - " & i & " - Other stuff here"
If i Mod 5 = 0 Then
Debug.Print "Save here"
ActiveWorkbook.Save
End If
Next i
End Sub

Is there a way to incorporate a timer within a for loop to loop incase code is taking too long to execute?

I have a VBA macro that cycles through a list of 1500 PDF Files Ranging from 60 to 500 pages. The code checks each file from the list to see if it contains a certain keyword obtained from a user. The code seems to bug out sometimes if the file is too big, so I limited each pdf that will be searched to 12 MB.
Now The problem I am having is that randomly the macro will just stall on a random file and not do anything regardless of file size. It will just stay on that file unless I go and move the mouse.
So I was wondering what the best way to tackle this would be? I was thinking of adding an event of moving the mouse before and after the .FindText method, but I think the best way would be to limit the time each file is open to 30 seconds. I am not sure how to incorporate it within the loop though, Thanks.
Also if you have any suggestions on other improvements I would aprreciate it thank you.
Sub PDFSearch()
Dim FileList As Worksheet, Results As Worksheet
Dim LastRow As Long, FileSize As Long
Dim KeyWord As String
Dim TooLarge As Boolean
Dim PDFApp As Object, PDFDoc As Object
Application.DisplayAlerts = False
Set FileList = ThisWorkbook.Worksheets("Files")
Set Results = ThisWorkbook.Worksheets("Results")
LastRow = FileList.Cells(Rows.Count, 1).End(xlUp).Row
KeyWord = InputBox("What Term Would You Like To Search For?")
Results.Rows(3 & ":" & .Rows.Count).ClearContents
For x = 3 To LastRow
TooLarge = False
FileSize = FileLen(FileList.Cells(x, 1).Value) / 1000
If FileSize > 12000 Then TooLarge = True
If TooLarge = False Then
Set PDFApp = CreateObject("AcroExch.App")
If Err.Number <> 0 Then
MsgBox "Could not create the Adobe Application object!", vbCritical, "Object Error"
Set PDFApp = Nothing
Exit Sub
End If
On Error Resume Next
App.CloseAllDocs 'Precautionary - Sometimes It Doesn't Close The File
On Error GoTo 0
Set PDFDoc = CreateObject("AcroExch.AVDoc")
If Err.Number <> 0 Then
MsgBox "Could not create the AVDoc object!", vbCritical, "Object Error"
Set PDFDoc = Nothing
Set PDFApp = Nothing
Exit Sub
End If
If PDFDoc.Open(FileList.Cells(x, 1).Value, "") = True Then
PDFDoc.BringToFront
If PDFDoc.FindText(KeyWord, False, False, True) = True Then
Results.Range("B" & Rows.Count).End(xlUp).Offset(1, 0).Value = FileList.Cells(x, 1).Value
End If
End If
PDFApp.Exit
End If
On Error Resume Next
PDFDoc.BringToFront 'Precautionary - Sometimes Command Doesn't Close The File
PDFApp.Exit
On Error GoTo 0
Set PDFDoc = Nothing
Set PDFApp = Nothing
FileSize = 0
Next x
Application.DisplayAlerts = True
End Sub

VBA Macro Stops/Hangs Excel after about 4000 Iterations

I am posting this on behalf of someone else. Hoping I learn something in the process.
One of my team members is working on an excel macro that loops through the rows in a spreadsheet that contains over 14,000 rows. With each loop, it moves relevant data into a new tab within the workbook. The loop completes successfully unless we use the LastRow variable, or if we tell it to go for more than 400-4500 rows, then it crashes or hangs without any useful error info. The behavior does not change on different machines. We are using Excel 2016 to run the macro. I wanted to share the code with you to see if there is something that is causing it to hang (But why would it work fine for up to 4000 rows, and then quit beyond? I suspect memory issues to be the cause...)
I am sorry if this is answered elsewhere, I am not experienced enough to recognize if certain suggestions apply to this particular code.
Here is the code:
Function SheetExists(shtName As String, Optional wb As Workbook) As Boolean
Dim sht As Worksheet
If wb Is Nothing Then Set wb = ThisWorkbook
On Error Resume Next
Set sht = wb.Sheets(shtName)
On Error GoTo 0
SheetExists = Not sht Is Nothing
End Function
Sub SortProductionIntoWorkcenters()
Dim StartTime As Double
Dim SecondsElapsed As Double
StartTime = Timer
LastRow = Worksheets("TL Production").Cells.SpecialCells(Type:=XlCellType.xlCellTypeLastCell).Row
FirstRow = 3
Dim rng As Range, cel As Range
'The next line that says Cells(LastRow, 4)) is where I can change how may iterations the loop will process
Set rng = Worksheets("TL Production").Range(Cells(FirstRow, 4), Cells(LastRow, 4))
Dim SheetName As String
Dim r As Integer
r = 2
For Each cel In rng
Worksheets("TL Production").Select
If Cells(cel.Row, cel.Column) = "" Then
Cells(cel.Row, cel.Column) = "EMPTY"
End If
SheetName = Worksheets("TL Production").Cells(cel.Row, 4).Value
SheetName = Replace(SheetName, "/", " ")
If Not SheetExists(SheetName) Then
Worksheets.Add.Name = SheetName
End If
Worksheets("TL Production").Rows(cel.Row).Cut
Do While r > 0
If IsEmpty(Worksheets(SheetName).Cells(r, 1)) Then
Worksheets(SheetName).Rows(r).Insert shift:=xlDown
r = 2
Exit Do
End If
r = r + 1
Loop
Next cel
SecondsElapsed = Round(Timer - StartTime, 2)
MsgBox "This code ran successfully in " & SecondsElapsed & " seconds", vbInformation
'MinutesElapsed = Format((Timer - StartTime) / 86400, "hh:mm:ss")
' MsgBox "This code ran successfully in " & MinutesElapsed & " minutes", vbInformation
End Sub
Not an answer, but you would really benefit from simplifying your code. Eg:
For Each cel In rng
Worksheets("TL Production").Select
If cel = "" Then
cel = "EMPTY"
End If
SheetName = cel
etc...
Although I'm not entirely sure what the real issue in your code is (could very well be memory related), I see a couple of things that can improve your code, as well as its performance. See the bottom of the post for my proposal of a revised version of your code.
For Each cel In rng
Worksheets("TL Production").Select
If Cells(cel.Row, cel.Column) = "" Then
Cells(cel.Row, cel.Column) = "EMPTY"
End If
Executing .Select every single loop slows down your code drastically, as each .rows(r).Insert seems to change to another sheet. So your code forces Excel to constantly switch Worksheets. Redrawing the screen is orders of magnitude slower than performing calculations or reading some values from the sheet.
This can be further mitigated by completely switching off screen updating:
Application.ScreenUpdating = False
ws.Select
For Each cel In rng.Cells
...
Next cel
Application.ScreenUpdating = True
As mentioned by #PatrickHonorez, Cells(cel.Row, cel.Column) is a little bit overdoing it. It's a more complicated way of referencing cel - so why not use that directly? :) It also has the pitfall of not necessarily returning the correct cell, due to not being fully referenced. (Cells actually means ActiveWorkbook.ActiveSheet.Cells, so if your Workbook/Sheet change due to whatever reason, your script suddenly runs into trouble.)
If cel.Value = "" Then
cel.Value = "EMPTY"
End If
As mentioned in a comment by #dwirony, the While r > 0 condition in the Do Loop isn't really doing anything. There is no path through your code that allows for r < 2. Also, the way this loop is constructed is the major contributor to the macro's slow execution. (Several thousand rows in the original sheet means we enter this particular loop the equally often, and each time it has to count a little higher, due to the target sheets growing.)
I think this would be a good place to use a dictionary to store the number of the last row you inserted:
Do While r > 0
DoEvents
If IsEmpty(Worksheets(SheetName).Cells(r, 1)) Then
Worksheets(SheetName).Rows(r).Insert shift:=xlDown
dict(SheetName) = r
Exit Do
End If
r = r + 1
Loop
Generally:
Use Option Explicit at the top of any module. It will make your life easier. (Thus the compiler will force you to declare each and every variable you use. This makes your code more concise and eliminates potential typos, among other benefits.) You can also make this the standard in the VBA IDE's options.
If the sheets modified by your macro contain formulas you can deactivate automatic recalculation (if not already set to manual) with Application.Calculation = xlCalculationManual - this will in some cases further reduce execution times. If you want to set it back to automatic afterwards, use Application.Calculation = xlCalculationAutomatic.
Add a line DoEvents to each and every Do Loop you don't perfectly trust. This will allow you stop/pause the macro if it turns out to be an (almost) infinite loop.
My revised version, I tested it with about 6000 rows to be distributed to 3 different worksheets. It took about 2min to complete. Although rows with more data might take longer than my quick mock-up.
Sub SortProductionIntoWorkcenters()
Dim StartTime As Double
Dim SecondsElapsed As Double
Dim LastRow As Long, FirstRow As Long
Dim Ws As Worksheet
Dim Dict As Scripting.Dictionary
StartTime = Timer
Set Dict = New Scripting.Dictionary
Set Ws = Worksheets("TL Production") ' Set the reference to the starting sheet once and then use that
LastRow = Ws.Cells.SpecialCells(Type:=XlCellType.xlCellTypeLastCell).Row
FirstRow = 3
Dim rng As Range, cel As Range
'The next line that says Cells(LastRow, 4)) is where I can change how may iterations the loop will process
Set rng = Ws.Range(Cells(FirstRow, 4), Cells(LastRow, 4))
Dim SheetName As String
Dim r As Long ' Use Long datatype here to prevent integer overflow
r = 2
Application.ScreenUpdating = False
For Each cel In rng.Cells ' make explicit that we are iterating over all cells in range
If cel.Value = "" Then
cel.Value = "EMPTY"
End If
SheetName = Ws.Cells(cel.Row, 4).Value
SheetName = Replace(SheetName, "/", " ")
If Not SheetExists(SheetName) Then
Worksheets.Add.Name = SheetName
End If
Ws.Rows(cel.Row).Cut
If Dict.Exists(SheetName) Then r = Dict(SheetName)
Do
DoEvents
If IsEmpty(Worksheets(SheetName).Cells(r, 1)) Then
Worksheets(SheetName).Rows(r).Insert shift:=xlDown
Dict(SheetName) = r + 1 ' Add one, as the row r is not empty by defition
Exit Do
End If
r = r + 1
Loop
Next cel
Application.ScreenUpdating = True
SecondsElapsed = Round(Timer - StartTime, 2)
MsgBox "This code ran successfully in " & SecondsElapsed & " seconds", vbInformation
'MinutesElapsed = Format((Timer - StartTime) / 86400, "hh:mm:ss")
' MsgBox "This code ran successfully in " & MinutesElapsed & " minutes", vbInformation
End Sub

On error -> cancel what already done

Is there a way to say to the code that, everytime there is an error, cancel what it's previously done?
For example, I have a code which creates 6 workbooks and when there is an error at the half of the code I have to cancel every workbook and launch the macro again!
thanks in advance
You can try:
Application.UnDo
in your error code. It does not always work.
VBA execution causes the Undo history to be erased
code that changes the interface in any way will clear the Undo buffer (stack)
the history is a list (or collection) of strings
Application.CommandBars("Standard").Controls("&Undo").Control.ListCount is 0
but you might be able to do something like this
Option Explicit
Public Sub makeFiles()
Dim i As Long, currentWB As Long, newWBs As Long
On Error GoTo cancelAction
currentWB = Workbooks.Count
For i = 1 To 6
Workbooks.Add
newWBs = newWBs + 1
ActiveSheet.Cells(1, 1).Value = 4
'generate an error
If i = 3 Then Workbooks.Item(Workbooks.Count + 1).Activate
Next
cancelAction:
Do While newWBs > 0 'Workbooks.Count > 1
Workbooks(currentWB + newWBs).Close False
newWBs = newWBs - 1
Loop
End Sub

Resources