I have looked online as much as I could (except for the Microsoft support website, which is blocked at work for some reason). I am trying to simply skip an error. My code written here is simplified but should work the same way.
What my code is supposed to do:
One of my subs creates shapes in a loop and names them (btn_1, btn_2, etc). But before creating them, it calls a sub that tries to delete them so as not to create duplicates. This sub loops through (btn_1, btn_2, etc) and deletes the shapes using:
for i = 1 to (a certain number)
Set shp = f_overview.Shapes("btn_" & i)
shp.delete
next
Of course, it happens that the shape cannot be deleted because it simply does not exist. I have found that most of the time, the reccomended fix is to add (on error resume next) before setting the shape, as I get an error saying it does not exist. I have tried it inside the loop, before the loop, etc, like so:
for i = 1 to (a certain number)
On Error Resume Next
Set shp = f_overview.Shapes("btn_" & i)
shp.delete
next
As far as I understand it is supposed to loop right through if the shape doesn't exist, but I still get the same error whether or not I add the On error resume next! What am I doing wrong?
EDIT: There is no error when the shapes do exist.
I have found that most of the time, the reccomended fix is to add (on error resume next) before setting the shape, as I get an error saying it does not exist.
NO!
The recommended way to handle runtime errors is not to shove them under the carpet and continue execution as if nothing happened - which is exactly what On Error Resume Next does.
The simplest way to avoid runtime errors is to check for error conditions, and avoid executing code that results in 100% failure rate, like trying to run a method on an object reference that's Nothing:
For i = 1 To (a certain number)
Set shp = f_overview.Shapes("btn_" & i)
If Not shp Is Nothing Then shp.Delete
Next
In cases where you can't check for error conditions and must handle errors, the recommended way is to handle them:
Private Sub DoSomething()
On Error GoTo CleanFail
'...code...
CleanExit:
'cleanup code here
Exit Sub
CleanFail:
If Err.Number = 9 Then 'subscript out of range
Err.Clear
Resume Next
Else
MsgBox Err.Description
Resume CleanExit
End If
End Sub
There is nothing WRONG in using OERN (On Error Resume Next) provided you understand what you are doing and how it is going to affect your code.
In your case it is perfectly normal to use OERN
Dim shp As Shape
For i = 1 To (a certain number)
On Error Resume Next
Set shp = f_overview.Shapes("btn_" & i)
shp.Delete
On Error GoTo 0
Next
At the same time ensure that you don't do something like
On Error Resume Next
<Your Entire Procedure>
On Error GoTo 0
This will suppress ALL errors. Use proper error handling as shown by Matt
Edit:
Here is another beautiful example on how to use OERN This function checks if a particular worksheet exists or not.
Function DoesWSExist(wsName As String) As Boolean
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Sheets(wsName)
On Error GoTo 0
If Not ws Is Nothing Then DoesWSExist = True
End Function
If you wish you can also loop through all the sheets to check is the sheet exists or not!
Instead of trying to blindly delete shapes and skipping errors, why not run through the list of known shapes and delete them. Then you don't have to worry about an On Error Resume Next which often ends up being abused.
Sub Test(TheSheet As Worksheet)
Dim Shp as Shape
For Each Shp in TheSheet.Shapes
If left(Shp.Name, 4) = "btn_" Then
Shp.Delete
End if
Next
End Sub
If you want to delete all shapes, remove the If statement. If you want to delete a number of differently named shapes, modify the If statement appropriately.
It sounds like you have the wrong error trapping option set. Within the VBA Editor, Select Tools -> Options. In the window that opens, select the General tab, and pick the Break on Unhandled Errors radio button. This should allow Excel to properly process the On Error Resume Next command.
I suspect that you have Break on All Errors selected.
Try:
On Error Resume Next
for i = 1 to (a certain number)
Set shp = f_overview.Shapes("btn_" & i)
if err<>0 then err.clear else shp.delete
next
on Error Goto 0
Related
I insert an image into the specified area (rngOblastVlozeni). But before inserting the image, I want to remove all the images that have been there so far.
I use the following part of VBA to do this:
Dim shpObjekt As Shape
With rngOblastVlozeni.Parent
For Each shpObjekt In .Shapes
If Application.Intersect(shpObjekt.TopLeftCell, rngOblastVlozeni) Is Nothing Then
Else
If (shpObjekt.Type = msoPicture) Or (shpObjekt.Type = msoLinkedPicture) Then
shpObjekt.Delete
End If
End If
Next shpObjekt
End With
But in some cases I receive error
Run-time error '1004':
Application-defined or object-defined error
The error is in the line If Application.Intersect(shpObjekt.TopLeftCell, rngOblastVlozeni) Is Nothing Then , probably in part shpObjekt.TopLeftCell.
When I tried to capture it in more detail:
I have two pictures and one graph on the sheet
I insert another image in the designated area
If the area is EMPTY, then two shpObjects will pass without error, and the error will not appear until the third one.
If there IS an image in the area, then three shpObjects are OK, and the error is on the fourth.
When I remove both images and the graph, as well as any image in the area, the error still appears (if there is no image, then as the first shpObject if there is an image, then as the second shpObject) - from this I deduce that the error is not with any of these images .
Could there be more to the Shapes collection? For example, from a previous event?
And if so, can someone advise on adding the code so that I "ignore" the error ONLY in the case of this error and ONLY in this part of VBA, and jump to the Next shpObjekt when it occurs?
One more addition - if I use the absolutely same code on another sheet, it doesn't seem to cause this problem, i.e. it happens without any problems - i.e. it seems that I have some extra object on the problematic sheet, which I am not able to find. I tried F5 - Go to all objects, I deleted them, but the problem was not solved - i.e. it is probably not a visible object?
So, question:
is it possible there is another Shape Object on my sheet? Is it possible to identify it somehow?
any reccommendation how to skip part
If Application.Intersect(shpObjekt.TopLeftCell, rngOblastVlozeni) Is Nothing Then
Else
If (shpObjekt.Type = msoPicture) Or (shpObjekt.Type = msoLinkedPicture) Then
shpObjekt.Delete
End If : End If
in case If Application.Intersect(shpObjekt.TopLeftCell, rngOblastVlozeni) Is Nothing generates error 1004 (optimally ONLY this error)?
Thanks :-)
On your specific two questions:
I can't say whether there is another Shape in your sheet without having the sheet here in front of me, but you can use Debug.Print to test (see code below)
Test if shpObjekt and its Address property are not Nothing (again, see code below)
Dim shpObjekt As Shape
With rngOblastVlozeni.Parent
For Each shpObjekt In .Shapes
If Not shpObjekt Is Nothing Then
If Not shpObjekt.TopLeftCell Is Nothing Then
If Application.Intersect(shpObjekt.TopLeftCell, rngOblastVlozeni) Is Nothing Then
Debug.Print "Not intersecting:", shpObjekt.Name, shpObjekt.TopLeftCell.Address, shpObjekt.Type
Else
Debug.Print "Intersecting:", shpObjekt.Name, shpObjekt.TopLeftCell.Address, shpObjekt.Type
If (shpObjekt.Type = msoPicture) Or (shpObjekt.Type = msoLinkedPicture) Then
shpObjekt.Delete
End If
End If
End If
End If
Next shpObjekt
End With
In addition, I think the root cause of the problem you are having is quite likely you are using a For Each to iterate over a Collection (ie of Shapes) while deleting members of that Collection ... it is better to use For Next and loop backwards when deleting ... for this, your code would become
Dim shpObjekt As Shape
Dim index As Long
With rngOblastVlozeni.Parent
For index = .Shapes.Count To 1 Step -1
Set shpObjekt = .Shapes.Item(index)
If Not shpObjekt Is Nothing Then
If Not shpObjekt.TopLeftCell Is Nothing Then
If Application.Intersect(shpObjekt.TopLeftCell, rngOblastVlozeni) Is Nothing Then
Debug.Print "Not intersecting:", shpObjekt.Name, shpObjekt.TopLeftCell.Address, shpObjekt.Type
Else
Debug.Print "Intersecting:", shpObjekt.Name, shpObjekt.TopLeftCell.Address, shpObjekt.Type
If (shpObjekt.Type = msoPicture) Or (shpObjekt.Type = msoLinkedPicture) Then
shpObjekt.Delete
End If
End If
End If
End If
Next index
End With
The following statements (in a loop) end in an error when row 1 is empty.
I would have expected the error to be silently ignored and the next sheet to be processed.
How come the code stops when the on error is active ? I would expect it to ignore the error and continue processing after the skip label.
Sub listSheets()
Dim sh As Worksheet, ar
Dim a
Set a = Application
For Each sh In ThisWorkbook.Sheets
Sheet1.Cells(3 + sh.Index, 1) = sh.Name
On Error GoTo skip
ar = sh.Range("1:1").SpecialCells(xlCellTypeConstants) 'code stops here if row 1 empty
ar = a.Transpose(a.Transpose(ar))
Sheet1.Cells(3 + sh.Index, 2) = Join(ar, "//")
ar = Null
skip:
Next sh
End Sub
Note: Error trapping is set to "Break on unhandled errors"
I believe the reason lays in the fact you allready encountered an error. You however never cleared your error from the handler. A second error won't skip over that line again.
So to replicate this I had three worksheets, a blank row in the second and third. Your On Error Goto went through the second sheet but would return Error 1004 on the third.
You might want to include an Err.Clear
On Error Resume Next
ar = sh.Range("1:1").SpecialCells(xlCellTypeConstants) 'error 1004 on certain sheets
skip:
Err.Clear
Next sh
EDIT: Just found an alternative solution here
And even then, maybe even drop the error handling completely and use something like:
If WorksheetFunction.CountA(sh.Rows(1)) > 0 Then
ar = sh.Range("1:1").SpecialCells(xlCellTypeConstants)
End If
A Resume statement is needed after an error occurs in which an error handler is enabled by the On Error statement. As per VBA reference...
If an error occurs while an error handler is active (between the occurrence of the error and a Resume, Exit Sub, Exit Function, or Exit Property statement), the current procedure's error handler can't handle the error.
For additional information have a look at the following link...
https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/on-error-statement
I've created an excelfile with a couple of activeX-elements. So far the file is working as intended, now I'm working around "user errors" which might occur during daily business.
For later use the activeX-elements (Toggle-, Command- & SpinButtons) are quite important, so I've created some subs to restore each element. They are working as expected.
The issue: now what if someone deletes a button? I tried working with If-Statements (if >element< is nothing then...), but it didn't work. The next approach was "On Error GoTo".
So I've built an ErrorHandler and as a regular code it works as intended. The handler creates an SpinButton with the desired name. If I build an error into my code (a = 1/0) the handler is doing his job, but that's just 'in vitro'.
In vivo, if my desired >element< is not there, my code ends in an error (compiling error_ method or object not found), but my handler does nothing, despite this is its sole purpose.
My code:
Sub Cal_SpinButton_Nr()
Subroutine:
On Error GoTo CreateObject:
With Tabelle5.SpinButton_Nr
.Left = 198
.Height = 65.25
.Top = 1.5
.Width = 54.75
.Orientation = fmOrientationVertical
.BackColor = &H8000000F
.ForeColor = &H80000012
End With
Exit Sub
CreateObject:
Tabelle5.OLEObjects.Add("Forms.SpinButton.1").Name = "SpinButton_Nr2"
'Resume Subroutine
End Sub
Option Explicit is on and the sub contains no variables. As long as there is an object (SpinButton_Nr) it's working. With no object I get a compiling error.
"Resume Subroutine" is silenced right now to avoid an endless loop (I learned my lesson by silencing "exit sub" first and pressing F5...), regular function is to fire the same sub again in order to put the new object in the right place.
For ('in vitro') testing reasons the new object is called _Nr2, later it's just _Nr.
Now the question: Why is a compiling error not covered by the "on error"-statement? How can the code be modified in order to work properly?
Syntax should be "If >element< exists, set properties, if <element> is not there then create it and set properties afterwards.
I suggest the following:
So you actually test if a spin button exists if not create it before you start formatting. No odd error handling and goto jumps in your procedure.
Option Explicit
Sub Cal_SpinButton_Nr()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Tabelle5") '<-- tab name
'OR
'Set ws = Tabelle5 '<-- VBA code name
Dim SpinBtn As Object
On Error Resume Next 'next line errors if no spinbutton exists
Set SpinBtn = ws.OLEObjects("SpinButton_Nr")
On Error Goto 0 'always re-activate error reporting!
'if no spinbutton is found create it before formatting starts
If SpinBtn Is Nothing Then
Set SpinBtn = ws.OLEObjects.Add("Forms.SpinButton.1")
SpinBtn.Name = "SpinButton_Nr"
End If
'format spin button
With SpinBtn
.Left = 198
.Height = 65.25
.Top = 1.5
.Width = 54.75
'not that for these .Object is necessary because of using .OLEObjects("SpinButton_Nr")
.Object.Orientation = fmOrientationVertical
.Object.BackColor = &H8000000F
.Object.ForeColor = &H80000012
End With
End Sub
Compiling errors are errors, which occur in compiling time. They cannot be covered by OnError statement. The errors, covered by OnError are run-time errors, which appear after compiling and during run time.
"As long as there is an object (SpinButton_Nr) it's working. With no object I get a compiling error." - If you want to get run-time error and not compiling error, the binding of the object should be done during run-time.
Imagine the following scenario, with Tabelle5 as a worksheet variable in Excel:
Sub CompileTimeError()
With Tabelle5
Debug.Print .Cells(1, 1)
End With
End Sub
This would not compile, because VBE would not find the Tabelle5 object. However, if you want a run-time error, this is a possible solution:
Sub RunTimeError()
Dim nameOfWorksheet As String
nameOfWorksheet = "Tabelle5"
With Worksheets(nameOfWorksheet)
Debug.Print .Cells(1, 1)
End With
End Sub
I can do this by going to file -> links -> update automatically.
However, this leads to crashes on other people's computers. This might be a compatibility issue since I'm on PowerPoint 2016.
I have 30+ charts that I would like updated.
I tried the following VBA but I end up with a pop-up from Excel saying:
Microsoft Excel is waiting for another application to complete an OLE action.
This continued popping up for 15 minutes.
Sub ChangeChartData()
Dim pptChart As Chart
Dim pptChartData As ChartData
Dim pptWorkbook As Object
Dim sld As Slide
Dim shp As Shape
For Each sld In ActivePresentation.Slides
For Each shp In sld.Shapes
If shp.HasChart Then
Set pptChart = shp.Chart
Set pptChartData = pptChart.ChartData
pptChartData.Activate
Set pptWorkbook = pptChartData.Workbook
On Error Resume Next
'update first link
pptWorkbook.UpdateLink pptWorkbook.LinkSources(1)
On Error GoTo 0
pptWorkbook.Close True
End If
Next
Next
Set pptWorkbook = Nothing
Set pptChartData = Nothing
Set pptChart = Nothing
End Sub
One thing I would be interested in trying is, change this:
On Error Resume Next
'update first link
pptWorkbook.UpdateLink pptWorkbook.LinkSources(1)
On Error GoTo 0
To this. (I had inquired earlier what happens if you remove On Error Resume Next and you never answered. As a general rule, you should never do that unless you are absolutely certain you know what you're doing with that, and are handling potential errors adequately, in this case you probably are not, but I don't know if there are errors).
'On Error Resume Next
'update first link
pptWorkbook.UpdateLink pptWorkbook.LinkSources(1)
'On Error GoTo 0
If that raises an error, please inform what specific error message is given.
If that doesn't raise an error, but results in the same problems, try this instead:
'On Error Resume Next
'update first link
pptWorkbook.UpdateLink pptWorkbook.LinkSources(1)
'On Error GoTo 0
Exit For '### Get out of the loop
If, as you say, all charts are linked to same data, then I think it's safe to Exit For to avoid redundant calls that aren't doing anything. Still problem, try:
'On Error Resume Next
'update first link
pptWorkbook.RefreshAll
'On Error GoTo 0
Exit For '### Get out of the loop
Finally, and I have some suspicion that this might actually work, try this. You've said there are no formula reference or external links (database, text, web queries, etc.) so it shouldn't seem necessary to do a RefreshAll or even UpdateLink seems pointless. Simply activating the chartdata worksheet and then immediately closing it seems like it should update the data in the PPT embedded Excel sheet and Chart. At least it does when I test on a non-Bloomberg file.
pptChartData.Activate
Set pptWorkbook = pptChartData.Workbook
'On Error Resume Next
'update first link
'pptWorkbook.UpdateLink pptWorkbook.LinkSources(1)
'On Error GoTo 0
pptWorkbook.Close True
Exit For
Note: again you want the Exit For because there is no sense in refreshing the same file 30 times.
For me worked this:
For Each sld In ActivePresentation.Slides
For Each shp In sld.Shapes
If shp.HasChart Then
shp.Chart.ChartData.Activate
shp.Chart.ChartData.Workbook.UpdateLink shp.Chart.ChartData.Workbook.LinkSources(1)
shp.Chart.ChartData.Workbook.Close True
End If
Next
Next
i.e. not creating new variables just working with the original ones.
I've just come across this happen today on a colleagues laptop. To solve the issue, in Excel go to File>Trust Center>Trust Center Settings>External Content select top radio button Enable all Data Connections
I have the following two lines of code:
On Error Resume Next
myWorkbook.Sheets("x").Columns("D:T").AutoFit
I've stepped into the macro and executed the line On Error Resume Next and then on the next line myWorkbook... it does the following:
Why doesn't the compiler resume the next line of code?
On Error has been liberally used throughout the procedures code; I realize best practice is to use this as little as possible but it seems to fit the purpose of this macro.
Reading this SO QUESTION it says that you can't have one set of error trapping within another. How can I guarantee that one set of error trapping has been "closed off" before the code moves on - does On Error Goto 0 reset the error trapping? If it does reset then why doesn't resume work in the following?:
Sub GetAction()
Dim WB As Workbook
Set WB = ThisWorkbook
On Error GoTo endbit:
'raise an error
Err.Raise 69
Exit Sub
endbit:
On Error GoTo 0
On Error Resume Next
WB.Sheets("x").Columns("D:T").AutoFit
End Sub
There is also a VBA setting that will cause On Error ... statements to be ignored and that dialog box to always appear. See this answer for more details on checking/changing the option: https://stackoverflow.com/a/3440789/381588
An example of an error when the initial error is not closed out.
Sub GetAction()
Dim WB As Workbook
Set WB = ThisWorkbook
On Error GoTo endbit:
'raise an error
Err.Raise 69
Exit Sub
endbit:
On Error Resume Next
WB.Sheets("x").Columns("D:T").AutoFit
End Sub
As you have found, within the same function or subroutine, On Error Resume Next doesn't override On Error Goto ... if it's still active.
You are correct that On Error Goto 0 restores the default error handler.
There are some cases where On Error is the most appropriate way to handle an exceptional condition. I prefer to use the following structure:
On Error Resume Next
statement which might fail
On Error Goto 0
if statement has failed then ...
This keeps everything together, but in other cases a generic error handler at the end of the procedure can be better.
I've found that in functions/subs that iterates over nested objects, errorhandling might be a drag in VBA. A solution that works for me to better handle complex iterations is separating setting of objects in their own functions, e.g.
main function/sub:
set FSOfolder = SetFSOFolder(FSOobject, strFolder)
Private Function SetFSOFolder(FSO as scripting.FileSystemObject, strFolder as string) as Scripting.Folder
on error resume Next
set SetFSOFolder = FSO.GetFolder(strFolder)
on error goto 0
End Function
and then in the next line in main function:
if (not fsofolder is nothing) then
I agree using On Error Resume Next is not best practice but most/many of my Access apps are not overly sensitive to minor nuances in data integrity (i.e. analytical or reporting, not transactions and not audited). For this reason I use OERN quite often because VBA is very sensitive to some situations that you cannot anticipate entirely.
1 - will the error cause data corruption. If yes handle it. Many routines I use are just crunching a large volume of records and there may be errors in imported data that haven't been fixed. Usually I have a lot of conversion processes that will let the system clean up its own data eventually.
2 - is the error frequent and non-critical (ie not a key). If yes it's OERN otherwise the error may not be predicable and you end up crashing out or have to write a bunch of I-T-E or Select Case logic to trap it.
Don't use On Error Resume Next instead write code that shouldn't crash.
Note: I am being careful how I phrase that because you never guaranty code doesn't crash. But if you use On Error Resume Next then part of the natural flow of your code is for it to crash, which is wrong, big time wrong.