Invalid Qualifier for String.Add in Outlook VBA - arrays

You all have been so helpful, and I was wondering whether I might trouble you a bit more. I have nearly completed my conversion from VB.net to VBA for Outlook, and in order to complete that, I need some information on what exactly a particular piece of code is returning. If you all can help out with that, the problem may go away; if not, I might need some help on this invalid qualifier error.
From what I understand, I declare an 'array' in VBA with a command like this:
Dim Computers(1, 1) As String
Which produces a 2x2 array. I then try to fill it like this:
Computers.Add ComputerName, ErrorState
where ComputerName and ErrorState are variables which I have already filled. This results in the error. I will provide you with the entire relevant section below. I need to know how many relevant items are in the outlook inbox, which means checking if they were sent on today's date and by the correct sender. I need this in order to get the correct dimensions on the array Computers. (The array is filled in right now as I know the correct dimensions, but in practise I will not.) I took a piece of code which I found through google, the Scripting Dictionary output, but I do not fully understand it. I need just the number of emails, without any irrelevant text, and I was hoping that the debug line would be able to tell me which command I would need to return the number and nothing else. Unfortunately, because of the above error, I cannot get to this line. Even by stepping through the code, the very first thing it tells me is that there is a problem here. The entire relevant section is below. I know that the formatting is problematic, but I have only been working with VB.NET for 4 days, and this is my second day of working with VBA, so I do not quite have everything down yet. There's some irrelevant stuff in here, I'm pretty sure, but I do not have the experience to make the call of whether something is relevant or not, so I left it all. Sorry for the length!
Private Sub Main()
Dim objOutlook As Outlook.Application
Dim Inbox As Outlook.MAPIFolder
Dim InboxItems As Outlook.Items
Dim Mailobject As Object
Dim strDate As String
Dim ComputerName As Object
Dim ErrorState As Object
Dim DateToday As String: DateToday = Format(Date, "yyyy/MM/dd")
Dim SenderEmail As String
Dim Computers(1, 1) As String
Dim compLength As Integer
Dim disabledList As Collection
Dim enabledList As Collection
Dim unknownList As Collection
Dim notpresentList As Collection
Dim problemList As Collection
Dim cArrayLength As Integer
Dim I As Integer
Dim J As Integer
Dim objOutlookMsg As Outlook.MailItem
Dim objOutlookRecip As Outlook.Recipient
Dim EmailCount As Integer
Dim compArray() As String
'\\ load csv declarations
Dim file_name As String
Dim fnum As Integer
Dim whole_file As String
Dim lines As Variant
Dim one_line As Variant
Dim num_rows As Long
Dim num_cols As Long
Dim R As Long
Dim C As Long
Set disabledList = New Collection
Set enabledList = New Collection
Set unknownList = New Collection
Set notpresentList = New Collection
Set problemList = New Collection
'\\\\\
'\\Retrieve info from outlook, add to sorted list Computers
'\\\\\
objOutlook = CreateObject("Outlook.Application")
Inbox = objOutlook.GetNamespace("Mapi").GetDefaultFolder(6)
InboxItems = Inbox.Items
InboxItems.SetColumns ("SentOn")
Dim myItems As Outlook.Items
Dim dict As Object
Dim msg As String
Set dict = CreateObject("Scripting.Dictionary")
myItems.SetColumns ("SentOn")
EmailCount = InboxItems.Count
For Each Mailobject In InboxItems
strDate = GetDate(Mailobject.SentOn)
If Not dict.Exists(strDate) Then
dict(strDate) = 0
End If
dict(strDate) = CLng(dict(strDate)) + 1
Next Mailobject
'\\ need redo to assign number of objects to CompLength
msg = ""
For Each o In dict.Keys
msg = msg & o & ": " & dict(o) & " items" & vbCrLf
Next
Debug.Print msg
For Each Mailobject In InboxItems
ComputerName = Mailobject.Subject
ErrorState = Mailobject.Body
strDate = GetDate(Mailobject.SentOn)
SenderEmail = Mailobject.SenderEmailAddress
If strDate = DateToday And SenderEmail = "itadmin#email.org" Then
'\\ Syntax is (key, value)
Computers.Add ComputerName, ErrorState
End If
'MsgBox(Mailobject.Subject)
'MsgBox(Mailobject.SenderName)
'MsgBox(Mailobject.To)
'MsgBox(Mailobject.Body)
Next Mailobject
This website has been incredibly helpful as not only a place for me to ask questions but also as a place for me to find relevant information without having to trouble you all. Unfortunately, it is all too often that I do have to trouble you all, but every time you have been very kind and helpful. And I would like to thank you for that.

