vb.net to pull data from Excel Data GridView - excel

I have 78 excel columns and I have 5 datagridviews.
How do I make connection?

I understand what you want to achieve, but to get the maximum out of any answer, it would be better if you add some code or some further explanation.
For example how should anyone know which excel data should be displayed in DataGridView one, two etc...
Anyway, i would recommend that you divide the task into two steps:
ReadExcel and DisplayData. In my opinion reading data from excel file via OLEDB is a good way to start. Therefore i recommend reading the following article: http://www.codeproject.com/Tips/705470/Read-and-Write-Excel-Documents-Using-OLEDB
For displaying the data in a DataGridView you need to bind a dataset to it. Maybe you´ll find the following post helpful:
How to bind Dataset to DataGridView in windows application
Its both c# code, but i think getting things running for vb.net is an easy task.
Edit: I found some older vb.net of mine you can use. It´s not that good piece of code but it should get you started. It imports the whole data of an excel sheet. But please don´t just copy and run :)
Public Shared Function ImportExcelSheetData(ByVal ExcelFilePath As String, _
ByVal SourceExcelSheetName As String, _
ByRef pDestDataTable As DataTable, _
ByRef ErrMsg As String, _
Optional ByVal WithHeader As Boolean = False) As Integer
Dim ConnectionString As String = ""
Dim WithHeaderString As String = ""
Dim nOutputRow As Integer = 0
Dim oleExcelCommand As OleDbCommand
Dim oleExcelConnection As OleDbConnection
ImportExcelSheetData = -1 ' Error by default
If System.IO.File.Exists(ExcelFilePath) <> True Then
ErrMsg = "Error: File does not exist." + vbCrLf + "Filepath: " + ExcelFilePath
Exit Function
End If
If WithHeader = True Then
WithHeaderString = "Yes"
Else
WithHeaderString = "No"
End If
ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + ExcelFilePath + ";Extended Properties=""Excel 12.0;HDR=" + WithHeaderString + ";IMEX=1"""
oleExcelConnection = New OleDbConnection(ConnectionString)
oleExcelConnection.Open()
If IsNothing(pDestDataTable) = True Then
pDestDataTable = New DataTable
End If
' if SourceExcelSheetName is not set, use first sheet!
If SourceExcelSheetName.Trim = "" Then
Dim tmpDataTable As DataTable = oleExcelConnection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, Nothing)
if IsNothing(tmpDataTable) OR tmpDataTable.Rows.Count < 1 Then
throw new Exception("Error: Could not determine the name of the first worksheet.")
End If
Dim firstSheetName As String = tmpDataTable.Rows(0)("TABLE_NAME").ToString()
If firstSheetName.Trim() <> "" then
SourceExcelSheetName = firstSheetName
End If
End If
If SourceExcelSheetName <> "" Then
Try
Dim oleAdapter As New OleDbDataAdapter()
oleExcelCommand = oleExcelConnection.CreateCommand()
If SourceExcelSheetName.EndsWith ("$") = True Then
oleExcelCommand.CommandText = "Select * From [" & SourceExcelSheetName & "]"
Else
oleExcelCommand.CommandText = "Select * From [" & SourceExcelSheetName & "$]"
End If
oleExcelCommand.CommandType = CommandType.Text
oleAdapter.SelectCommand = oleExcelCommand
oleAdapter.Fill(pDestDataTable)
oleExcelConnection.Close()
Catch ex As Exception
ErrMsg = Err.Description
Exit Function
End Try
End If
ImportExcelSheetData = 0 ' Ok
End Function

Related

Custom DLL showing in Object Browser but Excel is showing "User-defined type not defined"

