I have a macro that needs to run for update.
If Excel is in edit, I need a macro to have Excel exit edit mode, that is, take control.
This is not possible as Excel will not run any macros while in cell editing mode.
The natural way to do this is by using the Application.OnTimer method, but it has the "edit problem" you just noted. The called function will not get executed until the user leaves the edit mode.
You may overcome this: This solution is not very efficient, but you may yield control from a macro and regain it from a timer (or any other event you choose).
The following code is from the Excel timer function help.
While the timer is counting down (or you are awaiting other event), you can continue working on your worksheet.
Sub a()
Dim PauseTime, Start, Finish, TotalTime
If (MsgBox("Press Yes to fire update in 1000 secs", 4)) = vbYes Then
PauseTime = 1000 ' Set duration 1000 secs or whatever.
Start = Timer ' Set start time.
Do While Timer < Start + PauseTime
DoEvents ' Yield to other processes - THIS IS THE TRICK
Loop
Finish = Timer ' Set end time.
TotalTime = Finish - Start ' Calculate total time.
MsgBox "Paused for " & TotalTime & " seconds" 'Program your update HERE
Else
End
End If
End Sub
You should invoke this macro beforehand, perhaps at the open workbook event.
Also, it is possible to keep the loop going, repeating the update when you want to.
You should check if this strategy does not interfere with other features used in your particular worksheet.
Related
I've found a lot of questions and answers about issues that feel very close to what I'm working on, but not quite. I have an Excel workbook with a large Data Model connected to two slicers. I need to cycle through every entry in the slicer, allow the workbook to catch up on loading a large number of cube formulas, then copy one particular worksheet over into another.
I've written VBA which does all of this, but I can't for the life of me get the VBA to wait for the workbook to finish uploading before it continues with the rest of the script. I can rule out background refresh-based solutions, which don't apply to OLAP. Various solutions I've found online which recommend waiting for calculations to be complete don't seem to work, the script just barrels right through those lines. The only solution I've seen which seems to apply here involved identifying every cell which would be updated as a result of the slicer change and looping through them until they no longer say #GETTING DATA. For my workbook, this would be hundreds of cells to identify and check and feels very unsustainable.
Even telling the script to Applcation.Wait seems to wait for the selected amount of time during which the workbook pauses getting data.
Setting different values of a slicer connected to a Data Model and automating some output feels like it should be such a common task that we have a solution for it. Any help would be much appreciated.
Running Office 365
Sub generate_all_forecasts()
'Cycle through all products and push forecast values to fcst_output'
Application.ScreenUpdating = True
Dim SC_products As SlicerCache
Dim selection, product_array As Variant
Dim push As Boolean
Set SC_products = ThisWorkbook.SlicerCaches("Slicer_PRODUCT_GROUPING_WRITTEN") 'The product slicer on the Inputs worksheet'
product_array = Range("product_array") 'Named range product_array on Tbl_Codes worksheet'
For Each p In product_array 'For each product'
push = WorksheetFunction.Index(Range("fcst_push_array"), WorksheetFunction.Match(p, product_array, 0)) 'Check if the product has been selected for this run'
If push = True Then
If p = "Major Medical Plan" Then 'If "Major Medical" '
selection = Array("[Query1 1].[PRODUCT_GROUPING_WRITTEN].&[Major Medical Plan - CMM]", _
"[Query1 1].[PRODUCT_GROUPING_WRITTEN].&[Major Medical Plan - GMM]") 'selection will be both CMM and GMM'
Else
selection = Array("[Query1 1].[PRODUCT_GROUPING_WRITTEN].&[" & p & "]") 'Otherwse selection is the single product'
End If
SC_products.VisibleSlicerItemsList = selection 'Change slicer to current selection'
'This is where the script needs to pause until #GETTING DATA is complete'
Application.Run "push_to_output" 'Run the forecast update macro'
End If
Next p
Worksheets("Fcst_Output").Range("B2:B1381").Value = "" 'Clear prior month's comments'
Application.ScreenUpdating = True
End Sub
Solutions which have not worked: Wait time after change slicer in
power pivot, Getting vba to wait before proceeding, Wait
until Application.Calculate has finished
The "solution" I really don't want to use: Force VBA to wait until
power pivot finishes refreshing
Thanks to Tragamor for linking to a thread where they already had a working answer. I included the following immediately after the slicer selection in my VBA and it appears to properly wait until all data fetching is complete:
Dim CalculationState As Long
With Application
CalculationState = .Calculation
.Calculation = xlCalculationAutomatic
.CalculateUntilAsyncQueriesDone
Do Until .CalculationState = xlDone
DoEvents
Loop
.Calculation = CalculationState
End With
Find your Query Properties, and set 'Enable background refresh' to False (Microsoft use True as default).
Then in your code you need to call for RefreshAll and wait for the data to load with the DoEvents. If you want your update of data to run on open you can use this in 'ThisWorkbook' Object.
Private Sub Workbook_Open()
For Each q In ThisWorkbook.Connections
q.Refresh
DoEvents
Next
End Sub
I'm building a process to convert linked images into embedded images in Excel:
for ws in wb.sheets
count = ws.shapes.count
for 1 to count
'Get first shape from collection
set shp = ws.shapes(1)
'Store shape's position/size
'...
'Break link if it exists
shp.CopyPicture
ws.Paste
shp.delete
set newShp = ws.shapes(count)
'Assign newShp, shp's old position/size
'...
next shp
next ws
Sometimes the code will error on line the 2nd line of:
shp.CopyPicture
ws.Paste
with the error "Unable to execute method paste...". This occurs also when I space out the copy and paste methods with DoEvents like so:
shp.CopyPicture
DoEvents
ws.Paste
DoEvents
However after clicking debug, and waiting a second or two, and pressing play again everything continues working like a charm.
I suspect Excel isn't waiting long enough for the CopyPicture method, to fully occupy the clipboard. Assuming this is the case, can I monitor the clipboard somehow and wait till the clipboard data is full?
I think you probably just need to add a pause to give the clipboard a moment to populate the clipboard before you try to access it.
I use a pause procedure like:
Sub Pause(sec as Single)
Dim startTime as Single
StartTime=Timer
Do
DoEvents
Loop While StartTime + sec > Timer
End Sub
I'd probably start with a quarter or half second pause (ie., Pause (0.25)) and then loop until the clipboard's ready.
A half second should be more than enough time, although ideally you'd be triggering your procedure from the other end after pasting, proactively instead of re actively.
I was searching something similar (proper syntax on API calls for the clipboard), and came across this thread. It may be worth noting that when you send data to the clipboard, it indeed takes time.
The "Do events while busy" I used to use came to mind when using my old IE.navigate commands.
Anyway, what do you think of this.
SendKeys ("^c") 'Psudeocode to copy
JumpPoint:
RandomVar = Clipboard.GetData
DoEvents 'Force this data into a random address somwhere while Clipboard loads
If ClipBoard.GetData <> RandomVar Goto JumpPoint
[Do the Magic here]
See that? If RandomVar = the current state of the ClipBoard, and Do Events makes it so that RandomVar is equal to SOMETHING referencable, then as ClipBoard continues to aggregate data, it will differ from RandomVar. Thus the Goto will kick back a tick, essentially "Do Events While Busy".
That's just my thought process using the tools you already have available without seeing the entire code or intention.
Yes, my coding methods are blasphemy. I don't care. Good luck, and my the dark side be ever with you.
It's been a while since I asked this question. Since then my knowledge has grew in this arena. In the future I'll use my stdClipboard class's xlShapeAsPicture method as follows:
for ws in wb.sheets
count = ws.shapes.count
for 1 to count
'Get first shape from collection
set shp = ws.shapes(1)
'Store shape's position/size
'...
'Break link if it exists
set stdClipboard.xlShapeAsPicture = shp '<<<---
'Paste
ws.Paste
'...
next shp
next ws
The key to this solution is making calls to poll IsFormatAvailable() after shp.CopyPicture() is called to wait until the image format is available, and only then exit the function and continue runtime.
I currently have this Excel VBA code written using Bloomberg API. I am trying to pull data from Bloomberg into Excel using VBA. That is working fine but the problem is that I have another macro that then copies the data from Bloomberg and pastes it in another sheet.
If the data hasn't all been output from Bloomberg then I end up having insufficient data to copy then paste into the other sheet.
Currently, I am using this line of code:
Application.OnTime Now + TimeValue("00:01:45"), "RunAll"
which waits after running the first macro for 1 min 45s till it runs the remaining macros. It is useful but way too much time. Issue is that this is around how long the data takes to output.
Is there any other more efficient way for me to pull in the bloomberg data faster by ensuring that the data gets output into excel faster?
One way to handle it would be when you start your second macro that copies the data, check to see to see if a mid-point cell is empty (something like A100?? Seeing your code would help here...). If so, wait 10 seconds and check again. This will force that second macro to stay in a holding pattern while the first one catches up.
Word of caution though, I would set a max number of loops otherwise if that data doesn't download for some reason it won't hang up your machine.
UPDATE 1:
I wrote out the code below to accomplish what you're trying to do. There are a couple of things you'll need to work into your current code, but it I've purposely made it comment heavy so you should be able to follow. Let me know if this works for you.
Public boolBloombergCompleted As Boolean
Sub GetBloombergData()
'add this line after the data grab is complete
boolBloombergCompleted = True
End Sub
Sub WriteData()
Dim iRow As Integer
Dim boolTimeOut As Boolean
'change the last number as fit, make sure it's larger than the number of rows of data you're pulling in though
For iRow = 1 To 1000
' Check to see if the cell is blank
If Sheet1.Cells(iRow, 1) = vbNullString Then
' If the cell is blank and GetBloombergData has completed then exit sub
If boolBloombergCompleted = True Then
Exit Sub: Debug.Print "WriteData completed"
Else
' Call the wait function below
boolTimeOut = WaitForDataGrabToCatchUp(Sheet1.Cells(iRow, 1))
If boolTimeOut = True Then GoTo TimeOutErr:
End If
End If
' < Add your code to write data in here >
Next iRow
Exit Sub
TimeOutErr:
MsgBox "The write sub timed out while waiting for data to load", vbExclamation
End Sub
Function WaitForDataGrabToCatchUp(rng As Range) As Boolean
Dim StartTime1 As Long
Dim StartTime2 As Long
Dim PauseTime As Long
Dim StopTime As Long
' Set the amount of time to pause between checking the spreadsheet for updates
PauseTime = 5 'seconds
' Set the maximum amount of time to wait before timing out
StopTime = 60 'seconds
' StartTime1 is used for calculating overall time
StartTime1 = Timer
Do While rng = vbNullString
' check if the StopTime has been reached
If Timer - StartTime1 > StopTime Then
WaitForDataGrabToCatchUp = True
Exit Function
Else
' loop for amount of PausedTime (the DoEvents part here is key to keep the data grab moving)
StartTime2 = Timer
Do While Timer < StartTime2 + PauseTime
Debug.Print Timer - StartTime1
DoEvents
Loop
End If
Loop
WaitForDataGrabToCatchUp = False ' means it did not time out
End Function
I have a macro that needs to be run for many workbooks. There is a part though where I update a segment of the connection string with unique values and apparently this can't be done in VBA so I must manually do it. Is there a way I can have the Macro do what it does then pause while I update the connection string for say 30 seconds then start running again to completion?
Thanks,
Here is a macro that will allow you 30 seconds to make changes and then resume operation:
Sub dural()
'first part
'do stuff
MsgBox "Perform updates"
t1 = Now
While Now < t1 + TimeSerial(0, 0, 30)
DoEvents
Wend
MsgBox "Resuming the second part of the macro"
'macro does more stuff
End Sub
NOTE:
the DoEvents allows focus to be shared with the user.
Morning
I need some help and not really sure where to begin.
I have an excel workbook, which populates a sheet of data from a SQL stored procedure. There are then a series of pivots off the data, all easy so far. I then create charts off those pivots and move them to their own sheets.
What I want to achieve is a rolling dashboard of those charts, say every 30 seconds, change sheet. I have tried a few things, grabbed a few ideas off this site, but for some reason, the sheets with the charts on will not loop with the others, so in effect I only see my data page and pivot page. Below is some code that I tried to modify for my purpose.
Sub TabShow()
Dim i As Integer
Dim Pause As Double
Pause = 3 'Pause delay
Loops = 3 'How many loops do you want to do
For j = 1 To Loops
For i = 1 To Worksheets.Count
Worksheets(i).Select 'Select the next worksheet
x = Timer
While Timer - x < Pause 'This does the pausing
Wend
Next i
Next j
End Sub
So if anyone has any code that loops through visible sheets including ones of moved charts, I would be eternally grateful.
Cheers
Time arithmetics: in VBA, time is stored in Date, being Date binary equal to Double, being each unit one day. So, Pause = 3 means 3 days!!! Better:
Dim Pause As Date
Pause = #0:0:30# 'Time constant
I recommend also using Now instead of Timer.
Program flow: while running a macro, Excel become irresponsive, unless you use DoEvents:
While Timer - x < Pause 'This does the pausing
DoEvents
Wend
That will ensure your workbook will allow user interaction while macro is running.
However, I do not recommend keeping a macro running while working on it. Check Application.OnTime for a better approach.