#VALUE ERROR from my VBA function - vba

So I wrote a simple function in VBA and I want to use it in my excel workbook. I wrote the following code:
Option Explicit
Public Function KOLICINA(fiksnacena As Long, ceni() As Long, nedela() As Long) As Long
Dim brojac As Integer
For brojac = 1 To UBound(nedela)
If Not ((IsEmpty(nedela(brojac) Or nedela(brojac) = 0) And ceni(brojac) <> fiksnacena)) Then KOLICINA = nedela(brojac)
Next brojac
End Function
When I try to use it in a worksheet cell (using =KOLICINA(18;G22:G26;H22:H26))
, I get the #VALUE error.
I don't understand why. The function should go through nedela Array and if it finds a Non empty or different value than 0 AND if the matching cell in the ceni Array is different from the number fiksnacena, it should return the value of the cell in nedela.

You cannot simply pass a cell range reference into a UDF and have it interpreted as a single dimensioned array of longs.
Public Function KOLICINA(fiksnacena As Long, ceni As Range, nedela As Range) As Long
Dim brojac As Long, vCeni As Variant, vNedela As Variant
vCeni = ceni.Value2
vNedela = nedela.Value2
For brojac = LBound(vNedela, 1) To UBound(vNedela, 1)
If Not ((IsEmpty(vNedela(brojac, 1) Or vNedela(brojac, 1) = 0) And vCeni(brojac, 1) <> fiksnacena)) Then
KOLICINA = vNedela(brojac, 1)
Exit For
End If
Next brojac
End Function
When you dump values from a range reference into an array, you always end up with a two dimensioned array; in your example it is 1 to 5, 1 to 1.
To further illustrate this point, your original UDF code would work if you pulled the values from the ranges after transposing them and finish off the UDF with CSE so that the values are processed as an array.
=KOLICINA(18, VALUE(TRANSPOSE(G22:G26)), VALUE(TRANSPOSE(H22:H26)))
Finalize with [ctrl]+[shift]+[enter].

Related

How to count specific char in vba

So I need to count how many ž and č are there in all of these fields.
Example.
http://prntscr.com/jwz1em
I tryed with this code but it gives me 0
Function slova(iVal)
Dim output As Integer
output = Application.WorksheetFunction.CountIf(Range("A2:A11"), "ž")
End Function
I see multiple problems with your code:
There is no assignment of return value to function, in my example slova = charCnt, so it wouldn't return anything besides default 0 no matter what.
It lacks Application.Volatile, so the formula used in Excel cell would require navigating to cell and pressing ENTER to force an update when data in range changes.
Function has an argument iVal which isn't used anywhere.
Application.WorksheetFunction.CountIf returns count of cells, so it is limited to 1 character per cell. On top of it, correctly specified argument would be "*ž*"
Here is my solution to count all occurrences of hardcoded character in hardcoded range (must have exactly 1 column).
Function slova() As Long
Application.Volatile
Dim vData As Variant
Dim rowCounter As Long, charCnt As Long
Const myLetter As String = "ž"
vData = Range("A2:A11")
For rowCounter = LBound(vData) To UBound(vData)
If vData(rowCounter, 1) <> vbNullString Then
charCnt = charCnt + UBound(Split(vData(rowCounter, 1), myLetter))
End If
Next rowCounter
slova = charCnt
End Function
As you use function, you can also take advantage of it and use source range as an argument, the same goes for character.

VBA UBound Function