I have a .DLL that I've written in VB.Net to be used in Excel. For context the .DLL is supposed to go and retrieve a dataset from an SQL server, convert it to a ADODB.RecordSet that Excel can use.
I've registered from COM interop use and made the assembly COM-Visible by following the instructions here: http://csharphelper.com/blog/2013/10/make-a-c-dll-and-use-it-from-excel-vba-code/
The dll appears in Tools -> References in excel VBA editor.
EDIT
#Rory spotted the mistake on the second line with the incorrect 'set' code. I have corrected this but no improvement.
When I go to look in the Object Browser it also appears there.
However when I go to run the sub routine it fails on the first line with the error message "User-defined type not defined".
I've seen other questions which recommend trying late binding with Set objMyConn = CreateObject("bbsSQLForExcel.SQLCOMS") but this produces an error where excel cannot create the Active X component.
If posting the code for the DLL would help then I can do that as well. (Its not huge)
EDIT Comment have pointed the error could be to do with my VB.net code so I'm going to post it up here and add the vb.net tag.
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
Namespace bbsSQLForExcel
<ComVisible(True)>
Public Class SQLCOMS
Function SQLDate(ByVal dtmDate As DateTime, Optional ByVal blTime As Boolean = False) As String
'From #BBS, TimeLog, BulkMailer, Contacts, BBSProjects, SchemeDocs, Billings, FileLoader.
Dim strDate As String
strDate = Month(dtmDate) & "/" & Microsoft.VisualBasic.Day(dtmDate) & "/" & Microsoft.VisualBasic.Year(dtmDate)
If blTime Then
strDate &= " " & Microsoft.VisualBasic.Hour(dtmDate) & ":" & Microsoft.VisualBasic.Minute(dtmDate) & ":00"
End If
SQLDate = "CONVERT(DATETIME, '" & strDate & "', 102)"
End Function
Function RunSQL(ByVal strSQL As String, ByVal strDatabase As String, Optional ByVal strTeam As String = "", Optional ByVal blAlwaysDS As Boolean = False, Optional ByVal blTimeLogComments As Boolean = False, Optional ByVal blSomerfield As Boolean = False, Optional ByVal blUsePayrollLive As Boolean = False, Optional ByVal intTimeOutOverride As Integer = 15) As ADODB.Recordset
'Fom FileLoader, BBS Contacts, BulkMailer, Time Log, SchemeDocs, Bookings, Accounts, #BBS, Billings, Projects, TimeCost
Dim strConn As String
Dim sqlConnection As System.Data.SqlClient.SqlConnection
Dim sqlCommand As System.Data.SqlClient.SqlCommand
Dim dataAdapter As System.Data.SqlClient.SqlDataAdapter
Dim dataSet As System.Data.DataSet
Select Case strDatabase
Case "Employees", "Users"
strConn = "data source=192.168.0.222;initial catalog=BBSEmployees;persist security info=False;user id=user;workstation id=DELL_LT;packet size=4096;password=pwd"
Case "P3"
strConn = "data source=192.168.0.222;initial catalog=p3;persist security info=False;user id=user;workstation id=DELL_LT;packet size=4096;password=pwd"
If Not (blUsePayrollLive) Then
strSQL = Replace(strSQL, "PayrollSQL", "PayrollSQLTest", , , CompareMethod.Text)
End If
End Select
sqlConnection = New System.Data.SqlClient.SqlConnection(strConn)
strSQL = "Set Arithabort ON; " + strSQL
sqlCommand = New System.Data.SqlClient.SqlCommand(strSQL, sqlConnection)
sqlCommand.CommandTimeout = intTimeOutOverride
dataAdapter = New System.Data.SqlClient.SqlDataAdapter(sqlCommand)
dataSet = New System.Data.DataSet()
Try
dataAdapter.Fill(dataSet)
Catch ex As System.Data.SqlClient.SqlException
MessageBox.Show(ex.Message)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
Return ConvertToRecordset(dataSet.Tables(0))
Exit Function
End Function
Public Shared Function ConvertToRecordset(ByVal inTable As DataTable) As ADODB.Recordset
Dim result As ADODB.Recordset = New ADODB.Recordset()
result.CursorLocation = ADODB.CursorLocationEnum.adUseClient
Dim resultFields As ADODB.Fields = result.Fields
Dim inColumns As System.Data.DataColumnCollection = inTable.Columns
For Each inColumn As DataColumn In inColumns
resultFields.Append(inColumn.ColumnName, TranslateType(inColumn.DataType), inColumn.MaxLength, If(inColumn.AllowDBNull, ADODB.FieldAttributeEnum.adFldIsNullable, ADODB.FieldAttributeEnum.adFldUnspecified), Nothing)
Next
result.Open(System.Reflection.Missing.Value, System.Reflection.Missing.Value, ADODB.CursorTypeEnum.adOpenStatic, ADODB.LockTypeEnum.adLockOptimistic, 0)
For Each dr As DataRow In inTable.Rows
result.AddNew(System.Reflection.Missing.Value, System.Reflection.Missing.Value)
For columnIndex As Integer = 0 To inColumns.Count - 1
resultFields(columnIndex).Value = dr(columnIndex)
Next
Next
Return result
End Function
Private Shared Function TranslateType(ByVal columnType As Type) As ADODB.DataTypeEnum
Select Case columnType.UnderlyingSystemType.ToString()
Case "System.Boolean"
Return ADODB.DataTypeEnum.adBoolean
Case "System.Byte"
Return ADODB.DataTypeEnum.adUnsignedTinyInt
Case "System.Char"
Return ADODB.DataTypeEnum.adChar
Case "System.DateTime"
Return ADODB.DataTypeEnum.adDate
Case "System.Decimal"
Return ADODB.DataTypeEnum.adCurrency
Case "System.Double"
Return ADODB.DataTypeEnum.adDouble
Case "System.Int16"
Return ADODB.DataTypeEnum.adSmallInt
Case "System.Int32"
Return ADODB.DataTypeEnum.adInteger
Case "System.Int64"
Return ADODB.DataTypeEnum.adBigInt
Case "System.SByte"
Return ADODB.DataTypeEnum.adTinyInt
Case "System.Single"
Return ADODB.DataTypeEnum.adSingle
Case "System.UInt16"
Return ADODB.DataTypeEnum.adUnsignedSmallInt
Case "System.UInt32"
Return ADODB.DataTypeEnum.adUnsignedInt
Case "System.UInt64"
Return ADODB.DataTypeEnum.adUnsignedBigInt
Case Else
Return ADODB.DataTypeEnum.adVarChar
End Select
End Function
End Class
End Namespace

