I don't get what's false in my code. I searched the error the whole morning! So I hope you can help me.
First, here's the problem code (the names of the variables aren't their real names):
Sheets(sheet).Range(nameOfTheRange).FormulaR1C1 = _
functionReturningString(functionReturningStrArr( _
Range(nameOfAnotherRange).Value, AnInputWorkSheet, "colNameInInputSheet"))
So my description on that:
All functions work fine standing alone, but in combination there is always this error (Language: German):
Fehler beim Kompilieren:
Unverträglicher Typ: Datenfeld oder benutzerdefinierter Typ erwartet
functionReturningString is a function with the following parameters(strArr() as Variant) --> it returns a String like a bulletlist.
functionReturningStrArr(nameWhichISearchInSheet as String, dataSheet as Worksheet, dataColumn, as String) --> it returns a Variant() for the bulletListing
I'm not sure if the second method really works so here's the code of it.
Function functionReturningStrArr(ByVal nameWhichISearchInSheet As String, ByVal datasheet As Worksheet, ByVal datacolumn As String) As String()
Dim returnArray() As String
Dim rowindex As Integer
Dim ID As String
Sheets(rawdataOverall).Cells(1, getColNumFromColName("Project")).EntireColumn.Select
'search correct dataset
For Each cell In Selection
If cell.Value = nameWhichISearchInSheet Then
rowindex = cell.row
Exit For
End If
Next cell
'get ID
ID = Sheets(rawdataOverall).Cells(rowindex, getColNumFromColName("ID")).Value
'search data from file with this ID
datasheet.Cells(1, getColNumFromColName(datacolumn)).EntireColumn.Select
Selection.UsedRange.Select
For Each cell In Selection
rowindex = cell.row
'check if row contains to this project
If Cells(rowindex, getColNumFromColName("ID")) = ID Then
ReDim Preserve returnArray(UBound(returnArray) + 1)
returnArray(UBound(returnArray)) = cell.Value
End If
Next cell
functionReturningStrArr = returnArray()
If you are asking yourselves what is getColNumFromColName, it is a method which works really fine, I used it in other projects too.
You really have to start declaring everything explicitly using Dim -- and force yourself to do this by writing Option Explicit at the top of your module. That way you will identify errors much more quickly.
Here
'get ID
ID = Sheets(rawdataOverall).Cells(rowindex, getcolnumformcolname("ID")).Value
you call a function called getcolnumformcolname; presumably form is a typo and you meant From as in getColNumFromColName. Had you had Option Explicit, you would have detected that error immediately.
The following three variables/arrays are not declared: rawdataOverall, cell, getDataFromThisProject. You should declare them and assign them a type explicitly.
Try fixing those things and see where that brings you.
Is seems a small portion of the function code snippet is wrong. on the very last line, you should assign the value of returnArray() to your function name like so:
functionReturningStrArr = returnArray()
Otherwise, you would need to extract the array from a variable named "getDataFromActualProject", as is shown in the example.
EDIT:
Alter your function "functionReturningStrArr As Variant" to return "As String()" instead of Variant. It seems you cant cast a Variant to a string array as you would expect.
EDIT:
I created a function to test this. This compile error shows up when you try to cast Variant as Array of string. This also includes a fix, your function that returns Variant MUST return array of string instead.
Sub RunTest()
Debug.Print getStringFromArray(getArray())
Debug.Print getStringFromArray(getVariant()) ' compile error! you cannot cast variant to array of string
End Sub
Function getArray() As String()
Dim returnArray(2) As String
returnArray(0) = "A"
returnArray(1) = "B"
returnArray(2) = "C"
getArray = returnArray()
End Function
Function getVariant() As Variant()
Dim returnArray(2) As String
returnArray(0) = "A"
returnArray(1) = "B"
returnArray(2) = "C"
getArray = returnArray() ' Not a compile error, you can cast string array to variant
End Function
Function getStringFromArray(inputArray() As String) As String
Dim returnString As String
For i = LBound(inputArray()) To UBound(inputArray())
If returnString = "" Then
returnString = inputArray(i)
Else
returnString = returnString & "," & inputArray(i)
End If
Next i
getStringFromArray = returnString
End Function
Related
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.
Situation:
I have a function, RemoveEmptyArrRowCol, which accepts two arguments, one of which is an array (tempArr).
When the second argument was a Long everything was fine. When I changed the second argument to a String (and the associated call variable) I got:
Type mismatch (Error 13)
So in the below code examples:
Test1 runs fine
Test2 fails
Questions:
1) Why is the behaviour different between the two?
2) How do I fix the second version so it behaves as per the first?
3) How will I future proof this for when I pass a dictionary value (array) as the 1st parameter, rather than reading directly from the worksheet?
What I have tried:
This appears to be a common question on SO and I have looked at a number of these questions; some of which I have put as references at the bottom of this question. I still, however, have not resolved why the first of these two sub works, but the second does not?
I played around with different combinations of:
Adding extra brackets
Explicitly declaring the type of tempArr: Dim tempArr() As Variant
Changing part of the function signature: ByRef tempArr() As Variant
After looking at #Fionnuala's answer to this question, MS Access/VBA type mismatch when passing arrays to function/subroutine, I decided to try using Call:
Call RemoveEmptyArrRowCol2(ws.Range("C4:I129").Value, tempStr)
This compiled but means I would need to change other parts of my code to ensure tempArr is correctly populated. If I were to do it this way, I might as well convert the function to a procedure.
As is, the flow is that I populate tempArr, in the test example, direct from the sheet and then hand off to another sub i.e.
tempArr = RemoveEmptyArrRowCol(ws.Range("C4:I129").Value, tempStr)
ArrayToSheet wb.Worksheets("Test").Range("A1"), tempArr
Note re: Question 3:
In the final version, I will be passing an array, pulled from a dictionary, as the first parameter i.e.
tempArr = RemoveEmptyArrRowCol( ArrayDict(tempStr), tempStr)
Working version:
Option Explicit
Public Sub Test1()
Dim tempArr() 'variant
Init
Dim tempStr As String: tempStr = "Response Times"
tempArr = RemoveEmptyArrRowCol(ws.Range("C4:I129").Value, categoryDict(tempStr & "Cols"))
End Sub
Private Function RemoveEmptyArrRowCol(ByRef tempArr As Variant, ByVal nCols As Long) As Variant
End Function
Failing version:
Public Sub Test2()
Dim tempArr()
Init
Dim tempStr As String: tempStr = "Response Times"
tempArr = RemoveEmptyArrRowCol2(ws.Range("C4:I129").Value, tempStr)
End Sub
Private Function RemoveEmptyArrRowCol2(ByRef tempArr As Variant, ByVal tempStr As String) As Variant
End Function
Example of the current full function:
Private Function RemoveEmptyArrRowCol(ByRef tempArr As Variant, ByVal tempStr As String) As Variant
Dim i As Long
Dim j As Long
Dim counter As Long
counter = 0
Dim tempArr2()
Dim totCol As Long
Dim adjColTotal As Long
totCol = categoryDict(tempStr & "Cols")
adjColTotal = categoryDict(tempStr & "ColsAdj")
Select Case tempStr
Case "ResponseTimes", "NoCCPR"
ReDim tempArr2(1 To 1000, 1 To adjColTotal)
For i = 1 To UBound(tempArr, 1)
If tempArr(i, 2) <> vbNullString Then 'process row
counter = counter + 1 'load row to temp array (counter becomes row count)
For j = 1 To totCol
Select Case j
Case Is < 4
tempArr2(counter, j) = tempArr(i, j)
Case Is > 4
tempArr2(counter, j - 1) = tempArr(i, j)
End Select
Next j
End If
Next i
RemoveEmptyArrRowCol = RedimArrDimOne(tempArr2, adjColTotal, counter)
Case "Incidents"
End Select
End Function
Additional references:
1) Passing arrays to functions in vba
2) Passing array to function returns compile error
3) Type mismatch error when passing arrays to a function in excel vba
4) Should I use Call keyword in VBA
It really depends on your input and output, e.g. what is in the RemoveEmptyArrRowCol2 function. This is an option, in which tempStr as String is not failing:
Public Sub Test2()
Dim tempArr()
Dim tempStr As String: tempStr = "Response Times"
tempArr = RemoveEmptyArrRowCol2(Range("C4:I129").Value, tempStr)
End Sub
Private Function RemoveEmptyArrRowCol2(ByRef tempArr As Variant, _
ByVal tempStr As String) As Variant
RemoveEmptyArrRowCol2 = Array(1, 2)
End Function
E.g., if you remove the returning value (Array(1,2), it fails) but it should fail, because it does not return anything.
Define "Dim tempArr As Variant" not as Variant-Array with "()"
Please show us your function "categoryDict" and "ArrayDict"
"Call" is not nessesary!
You access Values as follows:
Dim r As Long
Dim c As Long
For r = 1 To UBound(tempArr, 1)
For c = 1 To UBound(tempArr, 2)
Debug.Print tempArr(r, c)
Next
Next
I'd like to store all tab names in a string array, that I'll later use for other things. My current problem is, that I'd like to populate my array in a separate function and when thats done, hand it back to my main sub.
It throws
Can't assign to array
as an error, what am I'm not seeing here?
Sub Captions_Formatting()
Dim tabName() As String
Dim totaltabs As Long
Dim ws As Worksheet
Dim captionlines As Integer
totaltabs = get_Tabs
tabName = getTabNames(ws, totaltabs)
End Sub
Function getTabNames(ws As Worksheet, totaltabs As Long) As String()
Dim i As Integer
ReDim tabName(totaltabs)
For Each ws In Sheets
If ws.Name <> "Overview" Then
tabName(i) = ws.Name
i = i + 1
End If
Next ws
getTabNames = tabName
End Function
Thanks a lot!
You need to type your array to match the function return type:
ReDim tabName(totaltabs) As String
If you do not then its an array of variants and such a thing cannot be cast to an array of strings automatically (or at all).
Let's say I have the following Array, arr that contains a maximum of 20 substrings. In this example, the array will have 4 substrings:
arr = {"AAAA: 1234567" , "BBBB: 2345678" , "CCCC: 98765432" , "DDDD: 87654321"}
Note: Capitalization is not important.
I am needing assistance with finding a substring inside an array (again, up to 20 strings possible), that Starts With CCCC:, and then assigning that entire string to it's own variable, let's call it Var1.
I understand I could assign Var1 = arr(2), but I need to figure out how to determine the index number in the array that meets my criteria.
What I would really appreciate (although any method I will be happy with), is making a stand alone function such as:
Function ArrayIndex(ArrayInput as Variant, StartsWith as String) as Byte
'Arguments Here
End Function
Then I could just use this in my Subroutine:
Var1 = arr(ArrayIndex(arr, "CCCC:"))
Update 1
Here is a snippet of my current code
Sub T_Report
'<Shortcut = Shift+Ctrl+T>
Dim Data as String, DataArray as Variant
With ActiveSession
.Copy 0, 2, 80, 21 'Just a big block of text with multiple lines, copied to clipboard
Data = Clipboard 'Set Data to the clipboard value
DataArray = Split(Data,vbCrLf) 'This is "Data" in an Array, separated by line breaks
'Just checking to see if the Array was successful (it is)
Debug.Print DataArray(0) & vbNL & DataArray(1) & vbNL & DataArray(2) & _
vbNL & DataArray(3) & vbNL & DataArray(4) & vbNL & vbNL
'Misc code here
Dim sF1 as String, sF2 as String, sF3 as String
Dim Rslt1 as String, Rslt2 as String, Rslt3 as String
sF1 = "Field1:"
sF2 = "Field2:"
sF3 = "Field3:"
MsgBox DataArray(0) ' This works fine, giving me first substring
Rslt1 = FindMyString(sF1, DataArray)
' Misc Code
End With
End Sub
However, when I use the following function, I get Type Mismatch error on the MsgBox arr(0) line, although it should be giving me the very first substring of DataArray in the above sub (should be an exact match, the array has not been modified).. However, when I do MsgBox DataArray(0) above, I do get the first substring.
Private Function FindMyString(strToFind As String, ParamArray arr() As Variant) As String
Dim i As Integer
Dim iLen As Integer
Dim strArr As String
FindMyString = "" ' Returns Blank String if not found
' I get type mismatch here (Doesn't appear Array from sub loaded into this function)
MsgBox arr(0)
iLen = Len(strToFind)
For i = 0 To UBound(arr)
strArr = CStr(arr(i))
If strToFind = Left$(strArr, iLen) Then
FindMyString = strArr
Exit Function
End If
Next i
End Function
EDIT - FIX YOUR ARRAY LOAD
Change this (it MUST give you an error!):
arr = {"AAAA: 1234567" , "BBBB: 2345678" , "CCCC: 98765432" , "DDDD: 87654321"}
To This:
Dim arr As Variant
arr = Split("AAAA: 1234567,BBBB: 2345678,CCCC: 98765432,DDDD: 87654321", ",")
If you want to use a function to do your bidding you can pass an array of data using the ParamArray type. It has to be the last parameter in your function
This should do what you want:
Private Function FindMyString(strToFind as string, ParamArray arr() As Variant) As String
Dim i As Integer
Dim iLen as Integer
Dim strArr as String
FindMyString = "" ' Returns Blank String if not found '
Debug.Print arr(0)(0) ' Print first element of array
iLen = Len(strToFind)
For i = 0 To UBound(arr(0))
strArr = CStr(arr(0)(i))
If strToFind = Left$(strArr, iLen) Then
FindMyString = strArr
Exit Function
End If
Next i
End Function
In your example you can test it by:
Var1 = FindMyString("CCCC:", arr)
I have function that returns me list of current sheets:
Function getListOfSheetsW() As Variant
Dim i As Integer
Dim sheetNames() As Variant
ReDim sheetNames(1 To Sheets.Count)
For i = 1 To Sheets.Count
sheetNames(i) = Sheets(i).name
Next i
getListOfSheetsW = sheetNames
End Function
Then I have function which returns TRUE or FALSE depending on if needle is in haystack or not.
Function IsInArray2(ByVal needle As String, haystack() As String) As Boolean
Dim element As Variant
For Each element In haystack
If element = needle Then
IsInArray = True
Exit Function
End If
Next element
IsInArray = False
End Function
My goal is to create new subroutine which will first check if sheet with given name already exist and if not then create new one. I've tried following:
Sub CreateNewSheet(ByVal dstWSheetName As String)
Dim srcWSheetName As String
' Dim sheetNames() As String
Dim sheetNames() As Variant
sheetNames = getListOfSheetsW()
Dim sheetCount As Integer
If IsInArray2(dstWSheetName, sheetNames) Then
MsgBox "Sheet with following name: " & dstWSheetName & " already exists"
Else
srcWSheetName = ActiveSheet.name
sheetCount = Sheets.Count
' CREATE NEW SHEET
' Worksheets(dstWsheetName).Delete
Sheets.Add.name = dstWSheetName
' Q: why 6 instead of 5
' Worksheets("Test").Move after:=Worksheets("Sheet5")
Worksheets(dstWSheetName).Move After:=Worksheets(sheetCount + 1)
' SWITCH TO SRC SHEET
Worksheets(srcWSheetName).Activate
End If
End Sub
I'm calling it this way:
Sub CallCreateNewSheet()
Call CreateNewSheet("test")
End Sub
I guess the problem is with Dim sheetNames() As String or Dim sheetNames() As Variant.
When I use Dim sheetNames() As String I get
Run-time error '13': Type mismatch
When I use Dim sheetNames() As Variant I get:
Compile error: Type mismatch: array or user-defined type expected
I had similar problem before but defining sheetNames as array did not helped here. What is the problem and what does the two different errors mean?
You will avoid all these problems if you switch from typed arrays to variant-arrays.
In your first function, delete this line:
Dim sheetNames() As Variant
Change the definition line of your 2nd function from this:
Function IsInArray2(ByVal needle As String, haystack() As String) As Boolean
...to this:
Function IsInArray2(ByVal needle As String, haystack) As Boolean
In your sub, change this line:
Dim sheetNames() As Variant
...to this:
Dim sheetNames
How about a new script like:
Sub NewSheetByName(SName as String)
Dim oldSheet as Object
For Each oldSheed in ThisWorkbook.Sheets
if oldSheet.Name = Sname Then
MsgBox "Sheet with following name: " & SName & " already exists"
Exit Sub
End If
Next
oldSheet = ActiveSheet
Sheets.Add.Name = SName
ActiveSheet.Move , Worksheets(Sheets.Count)
oldSheet.Activate
End Sub
The variables has to be in sinc.
Declare the variable sheetNames in the same manner in both procedures:
Sub CreateNewSheet(ByVal dstWSheetName As String) and
Function getListOfSheetsW() As Variant
declare it as : Dim sheetNames() As String
Also note that the Function IsInArray2 always returns False.
To correct this replace IsInArray with IsInArray2 in the body of the function.
It's a good practice to always have the
Option Explicit
at the beginning of the modules.
However it'll save all the trouble to validate the existence of a worksheet, just to assign the target worksheet to a variable, it will give an error and the variable return nothing if the worksheet in not present. Try this:
Dim Wsh As Worksheet
On Error Resume Next
Set Wsh = Workbook(x).Worksheets("Test")
On Error GoTo 0
If Wsh Is Nothing Then Add Worksheet