I'm running a macro in a blank Excel 2007 workbook on a PC with a Bloomberg license. The macro inserts Bloomberg functions into sheet1 that pull yield curve data. Some additional functions' results are dependent on the first functions finishing and correctly displaying the Bberg data. When I step through the program it only displays '#N/A Requesting Data . . .' instead of the results of the query, no matter how slowly I go. Because some of the functions are dependent on string and numeric field results being populated, the program hits a run-time error at that code. When I stop debugging -- fully ending running the program -- all the Bberg values that should have populated then appear. I want these values to appear while the program is still running.
I've tried using a combination of DoEvents and Application.OnTime() to return control to the operating system and to get the program to wait for a long time for the data update, but neither worked. Any ideas would be helpful. My code is below. wb is a global-level workbook and ws1 is a global level worksheet.
Public Sub Run_Me()
'Application.DisplayAlerts = False
'Application.ScreenUpdating = False
Call Populate_Me
Call Format_Me
'Application.DisplayAlerts = True
'Application.ScreenUpdating = True
End Sub
Private Sub Populate_Me()
Dim lRow_PM As Integer
Dim xlCalc As XlCalculation
Set wb = ThisWorkbook
Set ws1 = wb.Sheets(1)
'clear out any values from previous day
If wb.Sheets(ws1.Name).Range("A1").Value <> "" Then
wb.Sheets(ws1.Name).Select
Selection.ClearContents
End If
xlCalc = Application.Calculation
Application.Calculation = xlCalculationAutomatic
Range("A1").Value = "F5"
Range("B1").Value = "Term"
Range("C1").Value = "PX LAST"
Range("A2").Select
ActiveCell.FormulaR1C1 = "=BDS(""YCCF0005 Index"",""CURVE_MEMBERS"",""cols=1;rows=15"")"
BloombergUI.RefreshAllStaticData
Range("B2").Select
ActiveCell.FormulaR1C1 = "=BDS(""YCCF0005 Index"",""CURVE_TERMS"",""cols=1;rows=15"")"
BloombergUI.RefreshAllStaticData
Application.OnTime Now + TimeValue("00:00:10"), "HardCode"
'******more code*******'
End Sub
Sub HardCode()
Range("C2").Select
ActiveCell.FormulaR1C1 = "=BDP($A2,C$1)"
BloombergUI.RefreshAllStaticData
End Sub
A way to get around this issue is to put all subs, etc that you want to run after pulling the bloomberg data into a different sub. You must do this each time you call Bloomberg information. If you call another sub in the "master" sub after the Application.OnTime Now +TimeValue("00:00:15"), it will fail- you must put all subs following into a new master sub.
For example:
Instead of
Sub Master1()
Application.Run "RefreshAllStaticData"
Application.OnTime Now + TimeValue("00:00:15"), "OtherSub1"
'This will cause the Bloomberg Data to not refresh until OtherSub2 and 3 have run
OtherSub2
OtherSub3
End Sub
It should be
Sub Master1()
Application.Run "RefreshAllStaticData"
Application.OnTime Now + TimeValue("00:00:15"), "Master2"
End Sub
Sub Master2()
OtherSub1
OtherSub2
OtherSub3
End Sub
Hope that helps
I googled for BloombergUI.RefreshAllStaticData and was immediately taken to this Mr Excel page: http://www.mrexcel.com/forum/showthread.php?t=414626
We are not supposed post answers which are only links in case that link disappears and takes the answer with it. However, I am not sure I understand the question or the answer well enough to summarise it.
The Google link will probably exist for the forseeable future.
Within Mr Excel, the chain is: MrExcel Message Board > Question Forums > Excel Questions > Bloomberg links and macros.
The key information appears to be:
On your Bloomberg terminal if you type in WAPI < GO > you will find listings of the Bloomberg API and downloadable examples.
Using the helpfile information in that area we can build a more robust solution to this using the Bloomberg Data Type Library. Go to Tools | References and add a reference to this library. This code can then be used to populate the cells:
Sub Test2()
Dim vResults, vSecurities, vFields
Dim objBloomberg As BLP_DATA_CTRLLib.BlpData
'fill our arrays - must be 1 dimension so we transpose from the worksheet
With Application.WorksheetFunction
vSecurities = .Transpose(Sheet1.Range("B2:B4").Value)
vFields = .Transpose(.Transpose(Range("C1:H1").Value))
End With
Set objBloomberg = New BLP_DATA_CTRLLib.BlpData
objBloomberg.AutoRelease = False
objBloomberg.Subscribe _
Security:=vSecurities, _
cookie:=1, _
Fields:=vFields, _
Results:=vResults
Sheet1.Range("C2:H4").Value = vResults
End Sub
Once you have tried out Mr Excel's solution, perhaps you could update this answer for the benefit of future visitors.
I gathered some information from around the web and wrote what imho is an improved version in comparison with everything I have found so far:
Private WaitStartedAt As Double
Private Const TimeOut As String = "00:02:00"
Public Function BloomCalc(Callback As String) As Boolean
Dim rngStillToReceive As Range
Dim StillToReceive As Boolean
Dim ws As Worksheet
StillToReceive = False
If WaitStartedAt = 0 Then
WaitStartedAt = TimeValue(Now())
End If
If TimeValue(Now()) >= WaitStartedAt + TimeValue(TimeOut) Then
GoTo errTimeOut
End If
For Each ws In ActiveWorkbook.Worksheets
Set rngStillToReceive = ws.UsedRange.Find("*Requesting Data*", LookIn:=xlValues)
StillToReceive = StillToReceive Or (Not rngStillToReceive Is Nothing)
Next ws
If StillToReceive Then
BloomCalc = False
Application.OnTime Now + (TimeSerial(0, 0, 1)), Callback
Else
WaitStartedAt = 0
BloomCalc = True
End If
Exit Function
errTimeOut:
Err.Raise -1, , "BloomCalc: Timed Out. Callback = " & Callback
End Function
It should an arbitrary task by calling a sub like DoSomething()
Public Sub DoSomething()
DoSomethingCallback
End Function
That calls a "callback" function that will call itself until either the data has been refreshed or the time limit reached
Public Sub AutoRunLcbCallback()
If BloomCalc("AutoRunLcbCallback") Then
MsgBox "Here I can do what I need with the refreshed data"
' for instance I can close and save the workbook
ActiveWorkbook.Close True
End If
End Sub
Any comment is appreciated. A possible improvement might be to allow the workbook and / or worksheet to be an input of the function but I really didn't see the need for that.
Cheers
Hello there I think I have found a solution to this problem and I really want to share this with you guys.
Before starting with the real answer I want to make sure everyone understands how Application.OnTime actually works. And If you already know then you can safely skip to THE SOLUTION below.
Let's make a TOY EXAMLPE example with two subroutines Sub First() and Sub Second() and one variable x that is declared outside, so that it has scope inside the whole Module
Dim x as integer
Sub First()
x = 3
Application.OnTime Now + TimeSerial(0, 0, 2), "Sub2"
x = 2*x
End Sub
Sub Second()
x = x + 1
End Sub
I thought that the commands were executed in the following order:
x = 3
Application.OnTime Now + TimeSerial(0, 0, 2), "Sub2"
Then after 2 seconds of wait, in Sub Second() x = x + 1, hence 4
Finally we go back to Sub First() where x = 2*x , so that in the end x is equal to 8.
It turns out that this is not the way VBA operates; what happens instead is:
x = 3
Application.OnTime Now + TimeSerial(0, 0, 2), "Sub2"
Here the remaing code in Sub First() is executed until THE END, before switching to Sub Second().
So x = 2*x is executed right away along with every line of code that appears until the end of Sub First(). Now x is equal to 6.
Finally, after 2 seconds of waiting it executes the instruction in Sub Second(), x = x + 1, so that in the end x is equal to 7
This happens independently of how much time you make the application wait. So for instance if in my example, after
Application.OnTime Now + TimeSerial(0, 0, 2), "Sub2"
VBA took 10 seconds to execute the line
x = 2*x
it would still have to finish the execution of that command before switching to Sub Second().
WHY IS THIS IMPORTANT?
Because in the light of what I just explained I can now show you my solution to the OP question. Then you can adapt it to your needs.
And YES!!! This works with For Loops too!
THE SOLUTION
I have two subroutines:
BLPDownload() one where I refresh a workbook and I have to wait for the values to be dowloaded in order to execute some other code ...
BLPCheckForRefresh() where I check if all data have been downloaded
So just like before, I declare two variables with Module-Level Scope
Dim firstRefreshDone As Boolean, Refreshing As Boolean
Sub BLPDownload()
CHECK:
What I do right below is to:
check if I already told VBA to Refresh the workbook. Of course the first time you run the macro you have not; hence firstRefreshDone = False and it steps into this block of the if statement.
next it calls the other Sub BLPCheckForRefresh() and it exits the current Subroutine.
And this is the trick. To Exit the Subroutine after calling Application.OnTime*
Inside BLPCheckForRefresh() what happens is
that I set the value of firstRefreshDone = True
check if, in the UsedRange, I have cells wiht #N/A Requesting Data. If I have, the value of Refreshing = True.
finally I call back the Sub BLPDownload()
If Not firstRefreshDone Then
Application.Run "RefreshEntireWorkbook"
Application.Run "RefreshAllStaticData"
Application.OnTime Now + TimeSerial(0, 0, 4), "BLPCheckForRefresh"
Exit Sub
This time though, firstRefreshDone = True so, if also the refreshing is finished it goes to AFTER_REFRESH where you can put all the code you want, else ...
ElseIf Not Refreshing Then
GoTo AFTER_REFRESH
if the refresh is not finished, i.e. if I have cells wiht #N/A Requesting Data it calls the other Sub BLPCheckForRefresh() and it exits the current Subroutine again.
This funny game goes on and on until we have no more #N/A Requesting Data in our UsedRange
Else
Refreshing = False
Application.OnTime Now + TimeSerial(0, 0, 4), "BLPCheckForRefresh"
Exit Sub
End If
AFTER:
some code ...
End Sub
This is the sub where I check if refreshing is done.
Sub BLPCheckForRefresh()
Dim rng As Range, cl As Range
Set rng = Foglio1.UsedRange
As explained above here I set the value of firstRefreshDone = True
firstRefreshDone = True
And this is the loop where i go through each cell in the usedrange looking for #N/A Requesting Data
On Error Resume Next
For Each cl In rng
If InStr(cl.Value2, "#N/A Request") > 0 Then
Refreshing = True
Exit For
End If
Next cl
On Error GoTo 0
Finally I call back the Sub BLPDownload()
Call BLPDownload
End Sub
So this is my solution. I works for me and with another dirty trick that exploits always the GoTo statements and another Module-Level Scope variable that keeps count of the number of iteration it is possible to use this structure in For Loops too.
That being said I want to point out that in my opinion the best solution to this problem is to use Bloomberg API as suggested by Tony Dallimore.
Hope this helps!!
Related
Currently I have a reset button for my excel sheet which will clear the contents except the formulas I have. Is there a way to dynamically count how many times I "pressed" the reset button and clear contents?
This..
Public countR As Long
Sub Alternative1() 'This will return to 0 when you close the workbook
countR = countR + 1
MsgBox "The document has been cleared " & countR & " time(s)"
End Sub
or..
Sub Alternative2()
Dim rng As Range
Set rng = Range("A1") 'Change to some cell that isn't cleared by your code
rng.Value = rng.Value + 1
End Sub
and call them like..
Sub WhatYourCodeMaybeLooksLike()
Range("B1:C100").ClearContents
Alternative1
Alternative2
End Sub
The comment by reportgunner above is correct, but if you're asking for VBA options then look at module level variables.
Public x As Integer
Sub Button1_Click()
x = x + 1
MsgBox x
End Sub
x will begin as 0 when you open the workbook and will then be incremented each time Button1 is hit, in this case.
Slightly different are static variables which you can declare in the procedure with the code. Both will work fine in VBA.
The variable will often reset if your code hits errors during debugging. So the cell option might be preferable if you never want to lose track of the number.
I have a macro that hides certain rows when the values in a cell change. However this macro is not running unless you enter the target cell and click on it. I have tried several alternatives but none work for me.
Sheet
Private Sub Worksheet_Change(ByVal Target As Range)
If Range("$b$156").Value = 1 Then Call oculta_4
If Range("$b$156").Value = 2 Then Call oculta_5
If Range("$b$156").Value = 3 Then Call oculta_6
If Range("$b$156").Value = 4 Then Call oculta_7
End Sub
Macro
Sub oculta_4()
Rows("158:176").EntireRow.Hidden = False
Range("$c$158").Select
For Each celda In Range("$c$158:$c$176")
If celda.Value = 0 Then
ActiveCell.EntireRow.Hidden = True
End If
ActiveCell.Offset(1).Select
Next
End Sub
As others have said, to respond to a value changed by a Formula, you need to use Worksheet_Calculate.
As Worksheet_Calculate does not have a Target property, you need to create your own detection of certain cells changing. Use a Static variable to track last value.
You should also declare all your other variables too.
Repeatedly referencing the same cell is slow and makes code more difficult to update. Put it in a variable once, and access that
Select Case avoids the need to use many If's
Don't use Call, it's unnecessary and obsolete.
Adding Application.ScreenUpdating = False will make your code snappy, without flicker
Writing the hidden state of a row takes a lot longer than reading it. So only write it if you need to.
Something like this (put all this code in the code-behind your sheet (that's Hoja1, right?)
Private Sub Worksheet_Calculate()
Static LastValue As Variant
Dim rng As Range
Set rng = Me.Range("B156")
If rng.Value2 <> LastValue Then
LastValue = rng.Value2
Select Case LastValue
Case 1: oculta_4
Case 2: oculta_5
Case 3: oculta_6
Case 4: oculta_7
End Select
End If
End Sub
Sub oculta_4()
Dim celda As Range
Application.ScreenUpdating = False
For Each celda In Me.Range("C158:C176")
With celda.EntireRow
If celda.Value = 0 Then
If Not .Hidden Then .Hidden = True
Else
If .Hidden Then .Hidden = False
End If
End With
Next
Application.ScreenUpdating = True
End Sub
I am experiencing an odd bug on Excel. I have a macro that shows a non-modal userform when I press CTRL+m (Macro shortcut). Every once in a while, and it's not that frequent (Shows up once or twice during the day, I use the macro every 5 minutes or so), Excel won't run the macro, won't show the userform and will just beep (as in "mistake, cannot proceed executing code").
I went into the Macro window to try to press "Run" and manually execute, but all buttons are disabled, except for "Create". If you click it, it says the macro name is not valid. As you can see in the screenshot below, the name of the macro shows the instance where the code is (Sheet1 of the workbook).
Sometimes it can be fixed by saving the workbook and just re-trying, but sometimes it doesn't; when it doesn't, I run a different macro (by double clicking a specific column) that shows a modal userform, and executing its code. Then my first macro returns to normal.
Any help will be very much appreciated.
Edit: Adding the code as requested in the comments
Sub ShowCommentWindow()
Dim myCell As Range
Dim companyColumn As Long
Dim wbk as Workbook
Dim company as String
Dim phone as Long
Set wbk = ActiveWorkbook
For Each myCell In wbk.Worksheets(1).Range("A1:Q1")
If myCell.Text = "Company" Then
companyColumn = myCell.Column
company = ActiveCell.Text
phone = ActiveCell.Offset(0, 4).Value
Exit For
End If
Next myCell
If ActiveCell.Column = companyColumn Then
If EmailForm.Visible Then
GoTo ExitProc
Else
If Not ActiveCell.Row < 4 Then
ActiveWindow.ScrollRow = ActiveCell.Row - 3
Else
ActiveWindow.ScrollRow = ActiveCell.Row
End If
If CommentWindow.Visible Then
CommentWindow.AddButton.SetFocus
CommentWindow.CommentBox.SetFocus
Exit Sub
Else
CommentWindow.Show
ManageComments
AddComment
End If
End If
End If
ExitProc:
End Sub
Edit2: Posting more code, for QueryClose:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
Dim myCell As Range
Dim isCompany As String
If Not CommentWindow.CommentBox.Text = CommentWindow.TextCopy.Text Then
saveConf = MsgBox("Changes have not been saved yet. Do you want to save?", vbExclamation + vbYesNoCancel + vbDefaultButton2, "Save changes?")
If saveConf = vbYes Then
Call SaveComment
GoTo ExitProc
ElseIf saveConf = vbCancel Then
changed = True
Cancel = 1
CommentWindow.AddButton.SetFocus
CommentWindow.CommentBox.SetFocus
'CommentWindow.CommentBox.Text = CommentWindow.TextCopy.Text
Else
CommentWindow.TextCopy.Text = CommentWindow.CommentBox.Text
GoTo ExitProc
End If
Else
If Not changed = True Then
GoTo ExitProc
End If
End If
ExitProc:
End Sub
Seems like the issue is not unloading the forms from ( Unload(UserForm) )
This leads to a memory leak.
Even the official documentation -this refers to Access, but, should behave the same for Excel (there's no Form object or userform documentation there)- state the Lifecycle is Unload->Deactivate->Close, and this should happen when you close the userform as well, daily usage has shown that Unload if not stated may not be triggered when closing the userform.
The lifecycle is not that strictly monitored sometimes, but, that may lead to memory leaks and strange behaviors, always when working with objects you shouldn't rely that garbage collector will clean them if not specified. Probably adding something to confirm that terminate is being correctly handled will be helpful.
EDIT
If you're having problems remembering the unload -or still having problems with memory-, it will be a good practice to do the following:
Sub MyMainProcess()
Dim myform As UserForm1: Set myform = UserForm1 'this is your UserForm name
myform.Show
'my stuff needed...
Unload myform
Set myform = Nothing
End Sub
Unload and Nothing to clean as much as possible with coding
I see that you're calling an "outside" macro (it's not within the active workbook) - Is it possible that then those roughly 2 times a day that it doesn't work that workbook (Database 2 Lumber.xlsm) is being used by someone else at that time (eight running that, or another macro?).
If so, What I have done before is save a local copy of the workbook each time the macro is run
I see there's some old stuff about this, but i'm hoping someone has found a new solution.
with my routine, I update the dates in 3 workbooks, so that the BDH commands get the latest prices, then i copy the results from those 3 workbooks into a separate summary workbook. However, as common apparently, The BDH function doesn't refresh/update, even after a timepause in there.
What are the latest findings on refreshing or re-requesting the BDH commands before i Copy paste them to the results sheet?
===========
Twenty.Worksheets("Portfolio_2016").Activate
[K3].Value = TradeDay
[L3].Value = PrevTradeDay
'Japan.Worksheets("Portfolio_2016").Activate
'[K3].Value = TradeDay
'[L3].Value = PrevTradeDay
'AAR.Worksheets("Portfolio_2016").Activate
'[K3].Value = TradeDay
'[L3].Value = PrevTradeDay
Call RefreshStaticLinks
End Sub
Public Sub RefreshStaticLinks()
Call Twenty.Worksheets("Portfolio_2016").Range("K7:Q26").Select
Call Application.Run("RefreshCurrentSelection")
Call Application.OnTime(Now + TimeValue("00:00:01"), "ProcessData")
End Sub
Private Sub ProcessData()
Dim c As Range
For Each c In Selection.Cells
If c.Value = "#N/A Requesting Data..." Then
Call Application.OnTime(Now + TimeValue("00:00:01"), "ProcessData")
Exit Sub
End If
Next c
Call CopyPaste
End Sub
You need to use Application.OnTime to achieve this. Bloomberg formulas will not update while a macro is paused. There are examples in the FAQ section on WAPI on Bloomberg. The below is taken from there where you will also find an example spreadsheet.
[Download Example! Download a working Excel VBA example here]
The data returned by any of our BDx() family of functions is in an asynchronous manner. Therefore, if your Excel VBA application [macro] is dependent upon returned Bloomberg data, we recommend that you incorporate the following logic into your VBA code that will, essentially, pause your application while the data is populated in your Excel cells:
Option Explicit
Private Const MAXROWS = 25
Sub fillFormula()
Range("B2:B" & MAXROWS).Clear
Range("B2").Formula = "=BDP($A2,B$1)"
Range("B2").AutoFill Range("B2:B" & MAXROWS), xlFillDefault
checkStatus
End Sub
Sub checkStatus()
Dim i
For i = 2 To MAXROWS
' Check to make sure that the cells are NOT requesting data
If "#N/A Requesting Data..." = Cells(i, 2) Then
Application.OnTime (Now + TimeValue("00:00:02")), "checkStatus"
Exit Sub
End If
Next i
MsgBox "We just finished getting the values"
' Add code here that would process the data now that it's updated
End Sub
The above code can be added to a blank module and the following code will be added to the click event handler, for instance, of a button on the worksheet:
fillFormula
Hi I am using the following code to parsing some data from website to excel , I need to create a routine in order to update /Refresh the data and keep is up to date and I been advice to start new topic , is big list with multiple sheets so take long time every time excel has to calculate , I hope someone can may help me out
Public Function giveMeValue(ByVal link As Variant) As Variant
Set htm = CreateObject("htmlFile")
With CreateObject("msxml2.xmlhttp")
.Open "GET", link, False
.send
htm.body.innerhtml = .responsetext
End With
If Not htm.getelementbyId("JS_topStoreCount") Is Nothing Then
giveMeValue = htm.getelementbyId("JS_topStoreCount").innerText
Else
giveMeValue = "0"
End If
htm.Close
Set htm = Nothing
End Function
to retrive the value I use =GiveMeValue(A1) and condition formatting the returned value I use the Following code :
Dim color As Integer 'Start Color range
For Each cell In Sheets(1).Range("M4:M5000")
If IsEmpty(cell) Then GoTo nextcell:
If Not IsNumeric(cell.Value) Then GoTo nextcell:
If cell.Value > 14 Then
color = 4
ElseIf cell.Value < 8 Then color = 3
Else: color = 6
End If
cell.Interior.ColorIndex = color
nextcell:
Next cell
End Sub
so as I am not skilled at all with VBA I may unappropriate use the following code to try get refresh it but without result:
Sub Refresh()
Dim WAIT As Double
WAIT = Timer
While Timer < WAIT + 10
DoEvents 'do nothing'
ActiveWorkbook.RefreshAll
Wend
MsgBox "Update Finished Successfully !"
'End Sub
Sounds ambitious. I like it! :)
For a start can you just use conditional formatting? Will be a lot quicker than looping through every cell and changing colour.
Secondly, make your GiveMeValue function "volatile" (http://www.excel-easy.com/vba/examples/volatile-functions.html) so that it refreshes every time you recalculate the page i.e. add a line "application.volatile(true) before anything else in the function.
Thirdly: run your refresh code in the "worksheet_calculate" event and change it to:
Sub Refresh()
Dim WAIT As Double
WAIT = Timer
While Timer < WAIT + 10
DoEvents 'do nothing'
Wend
wsSheet.Calculate
End Sub
So it runs... waits a bit... calculates, then that calculation triggers another wait... etc... Now that your function is volatile, it should refresh all the "getvalue" formulas you have on the current sheet...
And I guess forthly put a "wsSheet.calculate" in the worksheet_activate event to trigger start of that endless loop...
Having said all that, perhaps that endless loop will cause huge issues i.e. mega computer slow down, inability to use computer, and general doom... only one way to find out! The doevents thing should in theory make everything OK. Maybe add if activesheet.codename = "wsSheet" then... so its only running when you're on that sheet...