Why does Microsoft Barcode Control break when the workbook is opened via interop?

I have a worksheet, to which I have added a QR code.
The QR code is an ActiveX control: Microsoft Barcode Control 14.0
The QR code is linked to a cell (A1), so that when the value in the cell changes, so does the QR code.
When I open the workbook normally, everything works as it should.
However, when I open it using Interop from a vb.net Winforms project, the QR code no longer responds when the value in the linked cell changes.
Whats more, when I right click on the barcode control, the "Microsoft Barcode Control 14.0 Object" context menu option (seen below) is missing.
The interop code that I am using to open the workbook is as follows:
Dim XLApp As New Excel.Application
XLApp.Visible = True
Dim XLBook As Excel.Workbook = XLApp.Workbooks.Open(FilePath)
Can anyone tell me what is causing this to happen? And perhaps suggest what I can do to prevent it happening.
You may call the Calculate method of the Worksheet class each time you need to update the QR code. For example, a raw sketch in VBA:
Application.EnableEvents = True
Application.ScreenUpdating = True
Sheets("QR_CodeSheet").Calculate
I could not get the Microsoft Barcode Control to function correctly with interop.
One way would be to open the file using a shell command and then hook into the process to work with it. But I found this too messy.
Instead, I decided to use google's Chart API. This does require an internet connection. But that is not a problem for me.
Here is a link for more info: https://sites.google.com/site/e90e50fx/home/generate-qrcode-with-excel
And the VBA code:
Option Explicit
'other technical specifications about google chart API:
'https://developers.google.com/chart/infographics/docs/qr_codes
Function URL_QRCode_SERIES( _
ByVal PictureName As String, _
ByVal QR_Value As String, _
Optional ByVal PictureSize As Long = 150, _
Optional ByVal DisplayText As String = "", _
Optional ByVal Updateable As Boolean = True) As Variant
Dim oPic As Shape, oRng As Excel.Range
Dim vLeft As Variant, vTop As Variant
Dim sURL As String
Const sRootURL As String = "https://chart.googleapis.com/chart?"
Const sSizeParameter As String = "chs="
Const sTypeChart As String = "cht=qr"
Const sDataParameter As String = "chl="
Const sJoinCHR As String = "&"
If Updateable = False Then
URL_QRCode_SERIES = "outdated"
Exit Function
End If
Set oRng = Application.Caller.Offset(, 1)
On Error Resume Next
Set oPic = oRng.Parent.Shapes(PictureName)
If Err Then
Err.Clear
vLeft = oRng.Left + 4
vTop = oRng.Top
Else
vLeft = oPic.Left
vTop = oPic.Top
PictureSize = Int(oPic.Width)
oPic.Delete
End If
On Error GoTo 0
If Len(QR_Value) = 0 Then
URL_QRCode_SERIES = CVErr(xlErrValue)
Exit Function
End If
sURL = sRootURL & _
sSizeParameter & PictureSize & "x" & PictureSize & sJoinCHR & _
sTypeChart & sJoinCHR & _
sDataParameter & UTF8_URL_Encode(VBA.Replace(QR_Value, " ", "+"))
Set oPic = oRng.Parent.Shapes.AddPicture(sURL, True, True, vLeft, vTop, PictureSize, PictureSize)
oPic.Name = PictureName
URL_QRCode_SERIES = DisplayText
End Function
Function UTF8_URL_Encode(ByVal sStr As String)
'http://www.nonhostile.com/howto-convert-byte-array-utf8-string-vb6.asp
Dim i As Long
Dim a As Long
Dim res As String
Dim code As String
res = ""
For i = 1 To Len(sStr)
a = AscW(Mid(sStr, i, 1))
If a < 128 Then
code = Mid(sStr, i, 1)
ElseIf ((a > 127) And (a < 2048)) Then
code = URLEncodeByte(((a \ 64) Or 192))
code = code & URLEncodeByte(((a And 63) Or 128))
Else
code = URLEncodeByte(((a \ 144) Or 234))
code = code & URLEncodeByte((((a \ 64) And 63) Or 128))
code = code & URLEncodeByte(((a And 63) Or 128))
End If
res = res & code
Next i
UTF8_URL_Encode = res
End Function
Private Function URLEncodeByte(val As Integer) As String
Dim res As String
res = "%" & Right("0" & Hex(val), 2)
URLEncodeByte = res
End Function