I'm trying to explore UBound applications for my code in Visual Basic for Applications (VBA). Let's say I have a 4 by 2 array...(A1:B4) and I want to count the number of rows. I would think my code would be...
Function test(list) As Double
test = UBound(list)
End Function
My input is =test(A1:B4)but so far I get "#value!" error. I thought the return would be 4.
What am I doing wrong? I know how to get the number of rows using the row command but I simply want to go through the coding exercise.
List is range object not an array. Range.Value will return an array of values from a range and Range.Formula will return an array of formulas from a range.
Function test(list As Range) As Double
test = UBound(list.Value)
End Function
It seems that you have 2-dimensional array, therefore you have to provide additional parameter for UBound like:
UBound(array, dimension)
Please remember, if you get array from Excel Range than it is 1-based array.
Complete solution can look like this one:
Function testArrray(List, Dimmension)
Dim ListConverted As Variant
ListConverted = List
testArrray = UBound(ListConverted, Dimmension)
End Function
Sample call: =testArrray(G15:H20,1) produces 6 as a result which is correct.
Since you do not Dim list explicitly, it is Variant. So if you call =TEST(A1:B4) it will be a Range and not an Array. A Range does not have a UBound but does have Rows.Count.
So:
Function test(list As Range) As Double
test = list.Rows.Count
End Function
will work.
Or if you really need an Array, you could do:
Function test(list As Variant) As Double
list = list
test = UBound(list)
End Function
The list = list does the following: If list is a Range-Object then it will be implicit converted to Array since Set is not used to set an object. If list is an Array already, then it will be also an Array after that.
This is how you get the number of rows in a function.
Option Explicit
Function l_number_of_rows(rng_range As Range) As Long
l_number_of_rows = rng_range.Rows.Count
End Function
if you want the one from the dimension, this is a possible solution.
Sub test()
Dim MyArray(1 To 2, 0 To 3) As Long
MyArray(1, 0) = 10
MyArray(1, 1) = 11
MyArray(1, 2) = 12
MyArray(1, 3) = 13
MyArray(2, 0) = 20
MyArray(2, 1) = 21
MyArray(2, 2) = 22
MyArray(2, 3) = 23
Debug.Print MyArray(UBound(MyArray), 3)
End Sub
The UBound function is to give you elements in a variant array, you're passing it a range. You could try this:
Sub mains()
Dim myRange As Range, myArray As Variant
Set myRange = Range("A1:B4")
myArray = myRange.Value
Debug.Print test(myArray)
End Sub
Function test(list) As Double
test = UBound(list)
End Function
EDIT With a two dimensional range (as mentioned by KazimierzJawor) the default ubound will be the vertical, as you wanted, if you want to specify, you can add the optional perameter UBound(list, 1), but with the data you specified this defults to 4 as you wished
Ubound is a function that works on arrays you are passing it a range
try this
dim i(3)
i(0) = 1
i(1) =2
i(2) = 3
msgbox ubound(i)

Custom Function Entering the Result Array into a Different Cell

I have created my own function to determine count the values in between to given values in increments of 30 as seen here
Function InBetween(First As Integer, Last As Integer)
Dim i As Long, F As String, a() As String
F = First
For i = First + 30 To Last Step 30
F = F & "|" & i
Next i
InBetween = F
End Function
When I use this function, I currently have it returning the result array in the cell the formula was entered into in the format of "1|2|3|4". Is there a way I can get this array to populate into the cell below the one containing the formula?
Note: I don't want the formula in the cell as I need to refer to the cell in a future equation that will use the result and not the equation.
This was surprisingly difficult. At first I tried calling a sub from the function to affect the cell below using application.caller but this always returned a #value error. It seems a UDF can't run anything that affects the worksheet.
Eventually I came up with this:
Create a worksheet change event by pasting this into the worksheet object in vb:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
On Error Resume Next
If Left(Target.Offset(-1, 0).Formula, 10) = "=InBetween" Then Call DoX(Target.Offset(-1, 0), InBetween(10, 60))
On Error GoTo 0
End Sub
Then paste this into a module
Sub DoX(r As Range, val As String)
Sheets(r.Parent.Name).Cells(r.Row, r.Column) = ""
Sheets(r.Parent.Name).Cells(r.Row + 1, r.Column) = val
End Sub
Then use your function as normal, but remember to hit return after you enter it so the active cell is the cell below where you entered the formula.

Function will not return array when range contains only one value

