I have an event handler Sub that listens for a response message from the Bloomberg API, and stores it in a Dd array.
According to MSDN, event handlers must be subs, but I would like to do further analysis on the data in my main method.
How can I reference the array created in the event handler, such that I can continue to process the data?
Event handler:
Private Sub session_ProcessEvent(ByVal obj As Object)
On Error GoTo errHandler
Dim eventObj As blpapicomLib2.Event
Set eventObj = obj
If Application.Ready Then
If eventObj.EventType = PARTIAL_RESPONSE Or eventObj.EventType = RESPONSE Then
Dim it As blpapicomLib2.MessageIterator
Set it = eventObj.CreateMessageIterator()
Dim numResponse As Integer
numResponse = 0
Dim data() As Variant
Do While it.Next()
numResponse = numResponse + 1
Dim msg As Message
Set msg = it.Message
Dim securityData As Element
Dim securityName As Element
Dim fieldData As Element
Set securityData = msg.GetElement("securityData")
Set securityName = securityData.GetElement("security")
Set fieldData = securityData.GetElement("fieldData")
Sheet1.Cells(currentRow, 4).Value = securityName.Value
Dim numDates As Integer
Dim numFields As Integer
numDates = fieldData.NumValues
numFields = fieldData.GetValue(0).NumElements
ReDim data(numDates, numFields, numResponse)
Dim b As Integer
For b = 0 To numDates - 1
Dim fields As blpapicomLib2.Element
Set fields = fieldData.GetValue(b)
Dim a As Integer
For a = 0 To numFields - 1
Dim field As Element
Set field = fields.GetElement(a)
data(b, a, numResponse) = field.Value
Sheet1.Cells(currentRow, a + 5).Value = data(b, a, numResponse)
Next
currentRow = currentRow + 1
Next b
Loop
' skip a row for next security
currentRow = currentRow + 1
End If
End If
Exit Sub
errHandler:
MsgBox Err.Description
End Sub
...and the sub I would like to process the array in here...
Public Sub RefDataExample()
' Calculate the number of securities and fields
Dim numSecurity As Integer
Dim numFields As Integer
numSecurity = 0
numFields = 0
' clear data area
Range("D4", "H60000").Clear
Do While Cells(numSecurity + 4, 1).Value <> ""
numSecurity = numSecurity + 1
Loop
Do While Cells(numFields + 4, 2).Value <> ""
numFields = numFields + 1
Loop
Dim sSecurity() As String
Dim sFields() As Variant
ReDim sSecurity(0 To numSecurity - 1) As String
ReDim sFields(0 To numFields - 1) As Variant
Dim i As Integer
For i = 0 To numSecurity - 1
sSecurity(i) = Cells(i + 4, 1).Value
Next i
For i = 0 To numFields - 1
sFields(i) = Cells(i + 4, 2).Value
Next i
bbControl.MakeRequest sSecurity, sFields
'Process response array here
End Sub
Remove
Dim data() As Variant
from within all the subs it currently appears in (like from within your session_ProcessEvent())
and place
Public data As Variant
at the top of the module containing your sub (actually you can place it at the top of any module you like)
and you'll have ”data” visible from all subs/functions of your project
Related
I am working with a schedule, that I have imported and formatted into my workbook.
I am wanting this to populate Phase in the upper listbox and then when a phase is selected the sub-task associated with those phases are displayed in the bottom listbox.
I want to use an array but I seem to be having problems when the columns are not next to each other or there are "gaps" with the blank cells.
My first attempt using assigning the Array to the currentregion worked but brought all columns and fields in. Listbox1 should contain (ID, PHASE NAME, DURATION, START DATE, FINISH DATE) List box 2 should when a Phase is selected contain the subtasks if any from the column to the right, listed before the next next Phase name. (ID, SUB-TASK NAME, DURATION, START DATE, FINISH DATE)
(See picture)
I have code but its more me trouble-shooting than an actual semi working script.
Dim shT As Worksheet
Dim schnumrng1 As Range
Dim schnumrng2 As Range
Dim schnumrng3 As Range
Dim schnumrng4 As Range
Dim schnumrng5 As Range
Dim schpersonrng As Range
Dim schphaserng As Range
Dim schlistrng As Range
Dim maxschnum
Dim schstatus
Dim schperson
Dim schlistnum
Dim Ar() As String
Dim i As Long
Dim j As Long
Dim rng As Range
Dim cl As Range
Dim lc
'allowevents = True
''Set Screen parameters
'Application.ScreenUpdating = False
'Application.EnableEvents = False
'
Worksheets("Schedule").Visible = True
ThisWorkbook.Worksheets("Schedule").Activate
'
Set shT = Worksheets("Schedule")
maxschnum = shT.Cells(shT.Rows.Count, "A").End(xlUp).Row
Set schnumrng = Range("B5", "B" & maxschnum)
'Set Ranges for the list box
Set schnumrng1 = Range("A5", "A" & maxschnum)
Set schnumrng2 = Range("B5", "B" & maxschnum)
Set schnumrng3 = Range("D5", "D" & maxschnum)
Set schnumrng4 = Range("E5", "E" & maxschnum)
Set schnumrng5 = Range("F5", "F" & maxschnum)
'This is static and not moving to the next line in my for statement / switched to named ranges and errors
Set rng = schnumrng1, schnumrng2, schnumrng3, schnumrng4, schnumrng5
'Set rng = Range("A5,B5,D5,E5,F5")
i = 1
j = 1
For Each lc In schnumrng
If lc <> vbNullString Then
For Each cl In rng
ReDim Preserve Ar(1, 1 To i)
Ar(j, i) = cl.Value
i = i + 1
Next cl
Else
End If
j = j + 1
Next lc
With ScheduleForm.SchMainTasklt
.ColumnCount = i - 1
.ColumnWidths = "50;150;50;50;50"
.List = Ar
End With
My problem then is two fold, trying to use the dynamic ranges or another tool Index? collection? to populate the 1st list box. 2. How to deal with blanks and noncontiguous columns when data is not separated for organization purposes.
I don't know if I figured out your intentions well.
First, only the data in column b, not empty cells, is extracted from listbox1.
Second, when listbox1 is selected, data related to listbox2 is collected through the selected listbox value.
Module Code
Place this code in the module. This is because global variables must be used.
Public vDB As Variant
Public Dic As Object 'Dictionary
Sub test()
Dim shT As Worksheet
Dim maxschnum As Long
Dim Ar() As String
Dim i As Long
Dim j As Long
Dim vC() As Variant
Dim cnt As Integer, n As Integer
Dim c As Integer
Dim s As String, s2 As String
Worksheets("Schedule").Visible = True
ThisWorkbook.Worksheets("Schedule").Activate
'
Set Dic = CreateObject("Scripting.Dictionary") 'New Scripting.Dictionary
Set shT = Worksheets("Schedule")
maxschnum = shT.Cells(shT.Rows.Count, "A").End(xlUp).Row
With shT
vDB = .Range("a5", .Range("f" & maxschnum))
End With
'vC is data colum A,B,D,E,F
vC = Array(1, 2, 4, 5, 6)
s2 = vDB(2, 2)
For i = 2 To UBound(vDB, 1)
s = vDB(i, 2) 'column B
If s = "" Then
n = n + 1
Else
If Dic.Exists(s) Then
Else
If i > 2 Then
Dic(s2) = Dic(s2) & "," & n
End If
Dic.Add s, i
s2 = s
cnt = cnt + 1
ReDim Preserve Ar(1 To 5, 1 To cnt)
For c = 0 To UBound(vC)
Ar(c + 1, cnt) = vDB(i, vC(c))
Next c
End If
n = 0
End If
Next i
Dic(s2) = Dic(s2) & "," & n
' Records information about the data in a dictionary.
' Dic is "phase neme" is Key, Item is "2,4"
' example for KICkOFF
' dic key is "KICKOFF", Item is "5,4"
' 5 is KICOFF's row number in array vDB
' 4 is the number of blank cells related to kickoff.
With ScheduleForm.SchMainTasklt
.ColumnCount = 5
.ColumnWidths = "50;150;50;60;60"
.BoundColumn = 2
'.List = Ar
.Column = Ar 'In the state that the array has been converted to row column, you can use listbox.column.
End With
End Sub
Form Code
Private Sub UserForm_Initialize()
Call test
End Sub
Private Sub SchMainTasklt_Click()
Dim s As String, sItem As String
Dim arr As Variant, vC As Variant
Dim vR() As Variant
Dim st As Long, ed As Long
Dim iLast As Long, iFirst As Long
Dim i As Long, n As Integer
Dim j As Integer
vC = Array(1, 3, 4, 5, 6) 'data colums A,C,D,E,F
s = SchMainTasklt.Value
'MsgBox s
sItem = Dic(s)
arr = Split(sItem, ",")
st = Val(arr(0))
ed = Val(arr(1))
iFirst = st + 1
iLast = st + ed
If ed = 0 Then
MsgBox "no data!!"
Exit Sub
End If
For i = iFirst To iLast
n = n + 1
ReDim Preserve vR(1 To 5, 1 To n)
For j = 0 To UBound(vC)
vR(j + 1, n) = vDB(i, vC(j))
Next j
Next i
With ListBox2
.ColumnCount = 5
.ColumnWidths = "50;150;50;60;60"
.BoundColumn = 2
.Column = vR
End With
End Sub
Result Image
When you click the "KICKOFF" , Show kickoff related data in listbox2.
I am working with Bloomberg's API in VBA and I want to be able to take in the arrays that the API gives out from requesting historical data and put it into a table that has field names. However, the array that the API gives me is given in this format: (x,y)(Z) but I cannot use that for inserting into a table. I also want to be able to add another piece of data into the array while I convert from one form to another
I have tried just going through the Bloomberg array and replacing each element in a different array, but the main issues I have are not being able to know how big I need the array to be and how I am going to loop through the bloomberg API without going out of index and getting an error. I have tried using Ubound, but it does not work the way I have intended.
This is the code I have tried using to convert my array and then insert it. It just puts in blank values and does not put in anything into the table
Sub mWriteToTable(vTableName As String, ByVal vArray As Variant, vCUSIPS As Variant, vFields As Variant)
On Error GoTo ErrorHandler
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim x As Long, y As Long
Dim TEST As String
Dim DataArray() As Variant
Set db = CurrentDb
Set rs = db.OpenRecordset(vTableName, dbOpenDynaset, dbSeeChanges)
TEST = ""
Dim xBound As Integer, yBound As Integer, ThirdBound As Integer, fieldcount As Integer, NewBoundY As Integer, Z As Integer
Dim Boundarynum As Integer
Boundarynum = 0
Dim Boundarynum1 As Integer
Boundarynum1 = 0
fieldcount = UBound(vFields, 1) + 1
xBound = UBound(vArray, 1)
yBound = UBound(vArray, 2)
NewBoundY = fieldcount * (fieldcount + 1)
ReDim DataArray(0 To 20, 0 To (xBound + 1))
'using a static size for the array for now. Will try and make it the same size as the bloomberg array
'TRANSFORMING ARRAY FROM BLOOMBERG
For x = 0 To xBound
For y = 0 To NewBoundY
For Boundarynum1 = 0 To yBound
On Error Resume Next
DataArray(Boundarynum, Boundarynum1) = vArray(x, y)(Boundarynum1)
Next
Boundarynum = Boundarynum + 1
Next
Next
'TRANSFORMING ARRAY FROM BLOOMBERG
'set CUSIP in array
y = 0
Dim counter As Integer
counter = 0
For Z = 0 To 20
If DataArray(Z, 0) = "" Then
Debug.Print ("")
counter = 1
ElseIf counter = 1 And DataArray(Z, 0) <> "" Then
y = y + 1
DataArray(Z, 3) = vCUSIPS(y)
counter = 0
Else
DataArray(Z, 3) = vCUSIPS(y)
End If
Next
'set CUSIP in array
For x = 0 To 20
With rs
.AddNew
For y = 0 To yBound
' On Error GoTo Line1
' If vArray(x, y) = "NA" Then
' TEST = "This is a test"
' End If
'Line1:
.fields(y) = DataArray(x, y)
Next
.Update
End With
Next
'Call fImmediateWindow(vArray)
ErrorHandler:
If Err.Number <> 0 Then
Dim vMsg As String
vMsg = "Error # " & Str(Err.Number) & " was generated by " & Err.Source & Chr(13) & "Error Line: " & Erl & Chr(13) & Err.Description
MsgBox vMsg, , "Error", Err.HelpFile, Err.HelpContext
End If
rs.Close
Set rs = Nothing
db.Close
Set db = Nothing
End Sub
'''
This is the way the Bloomberg Array looks when I get it. I am unsure of how to really work around this. The array from the program above just becomes blank.
Each element of the Bloomberg array is returning 2 sets of data. The key is to have your array have double the number of elements of the top level Bloomberg array.
Sub ConvertBloombergTestData()
Dim r As Variant
r = getBloombergTestData
Dim Values As Variant
Dim n As Long
Dim j As Long
Dim Item
ReDim Values(1 To (UBound(r) + 1) * 2, 1 To 2)
For n = LBound(r) To UBound(r)
j = j + 1
Item = r(n, 0)
Values(j, 1) = Item(0)
Values(j, 2) = Item(1)
Item = r(n, 1)
j = j + 1
Values(j, 1) = Item(0)
Values(j, 2) = Item(1)
Next
End Sub
Not knowing the the array nesting but knowing that we are returning pairs of data, we could add all the data to a collection and create our array bu iterating over the collection.
Sub Test()
Dim r As Variant, Values As Variant
r = getBloombergTestData
Values = ConvertBloombergArrayTo2d(r)
End Sub
Function ConvertBloombergArrayTo2d(BloombergArray)
Dim Map As New Collection
FlattenArray Map, BloombergArray
Dim Results As Variant
ReDim Results(1 To Map.Count / 2, 1 To 2)
Dim n As Long, j As Long
For n = 1 To Map.Count Step 2
j = j + 1
Results(j, 1) = Map.Item(n)
Results(j, 2) = Map.Item(n + 1)
Next
ConvertBloombergArrayTo2d = Results
End Function
Sub FlattenArray(Map As Collection, Element As Variant)
If Right(TypeName(Element), 2) = "()" Then
Dim Item
For Each Item In Element
FlattenArray Map, Item
Next
Else
Map.Add Element
End If
End Sub
I have a listbox with multiple (3 columns) selected items and I would like to move only the selected ones to another listbox, but this listbox should also contain 3 columns.
Before selecting new items, I've added the other items to the listbox1 with this code:
Private Sub UserForm_Initialize()
ListBox1.MultiSelect = 2
ListBox2.MultiSelect = 2
Dim RowsNumber As Integer
Dim RowsNumberOnly As Integer
Dim i As Integer
Dim Customers As Long
Dim customersloop As Long
Dim test As String
RowsNumber = FunctionCount.calculateRows
RowsNumberOnly = FunctionCount.calculateRowsValue
ListBox1.ColumnCount = 3
ListBox1.ColumnWidths = "50;50;50"
ListBox2.ColumnCount = 3
ListBox2.ColumnWidths = "50;50;50"
Customers = 10
i = 0
For customersloop = Customers To RowsNumber
ListBox1.AddItem
ListBox1.List(i, 0) = Sheets("Test").Range("J" & customersloop).Value
ListBox1.List(i, 1) = Sheets("Test").Range("K" & customersloop).Value
ListBox1.List(i, 2) = Sheets("Test").Range("L" & customersloop).Value
i = i + 1
Next
End Sub
After that the selected item need to be transferred to the other listbox2
This is my code:
Private Sub SelectItems_btn_Click()
Dim SelectedItems As String
Dim i As Integer
Dim ListBox2i As Integer
ListBox2i = 0
For i = 0 To ListBox1.ListCount - 1
If ListBox1.Selected(i) = True Then
Me.ListBox2.Additem
Me.ListBox2.List(ListBox2i, 0) = ListBox1.List(i, 0).Value
Me.ListBox2.List(ListBox2i, 1) = ListBox1.List(i, 1).Value
Me.ListBox2.List(ListBox2i, 2) = ListBox1.List(i, 2).Value
ListBox2i = ListBox2i + 1
End If
Next
End Sub
I hope that you could help. I always get the error message that am object is missing.
Best regards
Matthias
If you want to move items from list1 to another list2 you can use the columns property from vba.
this is the solution code after I added the values to my first list1 with 3 columns.
Private Sub SelectItems_Click()
Dim i As Integer
For i = 0 To ListBox1.ListCount - 1
If ListBox1.Selected(i) = True Then
ListBox2.AddItem
ListBox2.Column(0, (ListBox2.ListCount - 1)) = ListBox1.Column(0, i)
ListBox2.Column(1, (ListBox2.ListCount - 1)) = ListBox1.Column(1, i)
ListBox2.Column(2, (ListBox2.ListCount - 1)) = ListBox1.Column(2, i)
End If
Next
End Sub
Best regards
I have a userform that contains a combobox that's populated from the unique items in a worksheet column. I'm trying to sort the keys that represent the items in the combobox in ascending order using the below code, but I'm getting an "Object variable or With block variable not set" error:
Public Function funcSortKeysByLengthDesc(dctList As Object) As Object
Dim curKey As Variant
Dim key As Variant
Dim itX As Integer
Dim itY As Integer
Dim arrTemp() As Variant
Dim d As Object
'Only sort if more than one item in the dict
If dctList.Count > 1 Then
'Populate the array
ReDim arrTemp(dctList.Count)
itX = 0
For Each curKey In dctList
arrTemp(itX) = curKey
itX = itX + 1
Next
For itX = 0 To (dctList.Count - 2)
For itY = (itX + 1) To (dctList.Count - 1)
If arrTemp(itX) > arrTemp(itY) Then
curKey = arrTemp(itY)
arrTemp(itY) = arrTemp(itX)
arrTemp(itX) = curKey
End If
Next
Next
'Create the new dictionary
Set d = CreateObject("Scripting.Dictionary")
For itX = 0 To UBound(arrTemp)
d.Add arrTemp(itX), dctList(itX)
Next
Set funcSortKeysByLengthDesc = d
Else
Set funcSortKeysByLengthDesc = dctList
End If
End Function
I'm not really sure why you're using a Dicionary for this task, but I've assumed it's required elsewhere in your project, so I've tried to dovetail mine into your existing code.
If you are only putting sorted cells into a ComboBox then reading the cells into an array, removing duplicates and sorting that array, then populating the ComboBox would be simpler. There are plenty of examples of how to do each of these tasks on this site, so I won't reproduce them here.
Here's the code for you:
Sub RunMe()
Dim ws As Worksheet
Dim rCell As Range
Dim dctItem As String
Dim dctArray() As String
Dim i As Integer
Dim d As Object
Dim v As Variant
Set ws = ThisWorkbook.Worksheets("Sheet1")
'Code to poulate a few "C" cells
ws.Cells(3, "C").Resize(10).Value = Application.Transpose(Array("Z", "Y", "X", "W", "W", "E", "D", "C", "B", "A"))
UserForm1.Show False
'Clear the combobox
UserForm1.cbNames.Clear
'Create the dictionary
Set d = CreateObject("Scripting.Dictionary")
For Each rCell In ws.Range("C3", ws.Cells(Rows.Count, "C").End(xlUp))
dctItem = CStr(rCell.Value2)
If Not d.Exists(dctItem) Then
d.Add dctItem, dctItem
End If
Next
'Convert the dictionary items to an array
Debug.Print "PRE-SORT"
ReDim dctArray(1 To d.Count)
i = 1
For Each v In d.Items
dctArray(i) = v
i = i + 1
Debug.Print v
Next
'Bubble sort the array
dctArray = BubbleSort(dctArray)
'Populate the dictionary and combobox
Debug.Print "POST-SORT"
Set d = CreateObject("Scripting.Dictionary")
For i = LBound(dctArray) To UBound(dctArray)
d.Add dctArray(i), dctArray(i)
UserForm1.cbNames.AddItem dctArray(i)
Debug.Print dctArray(i)
Next
End Sub
Private Function BubbleSort(tempArray As Variant) As Variant
'Uses Microsoft's version: https://support.microsoft.com/en-us/kb/133135
Dim temp As Variant
Dim i As Integer
Dim noExchanges As Integer
' Loop until no more "exchanges" are made.
Do
noExchanges = True
' Loop through each element in the array.
For i = 1 To UBound(tempArray) - 1
' If the element is greater than the element
' following it, exchange the two elements.
If tempArray(i) > tempArray(i + 1) Then
noExchanges = False
temp = tempArray(i)
tempArray(i) = tempArray(i + 1)
tempArray(i + 1) = temp
End If
Next i
Loop While Not (noExchanges)
BubbleSort = tempArray
End Function
My end result is to output the names in column A to column B in random order.
I have been researching but cant seem to find what I need.
So far I can kinda of randomise the numbers but its still giving me repeated numbers + the heading (A1).
I need it to skip A1 because this is the heading\title of the column and start at A2.
I assume once that is working correctly I add the randomNumber to a random name to Worksheets("Master Sheet").Cells(randomNumber, "B").Value ...something like that...?
OR if there is a better way of doing this let me know.
Sub Meow()
Dim CountedRows As Integer
Dim x As Integer
Dim i As Integer
Dim PreviousCell As Integer
Dim randomNumber As Integer
i = 1
PreviousCell = 0
CountedRows = Worksheets("Master Sheet").Range("A" & Rows.Count).End(xlUp).Row
If CountedRows < 2 Then
' If its only the heading then quit and display a messagebox
No_People_Error = MsgBox("No People entered or found, in column 'A' of Sheetname 'Master Sheet'", vbInformation, "Pavle Says No!")
Exit Sub
End If
Do Until i = CountedRows
randomNumber = Int((Rnd * (CountedRows - 1)) + 1) + 1
If Not PreviousCell = randomNumber Then
Debug.Print randomNumber
i = i + 1
End If
PreviousCell = randomNumber
Loop
Debug.Print "EOF"
End Sub
Here's a quick hack...
Sub Meow()
'On Error GoTo err_error
Dim CountedRows As Integer
Dim x As Integer
Dim i As Integer
Dim PreviousCell As Integer
Dim randomNumber As Integer
Dim nums() As Integer
PreviousCell = 0
CountedRows = Worksheets("Master Sheet").Range("A" & Rows.Count).End(xlUp).Row
ReDim nums(CountedRows - 1)
If CountedRows < 2 Then
' If its only the heading then quit and display a messagebox
No_People_Error = MsgBox("No People entered or found, in column 'A' of Sheetname 'Master Sheet'", vbInformation, "Pavle Says No!")
Exit Sub
End If
For i = 1 To CountedRows
rand:
randomNumber = randomNumbers(1, CountedRows, nums)
nums(i - 1) = randomNumber
Worksheets("Master Sheet").Range("B" & randomNumber) = Range("A" & i)
Next i
Exit Sub
err_error:
Debug.Print Err.Description
End Sub
Public Function randomNumbers(lb As Integer, ub As Integer, used As Variant) As Integer
Dim r As Integer
r = Int((ub - lb + 1) * Rnd + 1)
For Each j In used
If j = r Then
r = randomNumbers(lb, ub, used)
Else
randomNumbers = r
End If
Next
End Function
I've managed something similar previously using two collections.
Fill one collection with the original data and leave the other collection empty. Then keep randomly picking an index in the first collection, adding the value at that index to the second collection and delete the value from the original collection. Set that to loop until the first collection is empty and the second collection will be full of a randomly sorted set of unique values from your starting list.
***Edit: I've thought about it again and you don't really need the second collection. You can pop a random value from the first collection and write it directly to the worksheet, incrementing the row each time:
Sub Meow()
Dim lst As New Collection
Dim rndLst As New Collection
Dim startRow As Integer
Dim endRow As Integer
Dim No_People_Error As Integer
startRow = 2
endRow = Worksheets("Master Sheet").Cells(startRow, 1).End(xlDown).Row
If Cells(startRow, 1).Value = "" Then
' If its only the heading then quit and display a messagebox
No_People_Error = MsgBox("No People entered or found, in column 'A' of Sheetname 'Master Sheet'", vbInformation, "Pavle Says No!")
Exit Sub
End If
' Fill a collection with the original list
Dim i As Integer
For i = startRow To endRow
lst.Add Cells(i, 1).Value
Next i
' Create a randomized list collection
' Use i as a row counter
Dim rowCounter As Integer
rowCounter = startRow
Dim index As Integer
Do While lst.Count > 0
'Find a random index in the original collection
index = Int((lst.Count - 1 + 1) * Rnd + 1)
'Place the value in the worksheet
Cells(rowCounter, 2).Value = lst(index)
'Remove the value from the list
lst.Remove (index)
'Increment row counter
rowCounter = rowCounter + 1
Loop
End Sub
P.S. I hope there's an excellent story behind naming your sub Meow() :P