Access to Excel: Decrease Runtime of Excel VBA

Similar versions of this question probably have been asked before, but I had questions regarding this issue.
Basically for my function, I just want to run simple a spell check on selected tables from Microsoft Access. Since Access doesn't support individual highlighting all too well in reports, I have the data exported to an Excel file and have VBA run tests for any errors there. After searching online for tips, I have the current code to run faster than what I originally had. But ideally no matter the size of the table I want the function to run under 10 minutes. But currently for some of them, for tables that have 500k+ cells the runtime can still go past 30 minutes. So I was wondering if anything further can be done to better enhance the runtime of this.
Private Function Excel_Parser(outFile As String, errorCount As Integer, ByVal tName As String)
' EXCEL SETUP VARIABLES
Dim OpenApp As Excel.Application
Set OpenApp = CreateObject("Excel.Application")
Dim parserBook As Excel.Workbook
Dim parserSheet As Excel.Worksheet
' Opening exported file
Set parserBook = OpenApp.Workbooks.Open(outFile, , , , , , , , , , , , , , XlCorruptLoad.xlRepairFile)
If parserBook Is Nothing Then
status2 = "Failed to set Workbook"
Exit Function
Else
status3 = "Searching [" & tName & "] for errors"
Set parserSheet = parserBook.Worksheets(1)
' --------------------------------------------------------------------------------
' Fetch Table information
lastCellAddress = parserSheet.Range("A1").SpecialCells(xlCellTypeLastCell).Address
Dim rng As Range
Set rng = parserSheet.Range("A1:" & lastCellAddress)
' --------------------------------------------------------------------------------
' Populating entire table data from Excel into array to save runtime.
Dim dataArr() As Variant, R As Long, C As Long
dataArr = rng.Value2
' Parsing through table data array
nRows = UBound(dataArr, 1)
nCols = UBound(dataArr, 2)
fileOuterLoop1 = Time
For R = 1 To nRows
For C = 1 To nCols
cCell = CStr(dataArr(R, C))
status4 = "Now running check on cell: [" & cCell & "]"
If cCell <> "" Or Not (IsNull(cCell)) Then
If Not OpenApp.Application.CheckSpelling(cCell) Then
errorCount = errorCount + 1
' Change cell status
vArr = Split(parserSheet.Cells(1, C).Address(True, False), "$")
fCol = vArr(0)
xDef = fCol & R
parserSheet.Range(xDef).Interior.Color = RGB(255, 213, 124)
End If
End If 'End of cCell is null check
Next C
Next R
fileOuterLoop2 = Time
fCheck = Format(fileOuterLoop2 - fileOuterLoop1, "hh:mm:ss")
' --------------------------------------------------------------------------------
parserSheet.Columns.AutoFit
status7 = "Loop Finished. Runtime: " & fCheck
' Save and Cleanup
OpenApp.DisplayAlerts = False
parserBook.SaveAs FileName:=outFile, FileFormat:=xlWorkbookDefault, ConflictResolution:=xlLocalSessionChanges
parserBook.Saved = True
parserBook.Close SaveChanges:=False
OpenApp.DisplayAlerts = True
Set parserSheet = Nothing
Set parserBook = Nothing
Set OpenApp = Nothing
' Return errorCount for database
Excel_Parser = errorCount
End If
End Function
outFile is a PATH string, where file exists from a TransferSpreadsheet command. And "status" variables are just error log textboxes in the Access form. I have tried adding in both Access' and Excel's versions of ScreenUpdating or Echo but I found that these commands actually make my function runtime slightly slower.
Two things:
Do you use status4 somewhere in your code to show current state of work and just omitted it here in the sample? If so, think about not displaying it for every loop, but maybe only every 50 steps by using Mod operator.
See https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/mod-operator
You should avoid screen refreshs and more on every loop in Excel by setting this before the loop:
OpenApp.ScreenUpdating = False
OpenApp.EnableEvents = False
OpenApp.Calculation = Excel_XlCalculation.xlCalculationManual
And this after the loop:
OpenApp.ScreenUpdating = True
OpenApp.EnableEvents = True
OpenApp.Calculation = Excel_XlCalculation.xlCalculationAutomatic
It can end in a massive speed up. Give it a try.

