How do I evaluate conditions passed into a function? - vba

I'm attempting to code a CONCATENATEIFS function in VBA that works like SUMIFS, etc. Here's an example call:
=ConcatenateIfs(",",$E$6:$E$9,$F$6:$F$9,"Something",$G$6:$G$9,">=2")
Public Function ConcatenateIfs(JoinStr As String, StrRange As Range, ParamArray var() As Variant) As String
Dim numberOfConditions As Integer
numberOfConditions = (UBound(var) - LBound(var) + 1) / 2
Dim tmpResult As String
tmpResult = ""
Dim includeItem As Boolean
For Item = 1 To StrRange.Count
includeItem = True
For Condition = 1 To numberOfConditions:
If var((Condition - 1) * 2)(Item) <> var((Condition - 1) * 2 + 1) Then
includeItem = False
End If
Next Condition
If includeItem = True And Item = 1 Then
tmpResult = StrRange(Item)
ElseIf includeItem = True Then
tmpResult = tmpResult + JoinStr + StrRange(Item)
End If
Next Item
ConcatenateIfs = tmpResult
End Function
The above function never seems to recognize that a condition has been met, meaning this part of the code is not working (i.e. that it always evaluates the inequality to True):
If var((Condition - 1) * 2)(Item) <> var((Condition - 1) * 2 + 1) Then
includeItem = False
End If
How do I fix this so that the conditions are tested properly between an item of the criteria_range and the criteria itself? Bonus points: how do I break out of the condition loop as soon as a criteria (properly) is not met?

Related

How to fix error "Arithmetic operation resulted in an overflow"

Whenever I want to find out a grade in the program I made.
It highlights the statement k = k + 1 and says:
Arithmetic operation resulted in an overflow
Can someone help?
Sub SearchStudentData()
Dim Sname, G As String
Dim Lname, Lgradetext, position, j, k, position1 As Integer
Dim gradefile As IO.StreamReader
Dim Valid As Boolean
Valid = False
Console.WriteLine("Enter the name of the student of whom you want the grade!")
Sname = Console.ReadLine()
Lname = Len(Sname)
gradefile = New IO.StreamReader("D:\Grades.txt")
Do Until gradefile.EndOfStream
gradetext = gradefile.ReadLine()
Lgradetext = Len(gradetext)
j = 0
k = 0
Do
k = k + 1 'It highlights this line of code
position1 = k
Loop Until Mid(gradetext, k, 1) = ":"
Do
j = j + 1
position = j
Loop Until Mid(Lgradetext, j, 1) = ","
If Sname = Right(gradetext, position1 + 1) And Sname = Left(gradetext, position - 1) Then
Valid = True
End If
If Valid = True Then
G = Right(Lgradetext, Lgradetext - 1)
Console.WriteLine(G)
Else
Valid = False
Console.WriteLine("Ypu have failed this PROGRAM")
End If
Loop
gradefile.Close()
End Sub
Your input is not what you expected. The line from your file has no ":" or "," in it, resulting in an infinite loop, and eventually the error when the counter goes past the max. You could use String.IndexOf() to determine if the value is present, instead of a loop. When the value is not present, -1 will be returned. Here's an example:
Dim indexOfColon As Integer = gradetext.IndexOf(":")
Dim indexOfComma As Integer = gradetext.IndexOf(",")
If indexOfColon <> -1 AndAlso indexOfComma <> -1 Then
' ... both ":" and "," were present in the string, do something with those values ...
End If

why i am getting this error "There is no row at position 0 vb.net"?