you're trying to use the Add() method from a Collection to assign an element in an array. To assign an element in a 2-D array you'd use arr(a,b)=someValue where a and b are numeric values. – Tim Williams
Question with no answers, but issue solved in the comments

Related

merge several bits of data from different sheets

I am trying to merge several bits of data from different sheets. After a few questions and attempts (at arrays, thanks to Stackoverflow before for help with this), i think a dictionary may be best. The final outcome is a populated table that holds all data for each individual entry. (depending on the entry the data in a column in raw data could be in different locations)
The data can include multiple entries for one person. But the data for each entry is different depending on the stage of entry. For example, if the data in column 3 would be in column 5 of the final table if a condition was stage 1, however if condition was stage 2, the same data that was in column 3 could be column 10 of the final table.
https://www.youtube.com/watch?v=o8fSY_4p93s
Following this video tutorial ondictionaires, i think i could o through the dtaa and find each individual entry and then add the corresponding variables for the case. E.g. find data for Steve Smith, if steve smith exists then if stage 1, add data to variable stagedate1, if stage2 add data to stage2date and so on. If not found add entry and find the stage.
Similar to the video, where he finds the corresponding data for each customer, and adds sales and volumes, i could do the same if an if function is round before to identify which datastage and to then put the value in correct variable.
I know there will be a million way to do it, but this seems simple and effective.
Sub Dictionary()
Dim dict As Dictionary
Set dict = ReadData()
Call WriteDict(dict)
End Sub
Function ReadData()
Dim dict As New Dictionary
Dim DataWs As Worksheet: Set DataWs = ThisWorkbook.Sheets("DATA")
Dim PoolOfWeekWs As Worksheet: Set PoolOfWeekWs = ThisWorkbook.Sheets("Pool of the week")
Dim LastrowData As Long: LastrowData = DataWs.range("A" & Rows.Count).End(xlUp).Row
Dim LastColData As Long: LastColData = DataWs.Cells(1 & DataWs.Columns.Count).End(xlToLeft).Column
Dim LastColDataString As String: LastColDataString = Split(Cells(1, LastColData).Address, "$")(1)
Dim DataRange As range: Set DataRange = DataWs.range("A1:" & LastColDataString & LastrowData)
Dim DataArr As Variant: DataArr = DataWs.range("A1:AO" & LastrowData)
Dim range As range: Set range = DataWs.range("A1").CurrentRegion
Dim i As Long
Dim CandidateProcessID As String, CandidateName As String, FirstName As String, ProcessStatus As String, PQLDate As Date, oCandidate As ClsCandidate
For i = 2 To range.Rows.Count
CandidateProcessID = range.Cells(i, 10).Value
CandidateName = range.Cells(i, 16).Value
FirstName = range.Cells(i, 17).Value
ProcessStatus = range.Cells(i, 9).Value
If dict.Exists(CandidateProcessID) = True Then
Set oCandidate = dict(CandidateProcessID) 'CODE ERRORS HERE AFTER A FEW ROWS (Comes across a
Else an entry that is already in the dictionary)
Set oCandidate = New ClsCandidate
dict.Add CandidateProcessID, oCustomer
End If
oCandidate.CandidateName = oCandidate.CandidateName
oCandidate.FirstName = oCandidate.FirstName
oCandidate.ProcessStatus = oCandidate.ProcessStatus
oCandidate.PQLDate = oCandidate.PQLDate
Next i
Set ReadData = dict
End Function
Sub WriteDict(dict As Dictionary)
Dim key As Variant, oCandidate As ClsCandidate
For Each key In dict
Set oCandidate = dict(key)
Debug.Print key, oCandidate.CandidateName, oCandidate.FirstName, oCandidate.ProcessStatus, oCandidate.PQLDate
Next key
End Sub
I believe the error is object error. It stops and debugs.
That would be "Object Required", and it's absolutely consistent with a very common trap: add Option Explicit at the top of every module, and now VBA won't let you run the code until it knows what oCustomer is.
Option Explicit being missing has allowed the code to run with oCustomer undeclared: simply, a new Variant/Empty pointer is created on-the-spot for that local identifier ("oCustomer"), such that every iteration that "works" is just preparing the ground to make the ones that don't, blow up:
If dict.Exists(CandidateProcessID) = True Then
Set oCandidate = dict(CandidateProcessID) '<~ Variant/Empty is retrieved here: "object required"
Else
Set oCandidate = New ClsCandidate
dict.Add CandidateProcessID, oCustomer '<~ Variant/Empty is added here
End If
The Variant/Empty is successfully retrieved from the dictionary: the problem is that the Set oCandidate instruction on the left side of that = operator says the right-hand side of the operation must be an object reference. Variant/Empty fails to satisfy this requirement, and an "object required" run-time error is raised.
The bug isn't with the retrieval: it's with the storage!
You can easily find bugs like this with static code analysis tooling, such as Rubberduck (disclosure: that's my website).

VBA failing with array or user defined type expected

I have some code I am moving from VB.NET to VBA which has worked in the .NET world quite well. I have successfully moved almost all of the code into the VBA world with one exception thus far. Here is much of the code in question and all the variable declarations`
Dim vault As IEdmVault14
Dim eFile As IEdmFile9
Dim eFolder As IEdmFolder7
Dim pos As IEdmPos5
Dim Pathlist As EdmStrLst5
Dim parentFolder As IEdmFolder5
Dim vaultName As String
Dim filePath As String
Dim AssyName As String
Dim LoggedIn As Boolean
Set EdmVault5 = New EdmVault5
Set vault = New EdmVault5Dim fso As New FileSystemObject
Dim sw As TextStream
Set sw = fso.CreateTextFile("c:\temp\" & AssyName & ".txt")
'-----------------------------GET COLUMN HEADERS
Dim columns() As EdmBomColumn
BOM.GetColumns columns
Dim header As String
header = "LEVEL" & vbTab
Dim column As EdmBomColumn
For i = 0 To UBound(columns)
header = header & columns(i).mbsCaption & vbTab
Next
sw.writeline (header)
'-----------------------------Bom.READ EACH BOM ROW
Dim rows As Object
Dim row As IEdmBomCell
BOM.GetRows (rows)
For i = 0 To UBound(rows)
If IsNothing(row) Then Exit For
Dim rowString As String
Set rowString = row.GetTreeLevel.ToString & vbTab
Dim varVal As String
varVal = ""
For i = 0 To UBound(columns)
row.GetVar(column.mlVariableID, column.meType, varVal, Nothing, Nothing, Nothing)
If IsNothing(varVal) Then varVal = ""
rowString = rowString & varVal & vbTab
Next
'-----------------------------WRITE THE ROW TO THE FILE
sw.writeline (rowString)
Next
sw.Close
`
The array error occurs at BOM.GetRows (rows). I am stuck on what the issue could be. This error code does not occur in VB.NET but .NET does warn that Variable 'rows' is passed by reference before it has been assigned a value. A null reference exception could result at runtime. I am not clear on how that translates into VBA if at all.
If anyone could shed some light on this it would be helpful I'm sure.
If you have a method signature (or function signature or whatever) that requires an array, then you have to Dim the variable you pass in as an array.
Public Sub test()
Dim x As Variant
Debug.Assert Not IsArray(x)
x = Array(1, 2)
Debug.Assert IsArray(x)
GetStuff x 'this fails
Stop
End Sub
Public Function GetStuff(a() As Variant) As Double
GetStuff = 1
End Function
Even though a Variant can hold an array, it doesn't pass the IsArray test just by declaring it. If I assign an array to it, it passes IsArray, but I still can't use it as an argument to function that requires an array. x is a Variant array and a() is an array of Variants. So the above code still won't compile.
It sounds from your comments that you got it sorted, but I thought I'd throw a little more information out there for posterity.

VBA Copy/paste static ranges to static ranges in dynamically built array of worksheets

I am trying to copy the cell values of 4 static ranges from one worksheet and paste those values into the cells within 4 static ranges for each worksheet that is included in my dynamically built list.
Here is my code:
Sub retpsh()
Dim Home As Worksheet: Set Home = Worksheets("Home")
Dim s3 As Worksheet: Set s3 = Worksheets("Sheet3")
Dim s9 As Worksheet: Set s9 = Worksheets("Sheet9")
Dim s5 As Worksheet: Set s5 = Worksheets("Sheet5")
Dim s7 As Worksheet: Set s7 = Worksheets("Sheet7")
Dim Back As Worksheet: Set Back = Worksheets("Home Backstage")
Dim wsarray As Variant
Dim message As String: message = Back.Cells(18, 13)
Dim ws As Variant
If Back.Cells(18, 19) = 0 Then
If MsgBox("Nothing selected!", vbOKOnly) = vbOK Then Exit Sub
Else
If MsgBox(message + " Do you wish to continue?", vbYesNo) = vbNo Then Exit Sub
wsarray = Array(Back.Cells(18, 19)) 'Doesn't work properly
End If
For Each ws In wsarray
ws.Range("C2:C5", "C8:C11", "C13", "B18:C22") = Worksheets("Home Backstage").Range("B1:B4", "B6:B9", "B11", "B18:C22").Value '''''450 error with or without "Set" before line
Next ws
End Sub
First, wsarray = Array(Back.Cells(18, 19)) does not work as it is not parsing that cell value, it takes the whole thing as a single value (i.e. "s3","s5","s7","s9" or whatever the cell value is). Back.Cells(18, 19) has a formula that builds the list based on the 4 options selected on the "Home" page. The formula builds the list to be any of the 16 combinations of: s3, s5, s7, or s9. The final cell value looks like: "s3" or like "s3","s7","s9". Using just Dim wsarray() or wsarray() = doesn't change the behavior. Any time I use wsarray() without Array(...) I get a '13' Type mismatch error.
Is this a matter of dynamically defining the dimensions of the array
first?
If not, is there a way to parse the cell value into the array?
If not, how would I build the array list dynamically in VBA?
Second, even bypassing the above issues by specifying the array manually, I still get a '450' Wrong number of arguments or invalid property assignment error. I know setting one range with multiple nonconsecutive cells = another range setup the same way works fine (e.g. Range("K15:C18","C29") = Range("C1:C4","C15")), so:
Why is that syntax not working within the For loop?
Third, the statement within the For loop needs the fully qualified name Worksheets("Home Backstage") and did not accept the alias Back.
Why does the For not accept the worksheet alias Back?
Does the For loop act outside of the Dim's that are setup prior to it thus needing the Dim within the loop?
I know I could get around all of this with a bunch of If statements and referencing the state of each of the 4 options on the "Home" page to determine which worksheets to copy to, but I don't like that idea. That doesn't seem to be the right way to go about this, having a bunch of duplicated code with slight changes to predicates hence my desire to use an array. Nevertheless, my questions are more "why" than "how", but I appreciate any guidance or explanations all the same!
Here's another option, very similar to Scott's, but making use of a few more arrays to handle your ranges:
Sub retpsh()
Dim Back As Worksheet: Set Back = Worksheets("Home Backstage")
Dim wsarray As Variant
Dim fromRangeArray As Variant
Dim toRangeArray As Variant
Dim message As String: message = Back.Cells(18, 13)
Dim ws As Variant
If Back.Cells(18, 19) = 0 Then
If MsgBox("Nothing selected!", vbOKOnly) = vbOK Then Exit Sub
Else
If MsgBox(message + " Do you wish to continue?", vbYesNo) = vbNo Then Exit Sub
wsarray = Split(Back.Cells(18, 19).Value, ",")
End If
'Could do this into a single multidimensional array if you are a sadist
fromRangeArray = Array("B1:B4", "B6:B9", "B11", "B18:C22")
toRangeArray = Array("C2:C5", "C8:C11", "C13", "B18:C22")
'loop through sheet names
For Each ws In wsarray
For rngIndex = 0 To UBound(fromRangeArray)
Sheets(ws).Range(toRangeArray(rngIndex)).Value = Back.Range(fromRangeArray(rngIndex)).Value
Next rngIndex
Next ws
End Sub
You will need to enter the actual sheet names in the cell:
sheet9,sheet7
equating a string or variable with a variable name does not work, so you will need to loop through the array created by Split and make sure the sheet exists, then use it.
You cannot use range with more than two cell references. Range expects a start and a finish.
Sub retpsh()
Dim Home As Worksheet: Set Home = Worksheets("Home")
Dim Back As Worksheet: Set Back = Worksheets("Home Backstage")
Dim wsarray() As String
Dim message As String: message = Back.Cells(18, 13)
Dim i As Long
If Back.Cells(18, 19) = 0 Then
If MsgBox("Nothing selected!", vbOKOnly) = vbOK Then Exit Sub
Else
If MsgBox(message + " Do you wish to continue?", vbYesNo) = vbNo Then Exit Sub
wsarray = Split(Back.Cells(18, 19).Value, ",")
End If
For i = LBound(wsarray) To UBound(wsarray)
If Not IsError(Application.Evaluate("'" & wsarray(i) & "'!A1")) Then
With Worksheets(wsarray(i))
.Range("C2:C5").Value = Back.Range("B1:B4").Value
.Range("C8:C11").Value = Back.Range("B6:B9").Value
.Range("C13").Value = Back.Range("B11").Value
.Range("B18:C22").Value = Back.Range("B18:C22").Value
End With
End If
Next i
End Sub

Creating Access table from text file

I was wondering is it possible to create an Access Database table from a text file.
For example a text file like this:
#Tom
Age:12
Info: Tall
#Alice
Age: 20
Info: Nice
Should be converted to a table with 3 parameters (Name, Age and Info) containing the info of Tom and Alice.
A different example would be the text file:
Tim
-------
A tall 12 years old.
Good In basketball
Jak
-------
A short 30 years old guy.
Bad at sports
Bald
Which should be converted into a table containing 2 parameters-age and info.
If coding is required I'd prefer using c# though Java is also an option.
Thanks in advance :D
After a searching far and wide I found a solution.
I loaded the text into a string in c#, than built an excel table using Microsofts API.
After you've got an excel spreadsheet converting it to access is rather simple using Access' built-in functionaily
It likely easer to do this in VBA – and you not need to write some messy c# (however, if you are actually familer with c#, then any compentent coder can EASY read some VBA and just type in c# line for line – assuming you are actually familer with C#.
So the VBA to import the first text file and write out the data to a table will look like this:
Sub Import1()
Dim intfileH As Integer
Dim strBuf As String
Dim rst As DAO.Recordset
Dim strRec, vBuf, strRecDetail
intfileH = FreeFile()
Open "c:\test\text1.txt" For Input As #intfileH
strBuf = Input(LOF(intfileH), #intfileH)
Set rst = CurrentDb.OpenRecordset("tblNames")
vBuf = Split(strBuf, "#")
For Each strRec In vBuf
If strRec <> "" Then
strRecDetail = Split(strRec, vbCrLf)
With rst
.AddNew
!MyName = strRecDetail(0)
!age = Split(strRecDetail(1), ":")(1)
!Info = Split(strRecDetail(2), ":")(1)
.Update
End With
End If
Next
rst.Close
Close (intfileH)
End Sub
And for the second data, much the same, this will work:
Sub Import2()
Dim intfileH As Integer
Dim strBuf As String
Dim rst As DAO.Recordset
Dim vBuf, strRec
Dim i As Integer
intfileH = FreeFile()
Open "c:\test\text2.txt" For Input As #intfileH
strBuf = Input(LOF(intfileH), #intfileH)
Set rst = CurrentDb.OpenRecordset("tblNames")
vBuf = Split(strBuf, vbCrLf)
For i = 0 To UBound(vBuf)
If vBuf(i) <> "" Then
With rst
.AddNew
!MyName = vBuf(i)
i = i + 2
!age = Split(vBuf(i), " ")(2)
i = i + 1
!Info = vBuf(i)
.Update
End With
End If
i = i + 1
Next
rst.Close
Close (intfileH)
End Sub

How to assign excel cell with string to array value in visual basic?

I'm pretty new to visual basic, but I'm having trouble assigning cell values to members of an array. Basically what I am doing is looping through a column and assigning each cell to a new part of the array. Test code is posted below:
Sub Test()
Dim List(5) As String
Dim celltext As String
For i = 1 To 5
celltxt = ActiveSheet.Range("K" & i).Text
List(i) = celltext
MsgBox List(i)
Next i
End Sub
Each time the message box pops up though, it is blank, meaning that the assignment didn't work. The cells at those coordinates DO have values. Any clues?
You are assigning to "celltxt" but reading from "celltext".
Add Option Explicit at the top of every module -- that will make these types of errors more obvious.
When you Dim List(5) As String. The lowest element in the array is 0 and not 1. You might want to change that to Dim List(1 to 5) As String else your first element will always be blank in the array.
You are using ActiveSheet. Are you sure it is the right sheet?
Try this code
Sub Test()
Dim List(1 To 5) As String
Dim ws As Worksheet
Dim i as Long
Set ws = ThisWorkbook.Sheets("Sheet1")
For i = 1 To 5
List(i) = ws.Range("K" & i).Value
MsgBox List(i)
Next i
End Sub
You might also with to try:
Dim List As Variant
Dim i As Long
List = ActiveSheet.Range("K1:K5")
For i = 1 To UBound(List)
MsgBox List(i, 1)
Next i
This will add performance by only reading from the worksheet once instead of each time the loop is looped.

Resources