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?
Related
This loop runs typically 4 to 8 times. It updates 30+ textboxes and prints. Has been working great. But we updated from office 2010 to 2016 (and to Office 365), so now it runs but all the textboxes on the printed pages have the same value from iteration 1. Happens on all printers including PDFcreator. But afterwards the sheet is in the state I expect for the last iteration. It's like the code outruns the printer. But adding a delay of even 10 sec does not help. Oddly, as I try different things I see on the first iter that the textboxes update (from the previous runs last iter). Seems like it should update every iter.
Sub printParamSheets()
On Error GoTo errHandler
Dim Bin1_Matl, Bin1_Parts 'Declaring types won't help or speed up
Dim iCond, conditions
Application.EnableEvents = True 'Is not helping with issue
For iCond = 1 To conditions
With Sheet2
'Assign from sheet2 to variables
Bin1_Matl = .Range("A64").Offset(0, iCond * 2).Value2
Bin1_Parts = .Range("B64").Offset(0, iCond * 2).Value2
'about 30 more of these
End With
With Sheet8 'Assign Sheet8 named ranges from variables above.
'Could skip intermed vars but nice for debugging.
'ALL LINKED TO ACTIVEX TEXT BOXES on Sheet8, atop an image.
.Range("Bin1Matl").Value2 = Bin1_Matl
.Range("Bin1Parts").Value2 = Bin1_Parts
'about 30 more of them
.Calculate 'Is not helping with issue
Dim ctrl As OLEObject 'Is not helping with issue
For Each ctrl In .OLEObjects
If TypeName(ctrl.Object) = "TextBox" Then
'ctrl.Update 'error, but was not in 2010 anyway
End If
Next ctrl
Application.Wait (Now + TimeValue("00:00:05")) 'Is not helping
Application.ScreenUpdating = True 'Never toggled off , no help
DoEvents 'Is not helping with issue
.PrintOut from:=1, To:=1, Copies:=1, Collate:=True
End With
Next iCond
Exit Sub
errHandler:
Application.EnableEvents = True 'Don't need but cannot hurt
Resume Next
End Sub
I will try skipping the extra intermediate assignments, going straight from sheet2 to the textboxes. But I'd like to know the cause because I have other code 'in the wild' that doesn't necessarily use any activex objects that may be affected. Hoping the symptom is not unique to our site and so others may benefit from an answer.
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
I read a data line from an instrument using Excel VBA.
I would like to plot the data dynamically on an Excel Active Chart, IMMEDIATELY after it has been read.
I need to wait and read the data every 5 seconds and, in the mean time, I "sleep", either through the VBA Application.Wait command, or through the Kernel32 Sleep command.
In either case the Active Chart does NOT get updated. The complete plot shows up only after the LAST "sleep".
Any suggestions will be appreciated.
Here is the simplified code
Sub New_Data(indx)
Dim x As Integer
While True
x = Read_Instrument(1)
y = Read_Instrument(2)
Cells(indx, 1) = x
Cells(indx, 2) = y
ActiveSheet.ChartObjects.Item(1).Activate
ActiveChart.FullSeriesCollection(1).XValues = "=Sheet1!$A$1:$A$" & indx
ActiveChart.FullSeriesCollection(1).Values = "=Sheet1!$B$1:$B$" & indx
indx = indx + 1
Sleep 5000 'Use the KERNEL32 Sleep function for 5000 milliseconds
Wend
End Sub
Wait and Sleep will keep VBA running, so the screen will not updated.
Try using Application.OnTime, along these lines (in a standard code module, i.e. Module1).
Sub refreshMyChart()
Dim indx as long: indx = sheet1.Cells(Rows.Count, 1).End(xlUp).offset(1)
Cells(indx, 1) = Read_Instrument(1)
Cells(indx, 2) = Read_Instrument(2)
With Sheet1.ChartObjects(1).FullSeriesCollection(1)
.XValues = "=Sheet1!$A$1:$A$" & indx
.Values = "=Sheet1!$B$1:$B$" & indx
End With
''''' Now Schedule the same routine for 5 seconds later''''''''''
Application.OnTime Now + TimeSerial(0,0,5), "refreshMyChart"
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
End Sub
p.s.: be aware that this data cannot grow indefinitely.. You should define some way to stop or to delete old rows to keep the number of displayed data rows reasonable.
Excellent! Thank you very much.
I was not aware of the .OnTime construct
Application.OnTime Now + TimeSerial(0, 0, 5), "refreshMyChart"
and the fact that VBA allows recursive routines.
As for ending the loop, there is code in there, which I did not post because it was not relevant to the issue. Here it is
If Int(current_index / nmeasure) * nmeasure > finalval Then
MsgBox ("End of Measurement")
Exit Sub
End If
Thanks again.
I am running into a race condition issue where I have two QueryTables, each is hooked with its own AfterRefresh event. Each AfterRefresh event does some copy'n'pasting as well as doing some calculations.
Now, when the user click Refresh All (Ctrl+Alt+F5) in Excel, I would love to have each AfterRefresh handler to execute, but ONLY after all QueryTable refreshes are entirely completed.
I did a search on StackOverFlow, and someone suggested
Activeworkbook.RefreshAll
DoEvents
However, that's assuming that we are programmatically triggering the RereshAll. In my case, Refresh All is done by the built-in Refresh All (Ctrl+Alt+F5) button within Excel. Thus, I don't see where I can insert DoEvents in my case (unless I create my own Refresh All button, but I would like to avoid doing so).
I tried to search for "Excel VBA mutex", but I did not find anything in particular. So how do I make sure that all the refreshes are done, before each AfterRefresh handler takes place?
Thanks for reading!
Update: To help out with debugging.. here are my VBA codes.
I have a module named AutoOpen
Dim S As New DataCopy
Dim U As New DataCopy
Sub Auto_Open()
Set S.qt = ThisWorkbook.Sheets(1).QueryTables(2)
S.myWorkbookName = ThisWorkbook.Name
S.sWorksheetProcessName = "ProcessS"
S.sWorksheetDataColumnStart = 1
S.sWorksheetDataColumnEnd = 5
Set U.qt = ThisWorkbook.Sheets(1).QueryTables(1)
U.myWorkbookName = ThisWorkbook.Name
U.sWorksheetProcessName = "ProcessU"
U.sWorksheetDataColumnStart = 6
U.sWorksheetDataColumnEnd = 10
End Sub
I also have a Class module named DataCopy
Public WithEvents qt As QueryTable
Public myWorkbookName As String
Public sWorksheetProcessName As String
Public sWorksheetDataColumnStart As Integer
Public sWorksheetDataColumnEnd As Integer
Private Sub qt_AfterRefresh(ByVal Success As Boolean)
DataCopier
End Sub
Private Sub DataCopier()
'Debug.Print sWorksheetProcessName & "," & Application.CalculationState
Dim LastNRows As Integer
Dim sWorksheetDataName As String
' How many rows to copy
LastNRows = 297
sWorksheetDataName = "Data"
Application.ScreenUpdating = False
' Clear content in process tab
With Workbooks(myWorkbookName).Worksheets(sWorksheetProcessName)
.Range(.Cells(4, 1), .Cells(.Cells(Rows.Count, 1).End(xlUp).Row, 6)).ClearContents
End With
' Copy to process Tab
With Workbooks(myWorkbookName).Worksheets(sWorksheetDataName)
LastRow = .Cells(Rows.Count, 1).End(xlUp).Row
FirstRow = LastRow - LastNRows
If FirstRow < 2 Then
FirstRow = 2
End If
.Range(.Cells(FirstRow, sWorksheetDataColumnStart), .Cells(LastRow, sWorksheetDataColumnEnd)).Copy _
Destination:=Workbooks(myWorkbookName).Worksheets(sWorksheetProcessName).Range("A4")
End With
Debug.Print (sWorksheetProcessName & "," & sWorksheetDataColumnStart & "," & sWorksheetDataColumnEnd)
Application.ScreenUpdating = True
End Sub
Because of the race condition, only one AfterRefresh handler succeeds in copy'n'pasting.. the other one doesn't work until I click Refresh All button (Ctrl+Alt+F5) again.
If a DoEvents works after the explicit VBA trigger Activeworkbook.RefreshAll then a DoEvents before the code that you want to run in the event handlers should cover the case when the refresh is triggered by Ctrl+Alt+F5. Thus, begin each event handler with the line DoEvents.
change the queries to not allow background refresh, and they will not relinquish control until refreshed
I have the following Sub. The script causes some information to be written to a table of fixed length every 15 seconds. The results are then graphed to create a real-time moving graph feeling.
The problem is that when I uncomment the line where it saves the workbook, I notice in the task manager that Excel uses more memory (about 3000KB), and holds it. Eventually, Excel runs out of memory and crashes. I have tried setting my objects and stuff to Nothing, but that doesn't help. It's all due to the save line, but I don't really know why. How can I save, and avoid the memory problem?
Sub Record()
Dim BookValueTable As ListObject: Set BookValueTable = Range("bookvalue_table").ListObject
If Sheets("Orders").LoggingButton.Value And _
BookValueTable.Range(BookValueTable.ListRows.Count + 1, 1) <> Now Then
Dim NewRow As ListRow: Set NewRow = BookValueTable.ListRows.Add(AlwaysInsert:=True)
NewRow.Range(1, 1) = Now
NewRow.Range(1, 2) = Sheets("Orders").Range("total_dollar_pnl")
While BookValueTable.ListRows.Count >= 28800
BookValueTable.ListRows(1).Delete
Wend
ThisWorkbook.RefreshAll
If Hour(Now) = 17 Then
Application.OnTime TimeValue("18:00:00"), "Record"
Else
Application.OnTime Now + TimeValue("00:00:" & 15 - (Second(Now) Mod 15)), "Record"
End If
'Workbooks("Book1.xlsm").Save
End If
Set BookValueTable = Nothing
Set NewRow = Nothing
End Sub