Is it possible to pass the fields of an array to a function with variant parameter array? - excel

I am in the following situation: I have a function, that takes a ParamArray of type variant and generates a string from the keywords given in its ParamArray in a special manner by execution mergeToString.
Function function1(ParamArray var() As Variant) As String
For i = LBound(var) To UBound(var)
function1 = mergeToString(function1, CStr(var(i))
Next i
End Function
In another subroutine, I have an array of strings obtained from the Split Function in VBA and want to use it as an input for function1
Sub displayFCTN1()
Dim arr() As String
arr() = Split("foo|bar", "|")
'and here I ran out of ideas...
Debug.Print function1(**???**)
End Sub
The two lines
function1(**???**)
function1("foo","bar")
should be equivalent the first somehow using arr().
In Matlab this is relatively easy - I know, VBA is not Matlab, still this might help as an extended description of my problem:
you could most likely do it by using the colon operator in Matlab
function1(arr(:))
since then the fields of the array arr() count as "free" parameters.
Is there something comparable to this in VBA? I tried ReDim already, that somehow didn't do the job (as far as I tried).
Thank you for your help!

You need to test, whether the first item of array is array:
Sub FFF()
MsgBox Func1("foo", "bar")
MsgBox Func1(Split("foo|bar", "|"))
End Sub
Function Func1$(ParamArray var() As Variant)
Dim s$, x%, args
args = IIf(IsArray(var(0)), var(0), var)
'//Do something
For x = 0 To UBound(args)
s = s & args(x) & "|"
Next
Func1 = Left$(s, Len(s) - 1)
End Function

A workaround as mentioned in the comments above
Sub displayFCTN1()
Dim arr() As String
arr() = Split("foo|bar", "|")
Myhelper arr
End Sub
Sub Myhelper(arr)
Select Case UBound(arr)
Case 0: Debug.Print function1(arr(0))
Case 1: Debug.Print function1(arr(0), arr(1))
Case 2: Debug.Print function1(arr(0), arr(1), arr(2))
Case 3: Debug.Print function1(arr(0), arr(1), arr(2), arr(3))
Case 4: Debug.Print function1(arr(0), arr(1), arr(2), arr(3), arr(4))
'etc up to 29.
Case Else
End Select
End Sub

This does require a change to function1 code, but should still work with orginal.
Sub Test()
Debug.Print function1("foo", "bar")
Dim arr() As String
arr = Split("foo|bar", "|")
Debug.Print function1(arr)
End Sub
Function function1(ParamArray var() As Variant) As String
Dim i As Long
If UBound(var) = 0 Then
For i = LBound(var(0)) To UBound(var(0))
'function1 = Join(var(0), "|")
function1 = mergeToString(function1, CStr(var(0)(i)))
Next i
Else
'Original code.
For i = LBound(var) To UBound(var)
'function1 = Join(var, "|")
function1 = mergeToString(function1, CStr(var(i)))
Next i
End If
End Function

Related

How to remove the last character of a word in a text string and insert to another cell using VBA in Excel?

Everything is working except for that little comma in the 5th word. How to remove that? My code is as follows.
The text looks like this: The data as of 20.12.2019, and so on.
I only want 20.12.2019 without that comma. Any clue? Thanks.
Public Function FindWord(Source As String, Position As Integer)
Dim arr() As String
arr = VBA.Split(Source, " ")
xCount = UBound(arr)
If xCount < 1 Or (Position - 1) > xCount Or Position < 0 Then
FindWord = ""
Else
FindWord = arr(Position - 1)
End If
End Function
subroutine calls the function.
Sub InsertDate()
Sheets("Sheet1").Range("B3").Value = FindWord(Sheets("Sheet2").Range("A2"), 5)
End Sub
So just for fun, a short introduction to regular expressions (which, by no means, I am an expert in):
Sub Test()
Dim str As String: str = "The data as of 20.12.2019, and so on."
Dim regex As Object: Set regex = CreateObject("VBScript.RegExp")
regex.Pattern = "\b(\d{2}.\d{2}.\d{4})"
regex.Global = True
Debug.Print regex.Execute(str)(0)
End Sub
This would be good practice if your string won't follow that same pattern all the time. However when it does, there are some other good alternatives mentioned in comments and answers.
One option is to Replace:
Sub InsertDate()
With Sheets("Sheet1").Range("B3")
.Value = FindWord(Sheets("Sheet2").Range("A2"), 5)
.Value = Replace(.Value, ",", "")
End With
End Sub
This is still text-that-looks-like-a-date, so you can call DateValue to convert it.
.Value = Replace(.Value, ",", "")
.Value = DateValue(.Value) '<~ add this line

How to return and use multiple values in functions in VBA

I am would like to write a user defined function (UDF), A that returns multiple values (let's say 3). I would then like to use these values in another UDF, B.
I have been following
https://www.geeksengine.com/article/vba-function-multiple-values2.html
on what VBA data structures would support this. I was originally using a collection to store the multiple values from UDF A, but in my use case, index 0, 1 and 2 have some value, so would like to use an array instead of a collection so that I can index with base 0 (I am not sure if this is possible with collections - it seems the first element is .Item(1)).
The problem is, I cannot seem to retrieve the values of the array returned by UDF A inside UDF B.
A minimum (not) working example:
Function A() As Variant
Dim arr(3) As Variant
arr(0) = "zero"
arr(1) = "one"
arr(2) = "two"
A = arr
MsgBox "Function A"
MsgBox arr(0)
End Function
Function B() As Variant
Dim arry(3) As Variant
Set arry = A()
MsgBox "Function B"
MsgBox arry(0)
End Function
Sub debugfunc()
MsgBox B()
End Sub
I have tried both Set arry = A() and arry = A(). Both produce 'Ca't assign to array' errors when running the debugfunc sub.
Any ideas?
You don't use Set and you need a dynamic array of the same type to receive the function result:
Dim arry() As Variant
arry = A()
You need to declare arry as Variant and get rid of Set.
Here is your code corrected:
Function A() As Variant
Dim arr(3) As Variant
arr(0) = "zero"
arr(1) = "one"
arr(2) = "two"
A = arr
MsgBox "Function A"
MsgBox arr(0)
End Function
Function B() As Variant
Dim arry As Variant: arry = A()
MsgBox "Function B"
MsgBox arry(0)
End Function
Sub debugfunc()
MsgBox B()
End Sub
You may also remove the parens after A, like Dim arry As Variant: arry = A().

function to return all checked items from list box into cell

I would like to get all checked items in a active x list box into a cell on the worksheet. I have this code that exactly does what I want as a sub:
Private Sub CommandButton1_Click()
Dim lItem As Long
Dim outstr As String
For lItem = 0 To ListBox1.ListCount - 1
If ListBox1.Selected(lItem) = True Then
outstr = outstr & ListBox1.List(lItem) & "/"
End If
Next
Range("A1") = Left(outstr, Len(outstr) - 1)
End Sub
This takes all checked items into cell a1.
I convert this into a function like this:
Function CopySelected(lb As MSForms.ListBox) As String
Dim lItem As Long
Dim outstr As String
For lItem = 0 To lb.ListCount - 1
If lb.Selected(lItem) = True Then
outstr = outstr & lb.List(lItem) & "/"
End If
Next
CopySelected = Left(outstr, Len(outstr) - 1)
End Function
But I cannot give the right argument to the function to return the same as the sub. What do I need to put as an argument please?
I tried the following:
=Copyselected(Sheet1.ListBox1)
=Copyselected(Sheet1.ListBoxes("ListBox1"))
=Copyselected(Sheet1.Shapes("ListBox1").OLEFormat.Object)
=Copyselected(Worksheets("Sheet1").ListBox1)
Nothing seem to work. Is the function incorrect or the passing?
Use the following code:
Public Function GetAllSelected(strListBox As String)
Dim lItem As Long
Dim outstr As String
With Worksheets(1).OLEObjects(strListBox).Object
For lItem = 0 To .ListCount - 1
If .Selected(lItem) = True Then
outstr = outstr & .List(lItem) & "/"
End If
Next lItem
End With
GetAllSelected = Left(outstr, Len(outstr) - 1)
End Function
Then call it passing the name of the ListBox as a string:
=GetAllSelected("ListBox1")
Note, that the sheet must be know at this time. If you need to pass the sheet name (as well) to the function then you will have to adjust the code accordingly.
Update:
For the proactive approach as outlined in the comments below using the ListBox1_Change() event the result would be something like this:
Yet, in this case you would have one procedure for each ListBox and the result of the sub would be always written into the same cell (hard-coded).
Using code from #Ralph, you should also be able to use your function as is...
Try...
=Copyselected(Sheet1.OLEObjects("ListBox1").Object)

check if array is empty (vba excel)

These if ... then statements are getting the wrong results in my opinion. The first is returning the value 'false' when it should be 'true'. The fourth returns the right value. The second and third return an error.
Sub empty_array()
Dim arr1() As Variant
If IsEmpty(arr1) Then
MsgBox "hey"
End If
If IsError(UBound(arr1)) Then
MsgBox "hey"
End If
If IsError(Application.match("*", (arr1), 0)) Then
MsgBox "hey"
End If
ReDim arr1(1)
arr1(1) = "hey"
If IsEmpty(arr1) Then
MsgBox "hey"
End If
End Sub
Arr1 becomes an array of 'Variant' by the first statement of your code:
Dim arr1() As Variant
Array of size zero is not empty, as like an empty box exists in real world.
If you define a variable of 'Variant', that will be empty when it is created.
Following code will display "Empty".
Dim a as Variant
If IsEmpty(a) then
MsgBox("Empty")
Else
MsgBox("Not Empty")
End If
Adding into this: it depends on what your array is defined as. Consider:
dim a() as integer
dim b() as string
dim c() as variant
'these doesn't work
if isempty(a) then msgbox "integer arrays can be empty"
if isempty(b) then msgbox "string arrays can be empty"
'this is because isempty can only be tested on classes which have an .empty property
'this do work
if isempty(c) then msgbox "variants can be empty"
So, what can we do? In VBA, we can see if we can trigger an error and somehow handle it, for example
dim a() as integer
dim bEmpty as boolean
bempty=false
on error resume next
bempty=not isnumeric(ubound(a))
on error goto 0
But this is really clumsy... A nicer solution is to declare a boolean variable (a public or module level is best). When the array is first initialised, then set this variable.
Because it's a variable declared at the same time, if it loses it's value, then you know that you need to reinitialise your array.
However, if it is initialised, then all you're doing is checking the value of a boolean, which is low cost. It depends on whether being low cost matters, and if you're going to be needing to check it often.
option explicit
'declared at module level
dim a() as integer
dim aInitialised as boolean
sub DoSomethingWithA()
if not aInitialised then InitialiseA
'you can now proceed confident that a() is intialised
end sub
sub InitialiseA()
'insert code to do whatever is required to initialise A
'e.g.
redim a(10)
a(1)=123
'...
aInitialised=true
end sub
The last thing you can do is create a function; which in this case will need to be dependent on the clumsy on error method.
function isInitialised(byref a() as variant) as boolean
isInitialised=false
on error resume next
isinitialised=isnumeric(ubound(a))
end function
#jeminar has the best solution above.
I cleaned it up a bit though.
I recommend adding this to a FunctionsArray module
isInitialised=false is not needed because Booleans are false when created
On Error GoTo 0 wrap and indent code inside error blocks similar to with blocks for visibility. these methods should be avoided as much as possible but ... VBA ...
Function isInitialised(ByRef a() As Variant) As Boolean
On Error Resume Next
isInitialised = IsNumeric(UBound(a))
On Error GoTo 0
End Function
I would do this as
if isnumeric(ubound(a)) = False then msgbox "a is empty!"
I may be a bit late, but following simple stuff works with string arrays:
Dim files() As String
files = "..." 'assign array
If Not Not files Then
For i = LBound(files) To UBound(files)
'do stuff, array is not empty
Next
End If
That's all the code for this.
Above methods didn´t work for me. This did:
Dim arrayIsNothing As Boolean
On Error Resume Next
arrayIsNothing = IsNumeric(UBound(YOUR_ARRAY)) And False
If Err.Number <> 0 Then arrayIsNothing = True
Err.Clear
On Error GoTo 0
'Now you can test:
if arrayIsNothing then ...
this worked for me:
Private Function arrIsEmpty(arr as variant)
On Error Resume Next
arrIsEmpty = False
arrIsEmpty = IsNumeric(UBound(arr))
End Function
The problem with VBA is that there are both dynamic and static arrays...
Dynamic Array Example
Dim myDynamicArray() as Variant
Static Array Example
Dim myStaticArray(10) as Variant
Dim myOtherStaticArray(0 To 10) as Variant
Using error handling to check if the array is empty works for a Dynamic Array, but a static array is by definition not empty, there are entries in the array, even if all those entries are empty.
So for clarity's sake, I named my function "IsZeroLengthArray".
Public Function IsZeroLengthArray(ByRef subject() As Variant) As Boolean
'Tell VBA to proceed if there is an error to the next line.
On Error Resume Next
Dim UpperBound As Integer
Dim ErrorNumber As Long
Dim ErrorDescription As String
Dim ErrorSource As String
'If the array is empty this will throw an error because a zero-length
'array has no UpperBound (or LowerBound).
'This only works for dynamic arrays. If this was a static array there
'would be both an upper and lower bound.
UpperBound = UBound(subject)
'Store the Error Number and then clear the Error object
'because we want VBA to treat unintended errors normally
ErrorNumber = Err.Number
ErrorDescription = Err.Description
ErrorSource = Err.Source
Err.Clear
On Error GoTo 0
'Check the Error Object to see if we have a "subscript out of range" error.
'If we do (the number is 9) then we can assume that the array is zero-length.
If ErrorNumber = 9 Then
IsZeroLengthArray = True
'If the Error number is something else then 9 we want to raise
'that error again...
ElseIf ErrorNumber <> 0 Then
Err.Raise ErrorNumber, ErrorSource, ErrorDescription
'If the Error number is 0 then we have no error and can assume that the
'array is not of zero-length
ElseIf ErrorNumber = 0 Then
IsZeroLengthArray = False
End If
End Function
I hope that this helps others as it helped me.
I'm using this
If UBound(a) >= 0 Then
' not empty
Else
' empty... UBound(a) = -1
End If
IsEmpty(a) did not work for me... I hate VBA
Dim arr() As Variant
Debug.Print IsEmpty(arr) ' False
Debug.Print UBound(arr) ' raises error
arr = Array()
Debug.Print IsEmpty(arr) ' False
Debug.Print UBound(arr) ' -1
ReDim Preserve arr(UBound(arr) + 1)
arr(UBound(arr)) = "test"
Debug.Print IsEmpty(arr) ' False
Debug.Print UBound(arr) ' 0
The below function works for both static and dynamic arrays.
Function array_Empty(testArr As Variant) As Boolean
Dim i As Long, k As Long, flag As Long
On Error Resume Next
i = UBound(testArr)
If Err.Number = 0 Then
flag = 0
For k = LBound(testArr) To UBound(testArr)
If IsEmpty(testArr(k)) = False Then
flag = 1
array_Empty = False
Exit For
End If
Next k
If flag = 0 Then array_Empty = True
Else
array_Empty = True
End If
End Function
Tente isso:
Function inic(mtz As Variant) As Boolean
On Error Resume Next
Dim x As Boolean
x = UBound(mtz): inic = x
End Function
...
if inic(mymtz) then
debug.print "iniciada"
else
debug.print "não iniciada"
end if

VB6: Splitling with multi-multicharactered delimiters?

I have a problem with the split function I have currently. I am able to either split with 1 delimited only (split()) or split with many single characters (custom()). Is there a way to split this? Keep in mind that these delimiters are not in order.
"MY!!DATA##IS!!LOCATED##HERE!!IN!!BETWEEN##THE##ATS!!AND!!MARKS"
I need your help to get the following result
"MY" , "DATA" , "IS" , "LOCATED" , "HERE" , "IN" , "BETWEEN","THE", "ATS" , "AND", "MARKS"
thanks
Create a new VB6 EXE project and add a button to the form you will be given, and use the following code for the Button1_Click event:
Private Sub Command1_Click()
Dim myText As String
Dim myArray() As String
Dim InBetweenAWord As Boolean
Dim tmpString As String
Dim CurrentCount As Integer
CurrentCount = 0
myText = "MY!!DATA##IS!!LOCATED##HERE!!IN!!BETWEEN##THE##ATS!!AND!!MARKS"
For i = 1 To Len(myText)
If (Mid(myText, i, 1) = "#" Or Mid(myText, i, 1) = "!") And InBetweenAWord = True Then
CurrentCount = CurrentCount + 1
ReDim Preserve myArray(CurrentCount)
myArray(CurrentCount) = tmpString
tmpString = ""
InBetweenAWord = False
Else
If (Mid(myText, i, 1) <> "#" And Mid(myText, i, 1) <> "!") Then
tmpString = tmpString & Mid(myText, i, 1)
InBetweenAWord = True
End If
End If
Next
For i = 1 To CurrentCount
MsgBox myArray(i) 'This will iterate through all of your words
Next
End Sub
Notice that once the first For-Next loop is finished, the [myArray] will contain all of your words without the un-desired characters, so you can use them anywhere you like. I just displayed them as MsgBox to the user to make sure my code worked.
Character handling is really awkward in VB6. I would prefer using built-in functions like this
Private Function MultiSplit(ByVal sText As String, vDelims As Variant) As Variant
Const LNG_PRIVATE As Long = &HE1B6 '-- U+E000 to U+F8FF - Private Use Area (PUA)
Dim vElem As Variant
For Each vElem In vDelims
sText = Replace(sText, vElem, ChrW$(LNG_PRIVATE))
Next
MultiSplit = Split(sText, ChrW$(LNG_PRIVATE))
End Function
Use MultiSplit like this
Private Sub Command1_Click()
Dim vElem As Variant
For Each vElem In MultiSplit("MY!!DATA##IS!!LOCATED##HERE!!IN!!BETWEEN##THE##ATS!!AND!!MARKS", Array("!!", "##"))
Debug.Print vElem
Next
End Sub

Resources