vba user defined operator in user defined fuction - vba

How do I make it so that the user can define the operator? For example the user will type
=iftrue(x+y,">=z",x+y) (much like a sumif or regular if function)
I understand I can use a normal if function or use a separate cell to solve for the logic. However the reason I am doing this is because I have a very cpu intensive computation that will have to recalculate rather than returning the already computed value.
This is currently what I have.
Function iftrue(Value1, Criteria1, ValueifTrue)
' Function
If Value1 = Criteria1 Then
iftrue = ValueifTrue
Else: iftrue = Value1
End If
End Function

This modified versions of your code will do I guess, try it out..
Function IfTrue(Value1, Criteria1, ValueifTrue)
'Function to Evaluate Calculation'
If Evaluate(Value1 & Criteria1) Then
IfTrue = ValueifTrue
Else
IfTrue = Value1
End If
End Function
This is just the basic idea, you might want to develop on this !

Related

(Micro Optimisation) - Exit function or let it run through all code

I am curious if Exit Sub, Exit Function, among others should be avoided when coding? I have read in various places that it is a bad practice and that you should, if possible, avoid it.
I have provided a function below to demonstrate what I mean:
Function Test()
Dim X As Integer
X = 5
If X = 10 Then
Test = True
Exit Function
Else
Test = False
Exit Function
End If
End Function
In the case above, the Exit Function isn't necessary since the entire code will be able to finish the entire routine without an issue. But by adding it, will it cause some issues?
The style you're using is from the VB5 and VBA era, which didn't support Return statement and we needed to assign the return value directly to the function name. In VB.NET you should always use Return assignment. Return will do both the assignment and the exit call in one statement.
Regarding your particular example, my impression is that the Exit Function statement does nothing more than doing a GoTo to the nearest End Function line. So these statements in your case are redundant and might get striped off by the compiler (not sure about this one).
Note that Exit statements become a nightmare from code-readability point of view. I have recently been a victim of this particular problem. Visual Basic is such a verbose language and Exit statements go unnoticed by the reader. A structured If/Else block is FAR more readable.
This depends on the current context of your code. If you are within a block (be that a loop, Sub or function etc.) and are attempting to leave, the Exit Statement will do fine.
The Return Statement will also work the same as a Exit Function or Exit Sub, see the answer to this SO question. But personally I prefer the exit statement in a function when I wish to return control to the calling code, but have no value to return.
In practically every case, there's a "better" approach than using Exit Function.
The following example will give the same result, but is shorter, and in my opinion much clearer.
Function Test() As Boolean
Dim X As Integer
X = 5
Return (X = 10)
End Function
To clarify a little, as #dotNET said. This is pre .net code. In dot net, you would change your code slightly to
Function Test() As Boolean
Dim X As Integer
X = 5
If X = 10 Then
Return True
Else
Return False
End If
End Function
This defines test as a function that returns a Boolean result and you use the Return statement to err.. return the result back to the calling statement and exits the function immediately at the aforementioned Return statement
for example ..
Dim result As Boolean
result = test

Syntax error using IIf

This is a simple question I hope. I learned about IIf today and want to implement it in some cases to save a few lines.
IIF(isnumeric(inputs), resume next, call notnum)
This is in red meaning there is a syntax error, how fix this? I have scanned MSDN article on this so I am not being lazy here.
That's not how the IIf function works.
It's a function, not a statement: you use its return value like you would with any other function - using it for control flow is a bad idea.
At a glance, IIf works a little bit like the ternary operator does in other languages:
string result = (foo == 0 ? "Zero" : "NonZero");
Condition, value if true, value if false, and both possible values are of the same type.
It's for turning this:
If foo = 0
Debug.Print "Zero"
Else
Debug.Print "NonZero"
End If
Into this:
Debug.Print IIf(foo = 0, "Zero", "NonZero")
Be careful though, IIf evaluates both the true and the false parts, so you will not want to have side-effects there. For example, calling the below Foo procedure:
Public Sub Foo()
Debug.Print IIf(IsNumeric(42), A, B)
End Sub
Private Function A() As String
Debug.Print "in A"
A = "A"
End Function
Private Function B() As String
Debug.Print "in B"
B = "B"
End Function
Results in this output:
in A
in B
A
That's why using IIf for control flow isn't ideal. But when the true result and false result arguments are constant expressions, if used judiciously, it can help improve your code's readability by turning If...Else...End If blocks into a simple function call.
Like all good things, best not abuse it.
You cannot use iif like this.
Here is a possible way to solve this:
if isnumeric(input) then
do something ...
else
call notnum
end if
IIf returns a value. As Mat's Mug stated, you can use it, to hand data into another method or function, but you can't call a procedure or function, which has no return value.
IsNumeric() for the conditional is okay, though.

