IWebBrowser2::Navigate2 occasionally freezes when opening several new tabs - vba

I'm writing an Excel script to open a list of PDFs in Internet Explorer tabs. It works fine most of the time, but occasionally when I try to close my browser window, a few of the tabs will close, then it stops and all IE instances will freeze, so I have to kill them all in Task Manager. Note that I can avoid the problem by closing each tab individually.
I'm running IE8 and Excel 2007, for the record. Here's my code:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
ShowBrowserWarning
Dim TheHTML As String, PDFs, PDF, First, SerialValue, Test, k
If Target.Column = 1 And Target.Count = 1 Then
' Get the serial number from the adjacent column
SerialValue = Cells(Target.Row, Target.Column + 1)
TheHTML = ShowHTML("http://ucmwww.dnr.state.la.us/ucmsearch/findAllDocuments.aspx?brief=False&query=xwellserialnumber+LIKE+'" & SerialValue & "'+AND+xdocumenttype+LIKE+'WELL ENGINEERING/MECHANICAL'&format=HTML&sortfield=xdate")
Set PDFs = ExtractPDFs(TheHTML)
If PDFs Is Nothing Then
MsgBox "No associated well engineering/mechanical PDFs."
Else
First = True
Dim ie As Object
Set ie = CreateObject("InternetExplorer.Application")
ie.Visible = True
For Each PDF In PDFs
'While ie.Busy
' Dim testvar
' testvar = 1 + 1
'Wend
If First Then
' Open new IE window
ie.Navigate2 PDF.Value
First = False
Else
' Open tab in existing IE window
ie.Navigate2 PDF.Value, 2048
End If
Next
End If
End If
End Sub
What gives? Why does it freeze like that? Does it have anything to do this issue? (Please try not to laugh at my ignorance!) Any help is much appreciated.
Edit: see the italicized text above. I didn't quite describe the problem accurately!

And what about Browser-Busy check? Could it help to avoid the issue?
For Each PDF In PDFs
While ie.Busy
DoEvents
Wend
If First Then
' Open new IE window
ie.Navigate2 PDF.Value
First = False
Else
' Open tab in existing IE window
ie.Navigate2 PDF.Value, 2048
End If
Next
Or just wait between the browser.Navigate calls for a while to give the browser enough time to load one dokument before starting to load next one. Try different time-periods and watch if the freezing issue could be avoided this way.
For Each PDF In PDFs
DoEventsForTimePeriod timePeriodInSeconds:=15 ' try different time periods here
If First Then
' Open new IE window
ie.Navigate2 PDF.Value
First = False
Else
' Open tab in existing IE window
ie.Navigate2 PDF.Value, 2048
End If
Next
Private Sub DoEventsForTimePeriod(ByVal timePeriodInSeconds As Single)
' VBA.Timer: Returns a Single representing the number of seconds elapsed since midnight.
Dim pause As Single: pause = VBA.Timer + timePeriodInSeconds
Do While VBA.Timer < pause
DoEvents ' Yield to other processes.
Loop
End Sub

Well, I´m new too but, as far as can see, I would set ie = Nothing at the end of the sub to loose any relation between VBA and InternetExplorer Application

Related

Automation error the server threw an exception on PPT SetSourceData

