I have a VBA function called getYear that is initialized as follows :
Public Function getYear(ByVal year As Date, Optional ByVal month As Date) As Long
Then, inside the function I have :
msg = MsgBox(IsEmpty(month), vbOKOnly) So I know whether or not the month has been input in the function.
The only thing is whether I have a function call such as : getYear(currentYear) or getYear(currentYear, currentMonth) isEmpty is always returning False
If I am understanding this correctly when I call getYear(currentYear) isEmpty() should return True, but it is not...
I gather that is from my function initialization, but I don't know what else to do.
Any help is appreciated!
From MSDN:
IsEmpty only returns meaningful information for variants
You can also use IsMissing() to check if an optional argument was passed to the function, but this works only on Variants as well. You might want to check if the optional argument has a value equal to his default value, which is zero for dates.
You can create a default value for "month" that is zero or negative, or out of the range of possible entries. Then instead of using IsMissing or IsEmpty, you can just test for that default value. If month = the default, you know that parameter was not passed to the function.
Related
I need to provide my RDL files to teammates so that they can make minor customizations for each client. One of the ways I thought I might improve the efficiency is if I could build more complex expressions inside of custom code functions so that they can input some simple arguments and have the function handle the "heavy lifting" of adjusting the expression accordingly.
This is a very simple example, and not one I would take this step for, but I thought it the easiest place to start figuring out if I can make this work. For instance, in a tablix we want a count returned based on a value where the value is customized per client (and isn't a parameter).
=Count(iif(trim(Fields!Category.Value)="OPTIONA",1,nothing))
Is there a way I could build a function so that my teammates would just need to enter the following?
=Code.CustomFunction("OPTIONA")
My understanding is that the custom code in Report Builder can't query datasets, or at least not in the way that an expression within a tablix would be able to. I've built custom functions that work with the results of an expression added as the argument, but I can't seem to wrap my head around if there's a way to construct an expression within a custom function and pass it back to the expression.
For instance:
Public Function CustomFunction(field As String) As String
Dim customExpression As String = "Count(iif(trim(Fields!Category.Value)=" & field & ",1,nothing))"
Return customExpression
End Function
As expected, this just returns a string with the text of the expression, but not an executed expression. Is what I'm trying to achieve possible with Report Builder?
Or, as an alternative approach, can I somehow place variables at the beginning of an expression that are used later so that anyone else working on the expression just needs to worry about the beginning? Essentially create multiple custom functions and call them later on?
=Code.CustomFunction("OPTIONA",Count(iif(trim(Fields!Category.Value)=Code.CustFunc01(),1,nothing)))
Honestly not sure how I would go about building the functions themselves from here.
You can use a function instead of the related field. The function takes the field string as an argument and the filter string for which will increase the counter. Finally it returns the original field value
Private Dim Counter As Integer
Public Function SetCounter( Expr As String, Filter As String) As String
If Expr = Filter Then Counter = Counter + 1
Return Expr
End Function
Public Function GetCounter( ) As Integer
Return Counter
End Function
For the field value you can use the following expression (yellow color)
=Code.SetCounter( Fields!MyString.Value,"OPTION A")
To get the counter value you can either use the following expression calling a function (orange color)
= Code.GetCounter()
Or make the variable public and use Code.Counter as the expression
I keep getting an error when I try to format this number. I've done it in VBA before and I tried to change the SOPID to a variant, a string, and an integer.
Dim SOPID As Integer
SOPID = DMax("file_id", "tblSOP") + 1
'Output test data
MsgBox (format(SOPID, "000"))
I have no idea what I am doing wrong.
Assuming the code was pasted directly from your IDE, the casing of format is suspicious; that would be Format, unless there's a format variable or function that's in-scope, ...and that's being invoked here.
Look for a public (could be implicitly Public, or if it's in the same module then it could also be Private) format function procedure that expects an array argument: that's very likely what's being invoked here.
Rubberduck (free, open-source; I manage this project) can help you easily identify exactly what's being invoked and an inspection would tell you about any shadowed declarations, but to be sure you can fully-qualify the function call to avoid inadvertently invoking another function that's in scope:
MsgBox VBA.Strings.Format$(SOPID, "000")
Note that there are no parentheses around the argument list of a parameterized procedure call in VBA; the parentheses you have there are surrounding the first argument and making the expression be evaluated as a value that is then passed to the invoked function: this isn't necessary.
Also note the $: the VBA.Strings.Format function takes and returns a Variant; VBA.Strings.Format$ takes and returns a String. If you aren't dealing with any Null values (an Integer couldn't be Null), consider using the String-returning alias.
i have a Function with1 Parameter and 10 optional Parameters.
The optional Parameters are all nothing.
Now i want to check if a optional Parameter had changed his Value or the Function was started with an optional Parameter.
Think of it that the Parameters can have the Value Nothing then the Function returns too.
ty for your help. :D
10 parameters.. that's a bit excessive... and hard to manage.
You would be better passing a class or structure.
Checking the optional parameter is the default value is the usual method for this for determining if it is pre-set or not as in the previous answer.
You would need to pass an object by reference if you need to test if it changed while the routine was running, but if it was originally passed as "Nothing" that will not work.
Without more information on what your usage intent is, it is a little hard to answer this conclusively.
If Parameter1 Is Nothing then
'Parameter1 changed
EndIf
If your other optional parameters is nothing I trust that their reference type is a string.
The following from MSDN to check if an optional argument is present -
A procedure cannot detect at run time whether a given argument has been omitted or the calling code has explicitly supplied the default value. If you need to make this distinction, you can set an unlikely value as the default. The following procedure defines the optional parameter office, and tests for its default value, QJZ, to see if it has been omitted in the call:
Sub notify(ByVal company As String, Optional ByVal office As String = "QJZ")
If office = "QJZ" Then
Debug.WriteLine("office not supplied -- using Headquarters")
office = "Headquarters"
End If
' Insert code to notify headquarters or specified office.
End Sub
Link can be found HERE
I am debugging a project right now and it has a function with the following signature:
Public Function changeRemoteDirectory(ByVal newDirectory As String, Optional ByVal Direction As Boolean = True) As Boolean
MsgBox(direction)
'rest of code
End Function
I was trying to figure out what was causing this function to return a value of False when I knew that it should return True given the input that I was supplying, so I put MsgBox(direction) into the Function to see what the value of direction was when I called the Function. I called the function like this, yet I received a MsgBox that showed the value of direction to be False:
changeRemoteDirectory("/internal")
The first parameter works just fine and the code that requires it to execute works correctly, but I cannot figure out why Direction has a value of False in a situation where, I believe, it should have its default value of True. I'm not totally opposed to rewriting the Function if need be, but can anybody here discern why Direction does not have a value of True when the function changeRemoteDirectory() is called without the second parameter supplied?
It sounds like you're experiencing one of many pains that optional parameters cause.
When the code that calls changeRemoteDirectory is compiled, the optional parameter is injected into the calls just like a const would be replaced at compile time. Changes to the value of an optional parameter will not take effect without recompiling the caller.
See this article for more info.
In general, you should use method overloads instead of optional parameters. You get all of the same functionality without the pain and performance drawbacks.
I have a class like this:
Public Class MyClass
Private _intList As New List(Of Integer)
Private _avg As Decimal
Public Sub Add(ByVal anInt As Integer)
_intList.Add(anInt)
End Sub
Public Property Avg() As Decimal
Get
Dim _sum As Integer = 0
For Each anInt In _intList
_sum += anInt
Next
Avg = If((_intList.Count > 0), _sum / _intList.Count, _avg)
Return _avg
End Get
Set(ByVal value As Decimal)
If _avg <> value Then
_avg = value
Console.WriteLine("Value changed")
End If
End Set
End Property
End Class
The Getter is calculating average and calls Setter to save the value. For some reason I cannot understand, the average is always 0. For example:
Dim c As New Class2()
c.Add(1)
c.Add(2)
c.Add(3)
Console.WriteLine(c.Avg.ToString()) ' This will print 0
Did I do something wrong? What is the cause of this?
Wow, I think you've discovered a very strange behavior of VB: when you are inside the definition of a function, you can return a value either with Return or by using = to "set" the function's value.
Like this:
Function GetInteger() As Integer
GetInteger = 5
End Function
In the above function, the line GetInteger = 5 is basically equivalent to Return 5*.
OK, so you probably already knew that. But here's the weird part, and I had no idea this was the case until testing it just now (admittedly, on Mono, but I am seeing the same behavior you are): apparently this applies to property getters as well. So look at this line:
Avg = If((_intList.Count > 0), _sum / _intList.Count, _avg)
You're actually not calling the property setter there; you're setting the return value for the getter. You can verify this by removing the line Return _avg; suddenly you will see your getter starts returning the actual average.
*Not exactly the same, as you could later set GetInteger to something else without returning immediately whereas using Return ensures the function returns right away.
This is by design and explicitly mentioned in the Visual Basic Language Specification, chapter 9.7.1:
A special local variable, which is implicitly declared in the Get
accessor body's declaration space with the same name as the property,
represents the return value of the property. The local variable has
special name resolution semantics when used in expressions. If the
local variable is used in a context that expects an expression that is
classified as a method group, such as an invocation expression, then
the name resolves to the function rather than to the local variable.
For example:
ReadOnly Property F(i As Integer) As Integer
Get
If i = 0 Then
F = 1 ' Sets the return value.
Else
F = F(i - 1) ' Recursive call.
End If
End Get
End Property
Solve your issue by assigning the _avg field directly. Property getters with side-effects like this is best avoided.
Your setters and getters really should just be returning properties, and not doing the calculations themselves. Try creating a method like calcAvg() that does the average calculations, and on the Add() call that method (which should internally not re-perform the whole average calculation, but simply update it. Let me know if you're not sure how to do that). That calcAvg() method will set the _avg instance variable.
Also, I'm not sure it really makes send to have a setter for the average. An average of numbers is a derived property, not something that should be set by an external user.
MrDanA answer is more correct than what I'm going to give you, however, I believe the reason why you are getting the value of 0 is because you are never setting the variable _avg to anything. After you do your AVG calculation if you do:
_avg = AVG
return _avg
I get a value of 2 when I do this.
Like I said before though... MrDanA's answer is a better way to go.