VBA UDF fails to call Range.Precedents property

I am trying to write a simple UDF, which is supposed to loop through each of the cells of a given_row, and pick up those cells which do not have any precedent ranges, and add up the values.
Here is the code:
Public Function TotalActualDeductions(given_row As Integer) As Integer
Total_Actual_Deduction = 0
For present_column = 4 To 153
Set precedent_range = Nothing
If Cells(2, present_column) = "TDS (Replace computed figure when actually deducted)" Then
On Error Resume Next
Set precedent_range = Cells(given_row, present_column).Precedents
If precedent_range Is Nothing Then
Total_Actual_Deduction = Total_Actual_Deduction + Cells(given_row, present_column)
End If
End If
Next
TotalActualDeductions = Total_Actual_Deduction
End Function
If I try to run it by modifying the top declaration, from:
Public Function TotalActualDeductions(given_row As Integer) As Integer
to:
Public Function TotalActualDeductions()
given_row = 4
then it runs successfully, and as expected. It picks up exactly those cells which match the criteria, and all is good.
However, when I try to run this as a proper UDF from the worksheet, it does not work. Apparently, excel treats those cells which have no precedents, as though they have precedents, when I call the function from the worksheet.
I don't know why this happens, or if the range.precedents property is supposed to behave like this.
How can this be fixed?
After a lot of searching, I encountered this:
When called from an Excel VBA UDF, Range.Precedents returns the range and not its precedents. Is there a workaround?
Apparently, "any call to .Precedents in a call stack that includes a UDF gets handled differntly".
So, what I did was use Range.Formula Like "=[a-zA-Z]" , because it satisfied my simple purpose. It is in no way an ideal alternative to the range.precedents property.
Foe a more detailed explanation, please visit that link.

Excel Visual Basic call function as stand-alone routine

I'll get straight to the point; I'm trying to define a Function in Visual Basic which can simply be called without having to have something on the 'other side of the equation' as it were. Essentially I want to be able to define a routine which can be passed a series of variables and executes a routine based on those variables.
I currently have the following Function defined:
Function ImportData(WebAddress As String, OutputCell As Range)
With ActiveSheet.QueryTables.Add(Connection:= _
"URL;" & WebAddress & _
"bin/excelget.exe?TICKER=msft", _
Destination:=OutputCell)
.BackgroundQuery = True
.TablesOnlyFromHTML = True
.Refresh BackgroundQuery:=False
.SaveData = True
End With
End Function
What I want to be able to do is simply call this Function but not necessarily make something equal to or use this Function to manipulate something. So, for example, I'd like to be able to do something like the following:
Private Sub ExampleButton_Click()
ImportData("http://www.exampleURL.com/examplejsonhtmlformat","A3")
End Sub
When this Function is called, it simply steps through the Function using the variables defined. There is already an output defined in OutputCell so a cell doesn't need to 'equal' the output of this Function.
If anybody has any input, it would be much appreciated.
Thanks.
You want to make it a Sub - it is exactly what you describe: code that can be called but that doesn't return a value. Note that you don't put the parameters of a sub in parentheses when you call it. If you have
Sub myTest(a,b)
Then you call it with
myTest thing1, thing2
And NOT with
myTest(thing1, thing2)
Update based on excellent comments from #hnk and #Ioannis:
It is possible to call a Sub with
Call myTest(thing1, thing2)
But there is a subtlety, which has to do with the difference between passing a variable by value or by reference. When you pass by value, you make a copy: changing the parameter in the program does not change the original. However, when you pass by reference, it becomes possible to change the value of the parameter inside the sub - and that becomes the new value of the parameter after the sub returns. I'd the prototype says you expect the value to be passed by reference:
Sub MyTest(ByRef a)
Then you can override this behavior as follows:
Call with Passing by
MyTest a Reference
MyTest (a) Value
Call MyTest(a) Reference
Call MyTest((a)) Value
In general it is better to be explicit in the function prototype - specify if you want byVal or byRef and if the calling program gets it wrong you get warned bu the compiler. More info at Hidden features of VBA
If you are not at least a little bit confused or at least annoyed at Microsoft after this, you were not paying attention...
afterword it was pointed out by Rory that it is possible to call functions without assigning their return value. So you can have either
X = myFunc(y)
Or
myFunc y
Both will call the function - but note that when you don't expect a return value you don't use parentheses. Oh Microsoft, what were you thinking...

