I want to design a Function or Sub that accepts any number of boolean conditions and add them to an IF statement. The code i imagine goes like this:
Function comp (ParamArray list() As Variant)
If (list(0) = True) And (list(1) = true) ..... (list(last) = true) then
'random code
End If
End Function
where list() would be expresions like:
x = y-1,somethingToCompare <> anotherThing, etc...
It would be interesting if i could add the "and" as another argument, to be changed to "or" if i wished to.
Function comp (VyVal compare as Boolean, ParamArray list() As Variant)
dim comparison as String???
If compare then
comparison = "and"
else
comparison = "or"
end if
If (list(0) = True) comparison (list(1) = true) ..... (list(last) = true) then
'random code
End If
End Function
The final idea is to use that function like this:
Sub code ()
if comp(True, condition1, condition2, ...) then
'random code'
End Sub
Avoid directly looking at that code i wrote lest it burn your eyes.
Is something like this posible or should i get a lollipop?
Maybe i'm looking at this in the wrong way, and there's an easier way of doing something similar or even better.
Python (and some other languages) has useful functions all() and any() which take as input an array (or some other iterable) of Booleans and returns either True or False depending on whether or not some, all, or none of the passed Booleans are True. You could write VBA versions of these (using Some() instead of Any() since Any happens to be a somewhat obscure keyword in VBA):
Function All(ParamArray conditions()) As Boolean
Dim i As Long
For i = LBound(conditions) To UBound(conditions)
If Not conditions(i) Then
All = False
Exit Function
End If
Next i
All = True
End Function
Function Some(ParamArray conditions()) As Boolean
Dim i As Long
For i = LBound(conditions) To UBound(conditions)
If conditions(i) Then
Some = True
Exit Function
End If
Next i
Some = False
End Function
You could use these functions directly to conditionally call code.
Arguably it might be more useful to change the above definitions to:
Function All(conditions As Variant) As Boolean
Dim i As Long
For i = LBound(conditions) To UBound(conditions)
If Not conditions(i) Then
All = False
Exit Function
End If
Next i
All = True
End Function
Function Some(conditions As Variant) As Boolean
Dim i As Long
For i = LBound(conditions) To UBound(conditions)
If conditions(i) Then
Some = True
Exit Function
End If
Next i
Some = False
End Function
Now you would need to use calls like Some(Array(c1, c2, c3)) rather than Some(c1,c2,c3) if you had a literal list of conditions, but you would have the flexibility to pass in an entire array of conditions. Using this second definition you could write something like the following (which answers your original question):
Sub Sometimes(when As String, ParamArray conditions())
Dim run As Boolean
Dim cond As Variant
cond = conditions
run = IIf(when = "All", All(cond), Some(cond))
If run Then
Debug.Print "Running random code"
Else
Debug.Print "Not running random code"
End If
End Sub
Then, for example, Sometimes "Some",True,True,False results in Running random code being printed.
sub pointless(byval IsAnd as boolean, paramarray conditions())
dim i as long
for i=lbound(conditions) to ubound(conditions)
if IsAnd then
if not conditions(i) then exit sub
else
if conditions(i) then exit for
end if
next
'random code
end sub
But you should realise that the procedure will receive results of the comparisons passed to it, not the comparisons themselves. So there is no point really to have such procedure in the first place, you can just write directly in your code:
if x = y-1 and somethingToCompare <> anotherThing then
'random code
end if
Related
I have a vba function that checks if a user-input from a text box is positive integer or not. The code below:
Public Function IsPosInteger(n As Variant) As Boolean
If IsNumeric(n) = True Then
If (n = Int(n)) And n > 0 Then
IsPosInteger = True
Else
IsPosInteger = False
End If
Else
IsPosInteger = False
End If
End Function
The problem is that upon testing the function still returns false for a valid positive integer. Upon further investigation, I noticed that the variable type by default for texbox values is String. Probably the main reason why IsNumeric is returning false.
Function below is what I used to determine the type of the variable.
TypeName(n)
This worked for me.
I used an inputbox that contained:
strings (False)
negative numbers (False)
positive numbers (true)
positive numbers and letters (false)
Public Function IsPosInteger(n As Variant) As Boolean
If n > 0 And IsNumeric(n) Then IsPosInteger = True
End Function
Now, the other issue may arise that the value n is still technically type String due to the nature of the inputbox -- even if it passes the test of this function. If you wish to change this behavior, continue reading.
To do this, ensure that you are using ByRef (when this is intentional, I usually type out ByRef on the argument, even though it is automatically assumed by VBA that any argument passed to a function is ByRef if it doesn't explicitly state ByVal).
If this is the outcome you are wanting, then you can use this function:
Public Function IsPosInteger(ByRef n As Variant) As Boolean
If n > 0 And IsNumeric(n) Then
IsPosInteger = True
n = clng(n) '<-- This converts the variant (currently a string) n to type long,
' only if it passes the test
end if
End Function
You must ensure that n in the calling routine is of type Variant, else you will encounter an error.
Public Function IsPosInteger(s As String) As Boolean 'boolean data type is false by default.
If (IsNumeric(s) = False) Then Exit Function
If (s < 1) Then Exit Function
If (InStr(s, ".") = False) Then IsPosInteger = True
End Function
Function tests that the input is numeric, not less than 1, and does not contain a decimal. Here is an example of how you could use it in your calling sub:
Sub TestInput()
Dim sUserInput As String
Dim boolPositiveInt As Boolean
Dim i As Integer
sUserInput = Range("A1").Value2
boolPositiveInt = IsPosInteger(sUserInput)
If (boolPositiveInt = False) Then
MsgBox "Invalid input. Please enter a positive integer"
Exit Sub
End If
i = CInt(sUserInput)
'continue working with integer variable here
End Sub
So I'm reading through my source code looking for places to improve the code when I come across this unholy chunk of code.
Public Function ReadPDFFile(filePath As String,
Optional maxLength As Integer = 0) As List(Of String)
Dim sbContents As New Text.StringBuilder
Dim cArrayType As Type = GetType(PdfSharp.Pdf.Content.Objects.CArray)
Dim cCommentType As Type = GetType(PdfSharp.Pdf.Content.Objects.CComment)
Dim cIntegerType As Type = GetType(PdfSharp.Pdf.Content.Objects.CInteger)
Dim cNameType As Type = GetType(PdfSharp.Pdf.Content.Objects.CName)
Dim cNumberType As Type = GetType(PdfSharp.Pdf.Content.Objects.CNumber)
Dim cOperatorType As Type = GetType(PdfSharp.Pdf.Content.Objects.COperator)
Dim cRealType As Type = GetType(PdfSharp.Pdf.Content.Objects.CReal)
Dim cSequenceType As Type = GetType(PdfSharp.Pdf.Content.Objects.CSequence)
Dim cStringType As Type = GetType(PdfSharp.Pdf.Content.Objects.CString)
Dim opCodeNameType As Type = GetType(PdfSharp.Pdf.Content.Objects.OpCodeName)
Dim ReadObject As Action(Of PdfSharp.Pdf.Content.Objects.CObject) = Sub(obj As PdfSharp.Pdf.Content.Objects.CObject)
Dim objType As Type = obj.GetType
Select Case objType
Case cArrayType
Dim arrObj As PdfSharp.Pdf.Content.Objects.CArray = DirectCast(obj, PdfSharp.Pdf.Content.Objects.CArray)
For Each member As PdfSharp.Pdf.Content.Objects.CObject In arrObj
ReadObject(member)
Next
Case cOperatorType
Dim opObj As PdfSharp.Pdf.Content.Objects.COperator = DirectCast(obj, PdfSharp.Pdf.Content.Objects.COperator)
Select Case System.Enum.GetName(opCodeNameType, opObj.OpCode.OpCodeName)
Case "ET", "Tx"
sbContents.Append(vbNewLine)
Case "Tj", "TJ"
For Each operand As PdfSharp.Pdf.Content.Objects.CObject In opObj.Operands
ReadObject(operand)
Next
Case "QuoteSingle", "QuoteDbl"
sbContents.Append(vbNewLine)
For Each operand As PdfSharp.Pdf.Content.Objects.CObject In opObj.Operands
ReadObject(operand)
Next
Case Else
'Do Nothing
End Select
Case cSequenceType
Dim seqObj As PdfSharp.Pdf.Content.Objects.CSequence = DirectCast(obj, PdfSharp.Pdf.Content.Objects.CSequence)
For Each member As PdfSharp.Pdf.Content.Objects.CObject In seqObj
ReadObject(member)
Next
Case cStringType
sbContents.Append(DirectCast(obj, PdfSharp.Pdf.Content.Objects.CString).Value)
Case cCommentType, cIntegerType, cNameType, cNumberType, cRealType
'Do Nothing
Case Else
Throw New NotImplementedException(obj.GetType().AssemblyQualifiedName)
End Select
End Sub
Using pd As PdfSharp.Pdf.PdfDocument = PdfSharp.Pdf.IO.PdfReader.Open(filePath, PdfSharp.Pdf.IO.PdfDocumentOpenMode.ReadOnly)
For Each page As PdfSharp.Pdf.PdfPage In pd.Pages
ReadObject(PdfSharp.Pdf.Content.ContentReader.ReadContent(page))
If maxLength > 0 And sbContents.Length >= maxLength Then
If sbContents.Length > maxLength Then
sbContents.Remove(maxLength - 1, sbContents.Length - maxLength)
End If
Exit For
End If
sbContents.Append(vbNewLine)
Next
End Using
'Return sbContents.ToString
Dim ReturnList As New List(Of String)
For Each Line In sbContents.ToString.Split(vbNewLine)
If String.IsNullOrWhiteSpace(Line.Trim) Then
Else
ReturnList.Add(Line.Trim)
End If
Next
Return ReturnList
End Function
All this does is read the text parts of a PDF using PDFSharp. What caught my eye however was line 17. Is that a Sub inside of the function?
So, what exactly is this Sub inside of a function? I didn't write this code so I've never seen anything like this before.
How does this work exactly and why wouldn't I use a function to do the processing and then return the results?
In short, my question is, what is this, how does it work, and why would I want to use something like this?
That's a so-called Lambda expression. They're used to create inline (or more correctly: in-method) methods, which makes them more dynamic than normal methods.
In your example a lambda expression is not necessary and only makes the code harder to understand. I suppose the author of that code wrote a lambda expression instead of a separate method in order to not expose ReadObject to any outside code.
One of the best uses for a lambda expression IMO is when you want to make thread-safe calls to the UI thread, for instance:
If Me.InvokeRequired = True Then
Me.Invoke(Sub() TextBox1.Text = "Process complete!")
Else
TextBox1.Text = "Process complete!"
End If
...where the same code without a lambda would look like this:
Delegate Sub UpdateStatusTextDelegate(ByVal Text As String)
...somewhere else...
If Me.InvokeRequired = True Then
Me.Invoke(New UpdateStatusTextDelegate(AddressOf UpdateStatusText), "Process complete!")
Else
UpdateStatusText("Process complete!")
End If
...end of somewhere else...
Private Sub UpdateStatusText(ByVal Text As String)
TextBox1.Text = Text
End Sub
There are also other examples where lambda expressions are useful, for instance if you want to initialize a variable but do some processing at first:
Public Class Globals
Public Shared ReadOnly Value As Integer = _
Function()
DoSomething()
Dim i As Double = CalculateSomething(3)
Return Math.Floor(3.45 * i)
End Function.Invoke()
...
End Class
Yet another usage example is for creating partially dynamic event handlers, like this answer of mine.
I want to create a function where I use one parameter so the function returns another value. In this case I want to insert "links" in a function and then the function should give me back "Left" (Im need a of 'translations' so therefore I use case of if statements.
I have the following function
Public Function newParameter(aParameter) As Integer
Select Case aParameter
Case Is = "Links"
aParameter = "ppAlignLeft"
End Select
End Function
Sub finalFunction()
Dim newParameter As String
newParameter = newParameter(Links)
MsgBox (newParameter)
End Sub
This however gives me a compile error that a matrix is required. Any thoughts on how I can get this working?
You're passing the argument ByRef and change it's value. You dont need a function for this. Functions retun values.
Since you are using a Function though, why dont you have the Function return the value you want?
Public Function newParameter(ByVal aParameter As String) As String
Select Case aParameter
Case "Links":
newParameter = "ppAlignLeft"
Case "A":
newParameter = "You passed A"
Case Else:
newParameter = "No match"
End Select
End Function
Sub finalFunction()
Dim newParameter1 As String
newParameter1 = newParameter("Links") 'This reads 'ppAlignLeft'
MsgBox newParameter1
End Sub
I am having a weird issue when I'm calling a function from another function inside an if statement. I defined a Sub, which I am using to test this function, and it calls on a function which relies on another function I use to compare values. The code is below, which should make things clear. Essentially, I don't understand how it's possible for the code to work fine in the print statement, and then throw an error in the GetMatch function. I appreciate any help.
Edit: All of a sudden everything works. Do debugging breakpoints affect the program? I haven't changed anything, but CStr() is no longer required when calling GetMatch. I haven't touched any of the subs or functions, but I did clear some breakpoints. If I find what caused it, I'll post a solution. Thanks for the help everyone.
Edit2: Maybe this is a bug with VBA? If I add the CStr() option to the indexOrder(...) calls, things work. Before, without the CStr() options, things did not work. Now, strangely enough, after using the CStr(), I am able to remove the CStr()'s entirely from the program, and things work again. It breaks if I undo to the point where they weren't there originally though. I don't know what this could be, but if anyone has an explanation, I'm very interested. Thanks
Sub testFind()
Dim SortOrder() As Variant
Dim indexOrder() As Variant
SortOrder = Array("Contact Email", "Last Name", "First Name", "Attempt #", "Customization", "Template #")
indexOrder = Array("First Name", "First Name", "Template #", "Customization")
findAndReplace(indexOrder, SortOrder)
End Sub
Function findAndReplace(indexOrder As Variant, list As Variant) As Variant
Dim indexLength As Integer
Dim listLength As Integer
Debug.Print TypeName(indexOrder(0)) ' Identifies as String
indexLength = getVariantLength(indexOrder)
listLength = getVariantLength(list)
Debug.Print GetMatch(CStr(indexOrder(1)), CStr(indexOrder(1))) ' This works fine. Returns 0 as it should
If GetMatch(indexOrder(1), indexOrder(1)) = 0 Then ' Fails with ByRef error
Debug.Print ("Why don't I work?")
End If
End Function
Function GetMatch(A As String, B As String) As Integer
A = Trim(A)
B = Trim(B)
If (IsEmpty(A) Or Trim(A) = "") Then
GetMatch = 1
Exit Function
ElseIf (IsEmpty(B) Or Trim(B) = "") Then
GetMatch = -1
Exit Function
End If
GetMatch = StrComp(A, B, vbTextCompare)
End Function
Function getVariantLength(vari As Variant) As Integer
If IsNull(index) Then
getVariantLength = 0
Else
getVariantLength = UBound(vari) - LBound(vari) + 1
End If
End Function
You don't have a Sub vartest() or Function vartest(), it's trying to call a sub/function that doesn't exist, or at least it isn't included.
Edit: You aren't doing anything with the function. A function will return a value, a sub will 'do' something. You need to assign a variable to whatever it returns, or do a MessageBox or some other way of returning the value.
The next issue is it's trying to call a function getVariantLength() that isn't defined or listed.
I would like to use this property:
Public Property Get HasNoData() As Boolean
HasNoData = (numberOfColumns < 2 And numberOfRows < 2)
End Property
Sub test()
Dim numberOfColumns As Long
Dim numberOfRows As Long
numberOfColumns = 5
numberOfRows = 3
If HasNoData Then
MsgBox True
Else:
MsgBox False
End If
End Sub
Everytime I get TRUE, no matter whether the conditions are met or not. I might get the whole idea wrong, so please let me know.
There are a few things that are wrong with your code as it stands now:
Properties can only be members of a class
Variables are subject to what is known as scope, and specifically your two variables numberOfColumns and numberOfRows are only available within the scope of the test() Sub, so they and their values cannot be seen inside the HasNoData() property unless they are passed as parameters or set as members of the class
The VBA Msgbox takes a String as the parameter, whereas you have provided a Boolean
I think what you really want is to make "HasNoData" into a function that takes two parameters, one for rows and one for columns. Try this:
Public Function HasNoData(NumRows, NumCols) As Boolean
HasNoData = (NumCols < 2 And NumRows < 2)
End Function
Sub test()
If HasNoData(3, 5) Then
MsgBox "There is data!"
Else
MsgBox "There is no data."
End If
End Sub