Some background, I had a VBA loop creating PPT slides with various filters/views on an Excel pivot table. It was working (after I added DoEvents). I have recently added functionality to create a new PPT file from scratch with multiple sides before they are populated with the data. It's not working anymore.
Two theories:
1) Somehow the memory got bogged down in the new PPT file creation loop and now the data population loop is erroring out.
2) Something about how the default chart is formatted is messed up. If I edit the charts manually, save, and populate, there is no error. However if I create and then automatically try to populate, there's an error.
Due to complexity of the scripts, the loop to create the slides is completely separate from the loop to reopen and populate the slides.
Here's the section that errors out:
'Paste the final temp dataset into PPT
Range("A1000").Activate
tempdata = Range(Selection, Selection.Offset(months, categories - 1)).Value
Set oChart = oPres.Slides(pages(b)).Shapes(metrics(a)).Chart
oChart.ChartData.Activate
Set wb = oChart.ChartData.Workbook
Set ws = wb.Worksheets(1)
ws.Range("A1:Z1000").ClearContents
ws.Range("A1", Range("A1").Offset(months, categories - 1)).Value = tempdata
'Let code catch up
Application.Wait (Now + TimeValue("00:00:02"))
DoEvents
'Redraw the selected dataset of the chart based on the # of categories and rows
oChart.SetSourceData Source:="='Sheet1'!$A$1:" & toChar(categories + 0) & months + 1, PlotBy:=xlColumns
Despite using both Application.Wait and DoEvents, it is still hanging up.
This is purely a timing issue because if I click Debug and continue running the code with no changes, it works fine. I am also using late binding (maybe?) through the Set Object statement and at the end of the loop I always Set oChart = Nothing.
Sometimes it works to write DoEvents multiple times, but as the process has gotten more complex, even this doesn't work. I'm all out of ideas. Any suggestions?
'Let code catch up
DoEvents
DoEvents
DoEvents
DoEvents
DoEvents
DoEvents
DoEvents
DoEvents
DoEvents
DoEvents
DoEvents
DoEvents
'Redraw the selected dataset of the chart based on the # of categories and rows
oChart.SetSourceData Source:="='Sheet1'!$A$1:" & toChar(categories + 0) & months + 1, PlotBy:=xlColumns
You may try:
Using Sleep, with this line at the top of your module (outside of your function):
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Then add this line in place of, or in addition to, DoEvents:
Sleep 1 ' Pause for 1 ms
See:
https://stackoverflow.com/a/3891017/2707864
See also:
https://www.myonlinetraininghub.com/pausing-or-delaying-vba-using-wait-sleep-or-a-loop
Using loops with DoEvents:
Dim PauseTime, Start, Finish, TotalTime
PauseTime = 4 ' Set duration.
Start = Timer ' Set start time.
Do While Timer < Start + PauseTime
DoEvents ' Yield to other processes.
Loop
Finish = Timer ' Set end time.
TotalTime = Finish - Start ' Calculate total time.
See:
https://www.mrexcel.com/forum/excel-questions/36052-when-how-use-doevents-solved-post166114.html#post166114
See also:
https://www.myonlinetraininghub.com/pausing-or-delaying-vba-using-wait-sleep-or-a-loop
Using combinations thereof, which can improve performance of your system depending on the wait time.
Public Sub WaitSeconds(intSeconds As Integer)
On Error GoTo PROC_ERR
Dim datTime As Date
datTime = DateAdd("s", intSeconds, Now)
Do
Sleep 100
DoEvents
Loop Until Now >= datTime
PROC_EXIT:
Exit Sub
PROC_ERR:
MsgBox "Error: " & Err.Number & ". " & Err.Description, , "modDateTime.WaitSeconds"
Resume PROC_EXIT
End Sub
See:
http://www.fmsinc.com/microsoftaccess/modules/examples/avoiddoevents.asp
#sancho.s, thanks for your help. So it turns out the error had nothing to do with DoEvents. I had been using that as a sloppy fix without understanding its functionality. Given that, none of the three options worked. I spent all day trying various combinations with no success. Instead, I had to brute force close the embedded PPT workbook, set oChart to Nothing, reinstantiate oChart, reopen the workbook, and close it again.
This made the process 2x slower (but no slower than forcing it to wait on a timer??), and it completely eliminated all errors. Apparently it just didn't like pasting the raw data and reselecting the data the first time the workbook was opened. No idea why.
Sub UpdateChart(ByVal a As Integer, ByVal b As Integer, ByVal months As Integer, ByVal categories As Integer, ByRef pages() As Integer, ByRef metrics() As String, ByVal oPres As Object, ByVal legend_flag As Boolean)
Dim tempdata As Variant
'Paste the final temp dataset into PPT
tempdata = Range(Worksheets("calc").Range("A1000"), Worksheets("calc").Range("A1000").Offset(months, categories - 1)).Value
If legend_flag Then
Set oChart = oPres.Slides(pages(b)).Shapes("legend").Chart
Else
Set oChart = oPres.Slides(pages(b)).Shapes(metrics(a)).Chart
End If
oChart.ChartData.Activate
Set wb = oChart.ChartData.Workbook
Set ws = wb.Worksheets(1)
ws.Range("A1:Z1000").ClearContents
ws.Range(ws.Range("A1"), ws.Range("A1").Offset(months, categories - 1)).Value = tempdata
'Close workbook
wb.Close
Set oChart = Nothing
If legend_flag Then
Set oChart = oPres.Slides(pages(b)).Shapes("legend").Chart
Else
Set oChart = oPres.Slides(pages(b)).Shapes(metrics(a)).Chart
End If
oChart.ChartData.Activate
'Redraw the selected dataset of the chart based on the # of categories and rows
oChart.SetSourceData Source:="='Sheet1'!$A$1:" & toChar(categories + 0) & months + 1, PlotBy:=xlColumns
'Close workbook
oChart.ChartData.Workbook.Close
Set oChart = Nothing
Exit Sub
End Sub
I also put the code snippet in a subroutine and added Exit Sub at the end to hard reset all parameters in an earlier attempt that didn't work. So all objects and parameters have definitely been cleared for good measure.
Does anyone have any ideas why the object definition/open workbook was tripping up like that? And why DoEvents doesn't actually work for this problem?