VB.NET (Excel Two Columns > Conditional Comboboxes)

I'm searching for nearly two hours to find a solution on the following. In Excel I have two columns (One Column for Master Records and one Column for Slave Records). Basically, in Combobox1 I want to populate all the Master Records. If a selection is made for MasterRecord A, I want Combobox2 to only show me the SlaveRecords belonging to A and not the other records belonging to other Master Records.
I have the Interop Assembly added and Excel opened (there is a connection already). Your help is much appreciated!
Private Sub Combobox2_Populate()
'Start Excel Script to populate ComboBox2
Dim excel As Application = New Application
Dim w As Workbook = excel.Workbooks.Open(Filename:=databasestatus, [ReadOnly]:=True)
Dim sheet As Worksheet = w.Sheets("AIR_NL_1")
Dim StartRow As Integer
Dim TotalRows As Integer
ComboBox2.Items.Clear()
sheet.UsedRange.AutoFilter(Field:=9, Criteria1:=ComboBox1.SelectedItem, Operator:=XlAutoFilterOperator.xlFilterValues)
TotalRows = sheet.Range("A1").CurrentRegion.Rows.Count
For StartRow = 3 To TotalRows
If XlCellType.xlCellTypeVisible = True Then
ComboBox2.Items.Add(sheet.Range("H:H").Cells(StartRow, 1).Text)
End If
Next
w.Close(SaveChanges:=False)
End Sub
This might help you, or at least give you a basic idea:
Private Function ExcelToDataTable(ByVal fileExcel As String, _
Optional ByVal columnToExtract As String = "*", _
) As System.Data.DataTable
Dim dt As New System.Data.DataTable
Try
Dim MyConnection As System.Data.OleDb.OleDbConnection
Dim MyCommand As OleDbDataAdapter
Dim fileExcelType As String
'Chose the right provider
If IO.Path.GetExtension(fileExcel.ToUpper) = ".XLS" Then
fileExcelType = "Excel 8.0"
MyConnection = _
New System.Data.OleDb.OleDbConnection _
("provider=Microsoft.Jet.OLEDB.4.0;Data Source='" & fileExcel & "';Extended Properties=" & fileExcelType & ";")
Else
fileExcelType = "Excel 12.0"
MyConnection = _
New System.Data.OleDb.OleDbConnection _
("provider=Microsoft.ACE.OLEDB.12.0;Data Source='" & fileExcel & "';Extended Properties=" & fileExcelType & ";")
End If
'Open excel connection
MyConnection.Open()
'Populate DataTable
Dim myTableName = MyConnection.GetSchema("Tables").Rows(0)("TABLE_NAME")
MyCommand = New OleDbDataAdapter(String.Format("SELECT " & columnToExtract & " FROM [{0}] ", myTableName), MyConnection)
MyCommand.TableMappings.Add("Table", columnToExtract)
MyCommand.Fill(dt)
MyConnection.Close()
Catch ex As Exception
Err.Clear()
End Try
Return dt
End Function
As you can see, we have an optional parameter called myWhereStatement.
What does it mean?
You can specify its value when you call the function, otherwise its value will be an empty string
After that we can call ExcelToDataTable inside our Sub in order to populate the ComboBox as shown below:
Private Sub Combobox_Populate()
Dim filePath As String = "your_file_path"
ComboBox1.DataSource = ExcelToDataTable(filePath, "MasterRecord")
End Sub
Now you have your ComboBox1 filled with data, but the ComboBox2 is still empty.
We are going to handle the event of ComboBox1_SelectedValueChanged that means that every time you select an Item from the ComboBox1 it will programmatically fill the ComboBox2 with the propper items as shown below.
Private Sub ComboBox1_SelectedValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedValueChanged
Dim slaveRecords As System.Data.DataTable = ExcelToDataTable(filePath)
Dim dt As New DataTable
dt.Columns.Add("SlaveRecords")
For i As Integer = 0 To slaveRecords.Rows.Count
If ComboBox1.SelectedItem Is slaveRecords.Rows(i).Item(0) Then
dt.Rows.Add(slaveRecords.Rows(i).Item(1))
End If
Next
ComboBox2.DataSource = dt
End Sub
Remarks
As you can see the first call of ExcelToDataTable has only 2 parameters while the second one has 3 parameters. That's the optional parameter feature!
N.B.
As you can see I'm using alot of _ because of better code formatting. It means that a single statement will continue across multiple lines
If something isn't 100% clear ot you have any dubt feel free to ask in the comments below.