I have a function meant to return an array which is created out of a single-column list of data. I have been using this function's return value essentially as a pseudo-global variable (LINENAMES_ARRAY) which I pass to many functions. Those functions than do checks on it such as If Len(Join(LINENAMES_ARRAY)) = 0 Then or go through items with For Each statements. Here is the code:
Function LINENAMES_ARRAY() As Variant
'returns an array of all items in the main sheet linenames column
LINENAMES_ARRAY = Application.Transpose(MAIN.Range( _
MAIN.Cells(MAIN_HEAD_COUNT + 1, MAIN_LINENAMES_COLUMN), _
MAIN.Cells(LINENAMES_COUNT + 1, MAIN_LINENAMES_COLUMN)))
End Function
I recently stumbled on one of those you-don't-see-it-till-you-see-it problems while using this workbook for a new project, where if the array happens to be only 1 element, everything fails. Apparently in that case, this returns a single value so Join() will fail For Each __ in LINENAMES_ARRAY will too. Why won't it treat this as a 1x1 array rather than a free value? I have started to mitigate the problem by rewriting functions where this is called, to check whether it is an array, then do some other procedure. Things like:
For j = 1 To LINENAMES_COUNT
LINES_BOX.AddItem lineNames(j)
Next j
is changed to:
If Not IsArray(LINENAMES_ARRAY) Then
myListBox.AddItem CStr(LINENAMES_ARRAY)
Else
For j = 1 To LINENAMES_COUNT
LINES_BOX.AddItem LINENAMES_ARRAY(j)
Next j
End If
However this becomes messy and is adding many extra checks to my code that I would prefer to handle in the LINENAMES_ARRAY function. Is there a way to return a 1x1 array? Or any other workaround?
An array can have a single element if you create it as a single element array and populate it in an array manner.
Option Explicit
Dim MAIN_HEAD_COUNT As Long
Dim LINENAMES_COUNT As Long
Dim MAIN_LINENAMES_COLUMN As Long
Dim MAIN As Worksheet
Sub stuff()
Dim arr As Variant
Set MAIN = Worksheets("Sheet1")
MAIN_LINENAMES_COLUMN = 2
MAIN_HEAD_COUNT = 2
LINENAMES_COUNT = 2
arr = LINENAMES_ARRAY()
Debug.Print IsArray(arr)
Debug.Print LBound(arr) & ":" & UBound(arr)
End Sub
Function LINENAMES_ARRAY() As Variant
Dim a As Long, tmp() As Variant
ReDim tmp(0 To LINENAMES_COUNT - MAIN_HEAD_COUNT)
For a = 0 To LINENAMES_COUNT - MAIN_HEAD_COUNT
tmp(a) = MAIN.Range(MAIN.Cells(MAIN_HEAD_COUNT + 1, MAIN_LINENAMES_COLUMN), _
MAIN.Cells(LINENAMES_COUNT + 1, MAIN_LINENAMES_COLUMN)).Cells(a).Value2
Next a
'returns an array of all items in the main sheet linenames column
LINENAMES_ARRAY = tmp
End Function
Results from the VBE's Immediate window:
True
0:0

Excel formula calculating once then deleting

I have an excel formula:
=SplitKey(GetSysCd(INDEX([ReportValue],MATCH("mtr_make_model",[FieldName],0)),INDEX([ListName],MATCH("mtr_make_model",[FieldName],0))), 0)
which is running a few subroutines in VBA, but mainly matching values and inserting those values into a cell. When it finds a value for "mtr_make_model" it runs and matches the values inside a sys codes table. The issue I am having is that it is calculating once and then it removes the formula and now has solely the value... In the event that I go to the mtr_make_model field and change the value, the formula does not recalculate. Has anyone heard of this happening? Is this due to something in the VBA code? How do I make that formula stay put and if certain values change, the formula recalculates?
Thanks in advance.
Here are the two functions:
Public Function GetSysCd(ByVal name As String, sysCdTableName As String) As String
Dim r As Integer
Dim sysCdTable As Range
Dim nameList As Variant
Dim sysCd As String
On Error GoTo GetSysCd_Error
Set sysCdTable = Worksheets("sys_cd").Range(sysCdTableName)
nameList = WorksheetFunction.Index(sysCdTable, 0, 2)
r = WorksheetFunction.Match(name, nameList, 0)
sysCd = WorksheetFunction.Index(sysCdTable, r, 1)
GetOutOfHere:
On Error GoTo 0
GetSysCd = sysCd
Exit Function
GetSysCd_Error:
sysCd = ""
GoTo GetOutOfHere
End Function
Public Function SplitKey(s As String, v As Integer)
Dim aString As Variant
Dim r As Integer
If Len(s) > 2 Then
aString = Split(s, "_")
If v = 0 Or v = 1 Then
SplitKey = aString(v)
Else
SplitKey = aString(0)
End If
Else
SplitKey = ""
End If
End Function
I don't think the functions are relevant at this point, but rather just a matter of the function not recalculating when a variable in the formula changes...
The problem could be that Excel only recalculates functions when one of their arguments changes, and your GetSysCd function is referring to a range that is not in its argument list
Set sysCdTable = Worksheets("sys_cd").Range(sysCdTableName)
where sysCdTableName is just a string rather than a reference.
You can make the functions recalculate in real time by adding Application.Volatile True to the top of each function.