VBA code only works when in debug mode

So I have this function that returns a value from a web page. The issue with this is that it works perfectly when I run it single step, but when I run it normally it returns another value and objIE.Quit is skipped. This is the code:
Private Function Mexico(partida As String) As String
partida = Left(partida, 8)
Set objIE = New InternetExplorer
objIE.Visible = True
objIE.navigate "http://www.siicexcaaarem.org.mx/Bases/TIGIE2007.nsf/4caa80bd19d9258006256b050078593c/$searchForm?SearchView"
Cargar
objIE.document.getElementsByName("Query")(0).Value = partida
For Each boton In objIE.document.getElementsByTagName("input")
If boton.Value = "Search" Then
boton.Click
Exit For
End If
Next
Cargar
Application.Wait Now + TimeValue("00:00:03")
Dim temp As String
Dim i As Integer
For Each t In objIE.document.getElementsByTagName("tr")
If t.className = "domino-viewentry" Then
temp = t.Children(8).innerText
End If
Next
If InStr(temp, "*") > 0 Then
temp = Left(temp, Len(temp) - 1)
End If
If InStr(temp, "%") = 0 Then
temp = temp & "%"
End If
Mexico = temp
objIE.Quit
End Function
And I am testing this with this sub:
Sub Mex()
MsgBox Mexico("33030001")
End Sub
When I run it single step, it returns "15%" with the parameter passed in the sub, while it returns just "%" when I run it normally with any given parameter. Any idea why is this happening? Any help will be appreciated.
Note: objIE is defined as a public variable, but this has not brought me any inconveniences so far, as I have other functions working properly for different websites. Also, Cargar is the usual "wait until page has loaded" instruction.
Private Sub Cargar()
Do Until objIE.Busy = False And objIE.readyState = 4
DoEvents
Loop
End Sub
You should ensure that your webpage is completely loaded before trying to grab objects off the webpage. Your line containing Application.Wait does not do this for you.
Add this sub into your module:
Sub ieBusy(ByVal ieObj As InternetExplorer)
Do While ieObj.Busy Or ieObj.readyState < 4
DoEvents
Loop
End Sub
then replace your line continaing the Application.Wait with: ieBusy objIE
So after messing with the code for hours and realizing that, sadly, the solution wasn't so simple as fixing the Cargar Load IE sub (as the page doesn't "load" as it's JavaScript driven), I found out that this was the solution:
For Each t In objIE.document.getElementsByTagName("tr")
DoEvents 'Holy fix
If t.className = "domino-viewentry" Then
temp = t.Children(8).innerText
End If
Next
I am quite unsure as why this fixed it, and came up with this while checking with msgboxes which parts of the code were not running correctly, and the msgbox inside the loop fixed it too. I'd appreciate your comments as to why this works.

Shapes.Visible True and False within Loop VBA