I am trying to add values from LANG_OBJ.TEXT to DataTableRow.
While adding i am getting a error:
There is no row at position 0
dtsaveTranslate = checkTranslateValues()
lang_id_text CType(Controls.Find("txt_id"True).FirstOrDefault(),TextB`enter code here`ox)
lang_de_text = CType(Controls.Find("txt_de", True).FirstOrDefault(), TextBox)
lang_row = dtsaveTranslate.NewRow()
lang_row("de") = lang_de_text
For Each row As DataRow In dtlang.Rows
lang_iso = Convert.ToString(row("ISO"))
lang_obj = CType(Controls.Find("txt_" + lang_iso, True).FirstOrDefault(), TextBox)
Dim len As Integer = lang_obj.Text.Length
Dim count_de As Integer = lang_de_text.Text.Length
progress.ProgressValue = len + 1
If Convert.ToString(row("isUbersetzen")) = "True" AndAlso lang_iso <> "de" Then
lang_obj.Text = lanClass.GoogleApiTranslate("de", Convert.ToString(row("ISO")), lang_de_text.Text.Trim())
lang_row(lang_iso) = lang_obj.Text
Else
count_txt = 0
End If
Next
dtsaveTranslate.Rows.Add(lang_row)
First you need to make sure that dtLang has rows and then try to loop them!
You need to make sure that dtLang is loaded with data. You should research why it doesn't contain any rows first. From the code above I can't see how the rows are loaded in there. Therefore, the proper check should start with:
If dtlang.Rows.count > 0 '<--- Added a check before looping
For Each row As DataRow In dtlang.Rows
lang_iso = Convert.ToString(row("ISO"))
lang_obj = CType(Controls.Find("txt_" + lang_iso, True).FirstOrDefault(), TextBox)
Dim len As Integer = lang_obj.Text.Length
Dim count_de As Integer = lang_de_text.Text.Length
progress.ProgressValue = len + 1
If Convert.ToString(row("isUbersetzen")) = "True" AndAlso lang_iso <> "de" Then
lang_obj.Text = lanClass.GoogleApiTranslate("de", Convert.ToString(row("ISO")), lang_de_text.Text.Trim())
lang_row(lang_iso) = lang_obj.Text
Else
count_txt = 0
End If
Next
End If

How can I extract the 'logical_test' from an if statement in excel?