Accessing SurveyMonkey API from VBA

I am tying to set-up a Excel VBA project to readout individual survey responses into a form in Excel for some calculations and then PDF reporting.
However I have great difficulty to deploy the .NET library (SurveyMonkeyApi) to be available for reference in VBA.
I have set up a VisualStudio project to test that way , and I can install it for that specific VS project (through NuGet PM). But the library is not made available for Excel on that machine.
I have downloaded (on another machine) the libraries through standalone NuGet and they download OK but then I am at loss on how to register for Excel VBA access. On top of it there is a dependency on NewtonsoftJson library too (which downloaded automatically on both occasions).
Good advice appreciated!
I just saw this now - is there a feature for StackOverflow to alert me when a comment is added or a question answered, so I know to look back?
Here is starting code:
Option Explicit
Public Const gACCESS_TOKEN As String = "xxxxxxxxxxxxxxxxxxxxxx"
Declare Function GetTickCount Lib "kernel32" () As Long
Declare Sub Sleep Lib "kernel32" (ByVal lngMilliSeconds As Long)
' for a JSON parser see https://code.google.com/p/vba-json/
Public Sub test()
Dim vRequestBody As Variant, sResponse As String, sSurveyID As String
sSurveyID = "1234567890"
vRequestBody = "{""survey_id"":" & """" & sSurveyID & """" _
& ", ""fields"":[""collector_id"", ""url"", ""open"", ""type"", ""name"", ""date_created"", ""date_modified""]" _
& "}"
sResponse = SMAPIRequest("get_collector_list", vRequestBody)
End Sub
Function SMAPIRequest(sRequest As String, vRequestBody As Variant) As String
Const SM_API_URI As String = "https://api.surveymonkey.net/v2/surveys/"
Const SM_API_KEY As String = "yyyyyyyyyyyyyyyyyyyyyyyy"
Dim bDone As Boolean, sMsg As String, sUrl As String, oHttp As Object ' object MSXML2.XMLHTTP
Static lsTickCount As Long
If Len(gACCESS_TOKEN) = 0 Then
Err.Raise 9999, "No Access token"
End If
On Error GoTo OnError
sUrl = SM_API_URI & URLEncode(sRequest) & "?api_key=" & SM_API_KEY
'Debug.Print Now() & " " & sUrl
Application.StatusBar = Now() & " " & sRequest & " " & Left$(vRequestBody, 127)
Set oHttp = CreateObject("MSXML2.XMLHTTP") ' or "MSXML2.ServerXMLHTTP"
Do While Not bDone ' 4.33 offer retry
If GetTickCount() - lsTickCount < 1000 Then ' if less than 1 sec since last call, throttle to avoid sResponse = "<h1>Developer Over Qps</h1>"
Sleep 1000 ' wait 1 second so we don't exceed limit of 2 qps (queries per second)
End If
lsTickCount = GetTickCount()
'Status Retrieves the HTTP status code of the request.
'statusText Retrieves the friendly HTTP status of the request.
'Note The timeout property has a default value of 0.
'If the time-out period expires, the responseText property will be null.
'You should set a time-out value that is slightly longer than the expected response time of the request.
'The timeout property may be set only in the time interval between a call to the open method and the first call to the send method.
RetryPost: ' need to do all these to retry, can't just retry .Send apparently
oHttp.Open "POST", sUrl, False ' False=not async
oHttp.setRequestHeader "Authorization", "bearer " & gACCESS_TOKEN
oHttp.setRequestHeader "Content-Type", "application/json"
oHttp.send CVar(vRequestBody) ' request body needs brackets EVEN around Variant type
'-2146697211 The system cannot locate the resource specified. => no Internet connection
'-2147024809 The parameter is incorrect.
'String would return {"status": 3, "errmsg": "No oJson object could be decoded: line 1 column 0 (char 0)"} ??
'A Workaround would be to use parentheses oHttp.send (str)
'"GET" err -2147024891 Access is denied.
'"POST" Unspecified error = needs URLEncode body? it works with it but
SMAPIRequest = oHttp.ResponseText
'Debug.Print Now() & " " & Len(SMAPIRequest) & " bytes returned"
sMsg = Len(SMAPIRequest) & " bytes returned in " & (GetTickCount() - lsTickCount) / 1000 & " seconds: " & sRequest & " " & Left$(vRequestBody, 127)
If Len(SMAPIRequest) = 0 Then
bDone = MsgBox("No data returned - do you wish to retry?" _
& vbLf & sMsg, vbYesNo, "Retry?") = vbNo
Else
bDone = True ' got reply.
End If
Loop ' Until bdone
Set oHttp = Nothing
GoTo ExitProc
OnError: ' Pass True to ask the user what to do, False to raise to caller
Select Case MsgBox(Err.Description, vbYesNoCancel, "SMAPIRequest")
Case vbYes
Resume RetryPost
Case vbRetry
Resume RetryPost
Case vbNo, vbIgnore
Resume Next
Case vbAbort
End
Case Else
Resume ExitProc ' vbCancel
End Select
ExitProc:
End Function
Public Function URLEncode(StringVal As String, Optional SpaceAsPlus As Boolean = False) As String
Dim StringLen As Long
StringLen = Len(StringVal)
If StringLen > 0 Then
ReDim result(StringLen) As String
Dim i As Long, CharCode As Integer
Dim Char As String, Space As String
If SpaceAsPlus Then Space = "+" Else Space = "%20"
For i = 1 To StringLen
Char = Mid$(StringVal, i, 1)
CharCode = Asc(Char)
Select Case CharCode
Case 97 To 122, 65 To 90, 48 To 57, 45, 46, 95, 126
result(i) = Char
Case 32
result(i) = Space
Case 0 To 15
result(i) = "%0" & Hex(CharCode)
Case Else
result(i) = "%" & Hex(CharCode)
End Select
Next i
URLEncode = Join(result, "")
End If
End Function
EDIT 23-APRIL add more code.
the Me. comes from code in a Userform.
Set jLib = New JSONLib
vRequestBody = "{"
If Me.txtDaysCreated > "" Then
vRequestBody = vRequestBody & JKeyValue("start_date", Format$(Now() - CDbl(Me.txtDaysCreated), "yyyy-mm-dd")) & ","
End If
If Me.txtTitleContains > "" Then
' title contains "text", case insensitive
vRequestBody = vRequestBody & JKeyValue("title", Me.txtTitleContains) & ","
End If
vRequestBody = vRequestBody _
& JKeyValue("fields", Array("title", "date_created", "date_modified", "num_responses", _
"language_id", "question_count", "preview_url", "analysis_url")) & "}"
'returns in this order: 0=date_modified 1=title 2=num_responses 3=date_created 4=survey_id
' and in date_created descending
sResponse = GetSMAPIResponse("get_survey_list", vRequestBody)
------------------------------------------
Function JKeyValue(sKey As String, vValues As Variant) As String
Dim jLib As New JSONLib
JKeyValue = jLib.toString(sKey) & ":" & jLib.toString(vValues)
Set jLib = Nothing
End Function
Edit 25-April overview of VBA code to get the data
This is covered in the SM documentation, but I'll sketch how that looks in VBA.
the response to get_survey_details gives you all the survey setup data. Use
Set oJson = jLib.parse(Replace(sResponse, "\r\n", " "))
to get a json object.
Set dictSurvey = oJson("data")
gives you the dictionary so you can get data like dictSurvey("num_responses"). I take it you know how to index into dictionary objects to get field values.
Set collPages = dictSurvey("pages")
gives you a collection of Pages. The undocumented field "position" gives you the order of pages in the survey UI.
For lPage = 1 To collPages.Count
Set dictPage = collPages(lPage)
Set collPageQuestions = dictPage("questions") ' gets you the Qs on this page
For lPageQuestion = 1 To collPageQuestions.Count
Set dictQuestion = collPageQuestions(lPageQuestion) ' gets you one Q
Set collAnswers = dictQuestion("answers") ' gets the QuestionOptions for this Q
For lAnswer = 1 To collAnswers.Count
Set dictAnswer = collAnswers(lAnswer) ' gets you one Question Option
etc etc
Then given the number of responses from above, loop through the respondents 100 at a time - again see the SM doc for details of how to specify start and end dates to do incremental downloads over time.
create a json object from the response to "get_respondent_list"
Collect the fields for each respondent and accumulate a list of at most 100 respondent IDs.
Then "get_responses" for that list.
Set collResponsesData = oJson("data")
For lResponse = 1 To collResponsesData.Count
If not IsNull(collResponsesData(lResponse)) then
... get fields...
Set collQuestionsAnswered = collResponsesData(lResponse)("questions")
For lQuestion = 1 To collQuestionsAnswered.Count
Set dictQuestion = collQuestionsAnswered(lQuestion)
nQuestion_ID = CDbl(dictQuestion("question_id"))
Set collAnswers = dictQuestion("answers") ' this is a collection of dictionaries
For lAnswer = 1 To collAnswers.Count
On Error Resume Next ' only some of these may be present
nRow = 0: nRow = CDbl(collAnswers(lAnswer)("row"))
nCol = 0: nCol = CDbl(collAnswers(lAnswer)("col"))
nCol_choice = 0: nCol_choice = CDbl(collAnswers(lAnswer)("col_choice"))
sText = "": sText = collAnswers(lAnswer)("text")
nValue = 0: nValue = Val(sText)
On Error GoTo 0
and save all those values in a recordset or sheet or whatever
Hope that helps.
I access the SM API in straight VBA.
Just CreateObject("MSXML2.XMLHTTP") then issue calls and use the SimpleJsON JSONLib to parse it.
If I wanted to access VB.Net code, I'd package it with ExcelDNA to create a XLL and that gives a straight Excel addin.
I would think you would need to add it into the References for your Excel project.
From the Ribbon, select, Tools, then References, then scroll through the list looking for something about SurveyMonkey API.
So encouraged by #sysmod I have tried to do something in VBA directly. I have left out the JSON for now as I am already in trouble. The below is giving me "Developer Inactive" as a result, though I have another project in VB.NET where the same key and token works fine.
Public Sub GetSMList()
Dim apiKey As String
Dim Token As String
Dim sm As Object
apiKey = "myKey"
Token = "myToken"
Set sm = CreateObject("MSXML2.XMLHTTP.6.0")
With sm
.Open "POST", "https://api.surveymonkey.net/v2/surveys/get_survey_list", False
.setRequestHeader "Authorization", "Bearer " & Token
.setRequestHeader "Content-Type", "application/json"
.send "api_key=" & apiKey
result = .responseText
End With
End Sub

Resources