I have this piece of code which I would like to show and hide some Shape objects one by one, in order to make a little animation. However, nothing happens as the code executes, all images are shown by once when the code stops running.
Sub test()
For i = 1 To 4
Sheets("Game").Shapes("North" & i).Visible = True
Sleep 500
'Sheets("Game").Shapes("North" & i).Visible = False
'by setting it to false i'd like to achieve the animation effect
Debug.Print i
DoEvents
Next i
End Sub
DoEvents allows other code (e.g. Excel's own) to run and handle things like user clicking on another worksheet (which invokes any Worksheet.Change or Workbook.WorksheetChange handler)... or just repainting itself.
By invoking DoEvents once per loop, Excel doesn't get a chance to repaint between the visibility toggles: it's already busy running your loop.
So you need to toggle visibility on, let Excel repaint (DoEvents), sleep for your animation delay (500ms seems a tad slow IMO), then toggle visibility off and let Excel repaint again, i.e. invoke DoEvents one more time.
If the Game worksheet is in ThisWorkbook, then I'd warmly recommend you give it a CodeName - select it in the Project Explorer, then look at its properties (F4) and change its (Name) to, say, GameSheet.
This gives you a global-scope object variable so that you don't need to dereference the same worksheet twice per iteration - heck you could even dereference its Shapes collection only once:
Private Const ANIMATION_DELAY As Long = 100
Sub test()
With GameSheet.Shapes
For i = 1 To 4
Dim currentShape As Shape
Set currentShape = .Item("North" & i)
currentShape.Visible = True
DoEvents
Sleep ANIMATION_DELAY
currentShape.Visible = False
DoEvents
Debug.Print i
Next
End With
End Sub
Amended the code by setting DoEvents after toggling True and Falseand now it works:
Sub test()
For i = 1 To 4
Sheets("Game").Shapes("North" & i).Visible = True
DoEvents
Sleep 100
Sheets("Game").Shapes("North" & i).Visible = False
DoEvents
'by setting it to false i'd like to achieve the animation effect
Debug.Print i
Next i
End Sub

Excel is waiting for another application to complete an OLE action

Before you go for the obvious: Application.DisplayAlerts = False has not solved my problem.
I have written a VBA procedure (initiated in Excel 2010) which loops around an array containing different Excel files. The loop opens the file, refreshes the data, saves and closes the file for each item in the array. I have written an error catch sub routine so I log which excel files have failed to open/refresh/save etc so a user can manually check them.
Some files are quite large and involve a large amount of data moving across the network; sometimes I get a dialog box with: Excel is waiting for another application to complete an OLE action.
I could use Application.DisplayAlerts = False to disable the message but this would presumably disable all alerts so I couldn't catch the errors?
Further I have tested using the line and it doesn't stop the dialog box pop-up. If I press enter it carries on but will likely pop-up again a few minutes later.
Is there a way to stop is message specifically without stopping other alerts?
NB. My process has a control instance of Excel which runs the VBA and opens the workbooks to be refreshed in a separate instance.
Thanks for your help
An extract of my code is below which contains the refresh elements
Sub Refresh_BoardPivots_Standard()
' On Error GoTo Errorhandler
Dim i
Dim errorText As String
Dim x
Dim objXL As Excel.Application
Set objXL = CreateObject("Excel.Application")
GetPivotsToRefresh ' populate array from SQL
For Each i In StandardBoardPiv
DoEvents
'If File_Exists(i) Then
If isFileOpen(i) = True Then
errorText = i
Failed(failedIndex) = errorText
failedIndex = failedIndex + 1
Else
objXL.Visible = True 'False
objXL.Workbooks.Open FileName:=i
If objXL.ActiveWorkbook.ReadOnly = False Then
BackgroundQuery = False
Application.DisplayAlerts = False
objXL.ActiveWorkbook.RefreshAll
objXL.Application.CalculateFull
objXL.Application.DisplayAlerts = False
objXL.ActiveWorkbook.Save
objXL.Application.DisplayAlerts = True
objXL.Quit
Else
errorText = i
Failed(failedIndex) = errorText
failedIndex = failedIndex + 1
objXL.Application.DisplayAlerts = False
objXL.Quit
Application.DisplayAlerts = True
End If
End If
' Else
' errorText = i
' Failed(failedIndex) = errorText
' failedIndex = failedIndex + 1
' End If
DoEvents
If Ref = False Then
Exit For
End If
Next i
Exit Sub
'Errorhandler:
'
'errorText = i
'Failed(failedIndex) = errorText
'failedIndex = failedIndex + 1
'Resume Next
End Sub
"Waiting for another application to complete an OLE action" isn't an alert message you can just turn off and forget, sometimes the macro will be able to continue on after, but in my experience if you are getting that error its only a matter of time until the problem crashes/freezes your whole macro so it should definitely be troubleshot and corrected.
I only get that error when I am using additional Microsoft Office Applications (other than the Excel that is running the code) as objects and one of them has an error- the Excel running the code doesn't know that an error occurred in one of the other applications so it waits and waits and waits and eventually you get the "Waiting for another application to complete an OLE action" message...
So to troubleshoot this sort of problem you got to look for the places you use other MSO apps... In your example, you have an additional instance of Excel and you are pulling data from Access, so its most likely one of those two that is causing the problems...
Below is how I would re-write this code, being more careful with where the code interacts with the other MSO apps, explicitly controlling what is happening in them.. The only piece I couldn't really do much is GetPivotsToRefresh because I cant see what exactly youre doing here, but in my code I just assumed it returned an array with a list of the excel files you want to update. See code below:
Sub Refresh_BoardPivots_Standard()
Dim pivotWB As Workbook
Dim fileList() As Variant
Dim fileCounter As Long
Application.DisplayAlerts = False
fileList = GetPivotsToRefresh 'populate array from SQL
For fileCounter = 1 To UBound(fileList, 1)
Set pivotWB = Workbooks.Open(fileList(fileCounter, 1), False, False)
If pivotWB.ReadOnly = False Then
Call refreshPivotTables(pivotWB)
pivotWB.Close (True)
Else
'... Error handler ...
pivotWB.Close (False)
End If
Next
End Sub
Public Sub refreshPivotTables(targetWB As Workbook)
Dim wsCounter As Long
Dim ptCounter As Long
For wsCounter = 1 To targetWB.Sheets.Count
With targetWB.Sheets(wsCounter)
If .PivotTables.Count > 0 Then
For ptCounter = 1 To .PivotTables.Count
.PivotTables(ptCounter).RefreshDataSourceValues
Next
.Calculate
End If
End With
Next
End Sub
So I created my own 'refreshPivotTables' but you could have embedded that into the master sub, I just thought the loops and loop counters might get a little messy at that point...
Hope this helps,
TheSilkCode

Refresh the webpage, while the webpage is not loaded properly in VBA excel

I have created a program that extracts data from website and keeps in a spreadsheet. But the major problem I am encountering is the hanging of Internet Explorer.
With ie
.Visible = True
.Navigate urll
Do While .readyState <> 4
Application.StatusBar = "Opening Page : " & i & ", Please wait..."
DoEvents
Loop
Set html = .Document
End With
The loop Do While sometimes sticks and never ends as the internet explorer is not able to load properly and never comes to readystate of 4. In this case I have to either refresh the page manually (keeping visibility of ie true) or I have to stop the program, and do some updates to program (locations of source and destination of data). This is pretty time consuming if every 10th webpage is keeping the loop open.
I have one solution, i.e. during the time loop goes on the program should check about the time elapsed during the loop execution, if the loop continues for more than 50 secs, the program should suspend the current loop and start again by refreshing page. (Please let me know if you have a better logic).
I am not able to do a correct coding for this job. Can anyone solve this...
Try this (UNTESTED)
What this does is increments a variable and checks for the number of time the loop was called.
Dim nCount As Long
Sub Sample()
'
'~~> Rest of the code
'
With ie
.Visible = True
.Navigate urll
Do While .readyState <> 4
Application.StatusBar = "Opening Page : " & i & ", Please wait..."
Wait 2 '<~~ Wait for 2 Seconds
If nCount > 25 Then
'~~> Approx 50 seconds have elapsed
End If
Loop
Set HTML = .Document
End With
'
'~~> Rest of the code
'
End Sub
Private Sub Wait(ByVal nSec As Long)
nSec = nSec + Timer
While nSec > Timer
DoEvents
Wend
nCount = nCount + 1
End Sub