I'm putting together an excel spreadsheet for calculations, and I need to be able to show the formulas to go with the decisions, for the most part its pretty straight forward, but When I come to an 'if' formula in an excel cell, I don't want to show the value_if_true and value_if_false... Just the logical_test value.
Example:
Formula is: =if(and(5<=A1, A1<=10),"Pass", "Fail");
Result will be: "and(5<=A1, A1<=10)"
I need to be able to work with complex logical tests which may include nested if statements, so just splitting at the commas won't work reliably. Similarly the value_if_true and value_if_false statements could also contain if statements.
Any ideas?
If have clear understanding of what you asking for, then you can use something like this (shall be used only with IF() statement :
Function extrIf(ByVal ifstatement As Range) As String
Dim S$, sRev$, x%, k
S = Replace(Replace(ifstatement.Formula, "IF(", "\"), "),", ")|")
sRev = StrReverse(S)
If InStr(1, sRev, "|") > InStr(1, sRev, "\") Or InStr(1, sRev, "|") = 0 Then
x = InStr(1, StrReverse(Left(sRev, InStr(1, sRev, "\"))), ",") - 1
S = Mid(S, 1, Len(S) - InStr(1, sRev, "\") + x) & "|"
End If
sRev = ""
For Each k In Split(S, "|")
If k <> "" Then
If k Like "*\*" Then
sRev = sRev & ", " & Mid(k, InStr(1, k, "\") + 1, 999)
End If
End If
Next
extrIf = Mid(sRev, 3, 999)
End Function
example:
test:
Maybe this is not complete solution for you, but I think it might give you right direction.
If the cell formula starts with an If statement then you can return the logic test (starting after the first open parenthesis) by determining the position of the first comma where the sum of the previous open parenthesis - the sum previous closed = 0.
Formulas
Function ExtractIfTest(Target As Range) As String
Dim ch As String, s As String
Dim openP As Long
Dim x As Long
s = Target.formula
For x = 5 To Len(s)
ch = Mid(s, x, 1)
If Mid(s, x, 1) = "(" Then
openP = openP + 1
ElseIf Mid(s, x, 1) = ")" Then
openP = openP - 1
ElseIf Mid(s, x, 1) = "," And openP = 0 Then
ExtractIfTest = Mid(s, 5, x - 12)
End If
Next
End Function
Results
There might be instances where the is a comma without parenthesis A1,B1. If this happens simple escape them with parenthesis (A1,B1)
I've written an UDF that extract any of the parameters of the target formula. It's close to the one in Thomas answer, but more global and takes into account strings that can enclose commas or parenthesis.
Function ExtractFormulaParameter(Target As Range, Optional Position As Long = 1) As Variant
Dim inString As Boolean
Dim formula As String
Dim st As Long, sp As Long, i As Long, c As String
Dim parenthesis As Long, comma As Long
formula = Target.formula
st = 0: sp = 0
If Position <= 0 Then ExtractFormulaParameter = CVErr(xlErrValue): Exit Function
For i = 1 To Len(formula)
c = Mid$(formula, i, 1)
If inString Then
If c = """" Then
inString = False
End If
Else
Select Case c
Case """"
inString = True
Case "("
parenthesis = parenthesis + 1
If parenthesis = 1 And Position = 1 Then
st = i + 1
End If
Case ")"
parenthesis = parenthesis - 1
If parenthesis = 0 And sp = 0 Then sp = i: Exit For
Case ","
If parenthesis = 1 Then
comma = comma + 1
If Position = 1 And comma = 1 Then sp = i: Exit For
If Position > 1 And comma = Position - 1 Then st = i + 1
If Position > 1 And comma = Position Then sp = i: Exit For
End If
Case Else
End Select
End If
Next i
If st = 0 Or sp = 0 Then
ExtractFormulaParameter = CVErr(xlErrNA)
Else
ExtractFormulaParameter = Mid$(formula, st, sp - st)
End If
End Function
By default it returns the first parameter, but you can also return the second or the third, and it should work with any formula.
Thanks for the replies all. I thought about this more, and ended up coming up with a similar solution to those posted above - essentially string manipulation to extract the text where we expect to find the logical test.
Works well enough, and I'm sure I could use it to extract further logical tests from substrings too.

Parsing the Parameters of a Function

I am trying to create a UDF within VBA which go through some function syntax and treat it as Text.
The function will look like :
FunctionA( Param1 , Param2 , Param3 , Param 4 )
I am trying to develop a UDF which will pull out the value of the Param based on the position I input into my UDF function.
GetN( FunctionA , 3 ) = "Param3"
GetN FunctionA , 1 ) = "Param1"
Here's my function so far but it's off....
It's behaving like :
GetN( FunctionA , 0 ) = Param2
Here's my function:
Function GetN(sInputString As String, n As Integer) As String
Dim sFindWhat As String
Dim j, FindA, FindB As Integer
Application.Volatile
sFindWhat = ","
FindA = 0
For j = 0 To n
FindA = InStr(FindA + 1, sInputString, sFindWhat)
FindB = InStr(FindA + 1, sInputString, sFindWhat)
If FindB = 0 Then FindB = InStr(FindA + 1, sInputString, ")")
If FindA = 0 Then Exit For
Next
GetN = Trim(Mid(sInputString, FindA + 1, FindB - FindA - 1))
End Function
Thank you for help
Split should work, though to correctly handle the case of nested functions, a preliminary hack is to first replace commas at the top level by a safe delimiter (e.g. [[,]]) and then splitting on that delimiter:
Function GetParameterN(func As String, n As Long) As String
Dim args As Variant
Dim safeArgs As String
Dim c As String
Dim i As Long, pdepth As Long
func = Trim(func)
i = InStr(func, "(")
args = Mid(func, i + 1)
args = Mid(args, 1, Len(args) - 1)
For i = 1 To Len(args)
c = Mid(args, i, 1)
If c = "(" Then
pdepth = pdepth + 1
ElseIf c = ")" Then
pdepth = pdepth - 1
ElseIf c = "," And pdepth = 0 Then
c = "[[,]]"
End If
safeArgs = safeArgs & c
Next i
args = Split(safeArgs, "[[,]]")
GetParameterN = Trim(args(n - 1))
End Function
For example,
Sub test()
Dim i As Long
For i = 1 To 3
Debug.Print GetParameterN("f(x,g(x,y,z),z)", i)
Next i
End Sub
Produces:
x
g(x,y,z)
z
I see no good reason to make this function volatile.

Excel VBA - listbox.selected property causing error

I'm developing a program within Excel and VBA, and am getting a run time error 380 when I try to change the selected property of a listbox.
The property is determined by a column on an excel spreadsheet, containing True or False values. I've printed the contents of these cells to the console and can confirm that the true/false values are working correctly, however when I try to assign these values to my listbox.selected property I get an error.
The function is below, any help or suggestions would be much appreciated.Error occurs on this line:
ElementListBox.Selected(count - 1) = TaskListSheet.Cells(TaskListCellRef(Task, ref.Row) + count + 1, TaskBreakdownColumnRefs(TaskBreakdownColumnHeaders.Included)).Value
Public Function LoadTier2SubTaskList(ByVal Task As Single, ByRef ElementListBox As Control)
ElementListBox.Clear
WorklistComboBox.Clear
Dim count As Single
Dim finished As Boolean
Dim TaskListSheet As Worksheet
Set TaskListSheet = TBSheet
finished = False
For count = 1 To 50
Next count
count = 1
Dim TaskString As String
Do While finished = False
TaskString = TaskListSheet.Cells(TaskListCellRef(Task, ref.Row) + count + 1, TaskBreakdownColumnRefs(TaskBreakdownColumnHeaders.ElementOfWork)).Text
If TaskString = vbNullString Then
finished = True
ElseIf TaskListSheet.Cells(TaskListCellRef(Task, ref.Row) + count + 1, TaskBreakdownColumnRefs(TaskBreakdownColumnHeaders.Tier)).Value = 2 Then
ElementListBox.AddItem (TaskString)
ElementListBox.Selected(count - 1) = TaskListSheet.Cells(TaskListCellRef(Task, ref.Row) + count + 1, TaskBreakdownColumnRefs(TaskBreakdownColumnHeaders.Included)).Value
Debug.Print (TaskListSheet.Cells(TaskListCellRef(Task, ref.Row) + count + 1, TaskBreakdownColumnRefs(TaskBreakdownColumnHeaders.Included)).Value)
End If
count = count + 1
Loop
End Function
Do you need to convert that value to a boolean?
ElementListBox.Selected(count - 1) = CBool(TaskListSheet.Cells(TaskListCellRef(Task, ref.Row) + count + 1, TaskBreakdownColumnRefs(TaskBreakdownColumnHeaders.Included)).Value)
Let's start eliminating some potential causes!
Does this work?
ElementListBox.Selected(count - 1) = true
Try this (replace your code from count=1 to Loop)...
count = 1
Dim itemCount as Integer
itemCount = 1
Dim TaskString As String
Do While finished = False
TaskString = TaskListSheet.Cells(TaskListCellRef(Task, ref.Row) + count + 1, TaskBreakdownColumnRefs(TaskBreakdownColumnHeaders.ElementOfWork)).Text
If TaskString = vbNullString Then
finished = True
ElseIf TaskListSheet.Cells(TaskListCellRef(Task, ref.Row) + count + 1, TaskBreakdownColumnRefs(TaskBreakdownColumnHeaders.Tier)).Value = 2 Then
ElementListBox.AddItem (TaskString)
ElementListBox.Selected(itemCount - 1) = CBool(TaskListSheet.Cells(TaskListCellRef(Task, ref.Row) + count + 1, TaskBreakdownColumnRefs(TaskBreakdownColumnHeaders.Included)).Value)
Debug.Print (TaskListSheet.Cells(TaskListCellRef(Task, ref.Row) + count + 1, TaskBreakdownColumnRefs(TaskBreakdownColumnHeaders.Included)).Value)
itemCount = itemCount +1
End If
count = count + 1
Loop
Note the addition of the itemCount variable to count the number of items in the listbox