VB.NET: Assign value to variable inside an IF condition?

is there any possibility to assign a value to a variable inside an IF condition in VB.NET?
Something like that:
Dim customer As Customer = Nothing
If IsNothing(customer = GetCustomer(id)) Then
Return False
End If
Thank you
Sorry, no. On the other hand, it would get really confusing, since VB.NET uses the same operator for both assignment and equality.
If a = b Then 'wait, is this an assignment or comparison?!
Instead, just set the variable and compare:
Dim customer As Customer = Nothing
customer = GetCustomer(id)
If IsNothing(customer) Then
Return False
End If
No, I'm fairly sure this is not possible - but be thankful!
This is a "feature" of C-based languages that I would never encourage the use of, because it is probably misused or misinterpreted more often than not.
I think the best approach is to try and express "one thought per line", and resist coding that combines two operations in the same line. Combining an assignment and a comparison in this way usually doesn't achieve much other than making the code more difficult to understand.
VB doesn't do that very well, especially since assignment and comparison both use the = operator. It'd get really confusing. And since VB to some degree is designed to be newbie-friendly (The B in "BASIC" actually stands for "Beginner's"), expressions with multiple side effects are not something the language generally likes to do.
If it's essential that you be able to do it all in one line, then you could make the GetCustomer method assign to a ByRef parameter, and return a boolean saying whether the assigned value was null. But really, it's better to just assign and then compare to null.
There is no builtin support for doing this, but you could use this workaround.
Public Function Assign(Of T)(ByRef destination As T, ByVal value As T) As T
destination = value
Return destination
End Function
And then it could be used like this.
Dim customer As Customer = Nothing
If IsNothing(Assign(customer, GetCustomer(id))) Then
Return False
End If
Thinking out loud because you didn't show Customer or GetCustomer...
Dim myCust As New Customer
If myCust.GetCustomer(someID) Then
End If
Class Customer
Private _customerID As Integer
Const noCustomer As Integer = -1
Public Sub New()
Me._customerID = noCustomer 'no customer
End Sub
Public Function GetCustomer(ByVal id As Integer) As Boolean
'perform required action to get customer
'if successful then set Me._customerID to ID else set it to no customer value
Return Me.HaveCustomer
End Function
Public Function HaveCustomer() As Boolean
If Me._customerID = noCustomer Then Return False Else Return True
End Function
End Class
Yes, it is possible to assign a value to a variable inside an IF condition in VB.NET.
There is even builtin support for doing this, albeit to a limited extend:
If Interlocked.Exchange(customer, GetCustomer(id)) Is Nothing Then
Return False
End If
where the methods within the Interlocked class are intended to be used within a multi-threading environment. By design, the return value of Exchange() is the old value, rather than the new one. Thus, to obtain the rather cryptic equivalent result:
If (customer = Interlocked.Exchange(customer, GetCustomer(id)) And customer Is Nothing Then
return false
End If
All of the people stating that this is an issue as VB uses the "same operator for both assignment and equality"..
I would argue that this is not a good reason as they could simply make slightly different syntax for it... for example:
Dim[Name = Expression]
If a = Dim[qwe = 1 + 2] Then
'qwe = 3
End If
'...
a = 3
If Dim[qwe = a = 1 + 2] Then
'qwe = True
End If