ByVal vs ByRef VBA - excel

I've tried to attempt something that was answered by JaredPar ByRef vs ByVal Clarification
ByVal in VB.NET means that a copy of the provided value will be sent
to the function. For value types (Integer, Single, etc.) this will
provide a shallow copy of the value. With larger types this can be
inefficient. For reference types though (String, class instances) a
copy of the reference is passed. Because a copy is passed in mutations
to the parameter via = it won't be visible to the calling function.
ByRef in VB.NET means that a reference to the original value will be
sent to the function (1). It's almost like the original value is being
directly used within the function. Operations like = will affect the
original value and be immediately visible in the calling function.
And I've tried to test it with the following code and I can't seem to get it to work use the ByRef to change the value of the cell to 0 if it's 1
Here's my below code that I'm testing with, what am I doing wrong? The value of Range("A1") is still 1
Sub test1()
Dim trythis As Boolean
trythis = False
If (Sheets("TESTING").Range("A1").value = 1) Then
testingRoutine (trythis)
If (trythis) Then
Debug.Print "Value changed to 0"
Sheets("TESTING").Range("A1").value = 0
End If
End If
End Sub
Private Function testingRoutine(ByRef trythis As Boolean)
Debug.Print "Ran Function"
trythis = True
End Function

VB subroutines don't require braces around the argument list. However, if you pass a single argument and you enclose that in braces, you are passing an expression . Expressions cannot be passed by reference. They are evaluated and their result is passed. Therefore you must remove the braces in the call testingRoutine (trythis) and write testingRoutine trythis
Note: if you call a function without using its return value, it must be written as a procedure call (without braces around the argument list). As an example:
myVal = myFunction (trythis) ' trythis will be passed by reference
myFunction (trythis) ' trythis will be seen as an expression
myFunction trythis ' trythis will be passed by reference
myVal = mySub (trythis) ' invalid: mySub is not a function
mySub (trythis) ' trythis will be seen as an expression
mySub trythis ' trythis will be passed by reference
Of course, the problem will be clear immediately when a function or sub has more than one parameter because a comma cannot appear in an expression.

Change this:
testingRoutine (trythis)
to this:
testingRoutine trythis
then try it.
Also, look what happens if I change it to this, where the function is being used as a function (returning something)
Sub test1()
Dim trythis As Boolean
Dim testit As Boolean
trythis = False
If (Sheets("Sheet1").Range("A1").Value = 1) Then
testit = testingRoutine(trythis)
If (trythis) Then
Debug.Print "Value changed to 0"
Sheets("TESTING").Range("A1").Value = 0
End If
End If
End Sub
Private Function testingRoutine(ByRef trythis As Boolean) As Boolean
Debug.Print "Ran Function"
trythis = True
End Function

I would do it this way.
Sub test1()
Dim trythis As Boolean
trythis = False
If (Sheets("TESTING").Range("A1").value = 1) Then
tr = testingRoutine(trythis)
If tr Then
Debug.Print "Value changed to 0"
Sheets("TESTING").Range("A1").value = 0
End If
End If
End Sub
Private Function testingRoutine(ByRef trythis As Boolean)
Debug.Print "Ran Function"
testingRoutine = True
End Function
Because trythis is not a global variable, changing it in the function will do nothing in the sub. It will just define trythis as True in the function and stay in that scope. To get trythis to be affected by the function and read by the sub you have to assign it to a variable within the sub.

i can't remember where i read this, but i know that if you add the bracets () to arguments when calling a sub/function, it is a specific (not an error) way microsoft implented to workaround byref calls (to byval).
example : lets say you call a sub who has ONLY byref, and 2 arguments:
call Sub1 (a), (b)
is a proper way to force Byvalarguments, even if they were initially coded as Byval

Related

Passing values from Function to Subroutine

I have been looking around for an answer to that, but have been unable to locate one that explains it well enough that I understand.
I have no experience with functions, and very little with VBA overall. So if I have a subroutine I am executing and then call a function that I pass parameters into, once that function runs how do I get the result back into the main subroutine to be used?
This is for Access where I am pulling a number from one record set then passing that to a function to be used for an insert to create a new number. I then need that number passed back to the subroutine to be used.
Here you have two alternatives.
Run the code using F8 key to see what happens and make sure to activate the Locals Window so you see how variables values change
1- Return the value directly from a function
Run the DoSomethingReturnFromFunction sub
' Return value from function
Public Sub DoSomethingReturnFromFunction()
' Define a parameter
Dim myParameter As String
myParameter = "SomeValue"
' Call a function and store its value into a variable
Dim myResult As Long
myResult = MyFunction(myParameter)
' Print the result variable
Debug.Print myResult
End Sub
Private Function MyFunction(ByVal myParameter as String) As Long
Dim result As Long
Select Case myParameter
Case "SomeValue"
result = 1
Case Else
result = 2
End Select
' Assign the result value to the function
MyFunction = result
End Function
Result: Debug.Print myResult prints to the inmediate window the value returned from the function
2- Change variable value passed ByRef inside another function
Run the DoSomethingReturnFromByRef sub
' Change variable value passed ByRef inside another function
Public Sub DoSomethingReturnFromByRef()
' Call a function
Dim myByRefParameter As Long
MySub myByRefParameter
' Print the result
Debug.Print myByRefParameter
End Sub
Private Sub MySub(ByRef myByRefParameter As Long)
' Change the value of the variable passed ByRef inside the procedure
myByRefParameter = 1
End Sub
Result: Debug.Print myByRefParameter prints to the inmediate window the value that is stored in the myByRefParameter variable originally declared in the DoSomethingReturnFromByRef procedure
Let me know if it's clear

Why is VBA function giving compile error for collection but not for string

I am trying to pass a collection to a VBA function but I'm getting a compile error I cannot seem to fix.
This is a simplified example that gives the error.
Sub test()
Dim fooString As String
Dim fooCollection As collection
Set fooCollection = New collection
useString (fooString)
useCollection (fooCollection)
End Sub
Public Function useString(foo As String)
MsgBox ("here")
End Function
Public Function useCollection(foo As collection)
MsgBox ("here")
End Function
I'm not seeing what I'm doing differently than what is shown in examples such as the one shown here: How do I sort a collection?
This is the error I'm getting (Compile Error: Argument not optional):
You're getting this dialog because of the implicit "helpful" default member. In the Object Browser navigate to the Collection in the classes pane and in the members you'll see that Item is the default member, designated by the teal icon.
Here's another example. Ask yourself what it will print and say it out loud before running the code.
Public Sub DontWrapIt()
Dim foo As Range
Set foo = Sheet1.Range("A1")
GiveMeARange foo
GiveMeARange (foo)
End Sub
Private Sub GiveMeARange(ByVal param As Variant)
Debug.Print TypeName(param)
End Sub
Wrapping the object with parentheses causes it to be evaluated which causes an implicit invocation of the "helpful" default member... Which requires an Index parameter that isn't supplied - hence, "parameter not optional" .
I don't know why that particular error (as opposed to a more informative error) is given, but when you call a sub or a function as a sub (not using the return value) you shouldn't have parentheses around the arguments. The following works:
Sub test()
Dim fooString As String
Dim fooCollection As collection
Set fooCollection = New collection
useString fooString
useCollection fooCollection
End Sub
Public Function useString(foo As String)
MsgBox "here"
End Function
Public Function useCollection(foo As collection)
MsgBox "here"
End Function
But -- if your functions don't really return anything, perhaps you could just define them as subs to begin with. In VBA, there really isn't any point in making something which in a language like C would be a void function.
Is just a matter of calling the other sub..
If it doesn't have any parameters, you can call it by name only:
useCollection or Call useCollection
If it does have parameters, then you either call it without parentheses, or you use Call:
useCollection parameter or Call useCollection(parameter)
Sub test()
Dim fooString As String
Dim fooCollection As Collection
Set fooCollection = New Collection
useString (fooString)
fooCollection.Add "test message", "test"
useCollection fooCollection
Call useCollection(fooCollection)
End Sub
Public Function useString(foo As String)
MsgBox ("here")
End Function
Public Function useCollection(foo As Collection)
MsgBox foo(1)
End Function
See some more info about the Call statement:
You are not required to use the Call keyword when calling a procedure. However, if you use the Call keyword to call a procedure that requires arguments, argumentlist must be enclosed in parentheses. If you omit the Call keyword, you also must omit the parentheses around argumentlist. If you use either Call syntax to call any intrinsic or user-defined function, the function's return value is discarded
This works:
Sub test()
Dim fooString As String
Dim fooCollection As VBA.collection
Set fooCollection = New VBA.collection
fooCollection.Add "bar", "bar"
useString (fooString)
useCollection fooCollection
End Sub
Public Function useString(foo As String)
MsgBox ("useString")
End Function
Public Function useCollection(ByRef foo As VBA.collection)
MsgBox ("useCollection")
End Function

Excel: Visual Basic: range as function input doesn't work

I'm starting with VB in Excel; so far I can't figure the following;
I've got the following function:
Function testGetRange(myRange As Range)
Dim weekStart As Integer
Dim weekEnd As Integer
weekStart = myRange(1).Value
weekEnd = myRange(2).Value
End Function
If I try to execute it like that:
Sub CreationRapport()
Dim testRange1 As Range
Set testRange1 = Range("A5:B5")
testGetRange (testRange1)
End Sub
I've got an error like "object needed" (sorry the error message is in French: "objet requis"), stopping right when I try to execute the function.
So: the range is created, the function takes a range as input; don't know why this doesn't work...
You are calling a function and the parantheses signify that you want the function to return something:
testGetRange (testRange1)
But your function doesn't return anything. You can fix this by adding this to testGetRange:
testGetRange ="My return output"
...And you don't put the output anywhere. You can fix this by changing in CreationRapport:
MyOutput = testGetRange (testRange1)
msgbox MyOutput
When you call a function but don't want a return value you need to either leave off the parenthesis
Sub CreationRapport()
Dim testRange1 As Range
Set testRange1 = Range("A5:B5")
testGetRange testRange1
End Sub
Or use call
Sub CreationRapport()
Dim testRange1 As Range
Set testRange1 = Range("A5:B5")
Call testGetRange (testRange1)
End Sub
For the why you can see how VBA handles transferring control to a sub or function here on MSDN
You are not required to use the Call keyword when calling a procedure.
However, if you use the Call keyword to call a procedure that requires
arguments, argumentlist must be enclosed in parentheses. If you use
either Call syntax to call any intrinsic or user-defined function, the
function's return value is discarded.
OK, so after testing the different answers, this worked:
Function testGetRange(myRange As Range) As String
Dim weekStart As String
Dim weekEnd As String
weekStart = myRange(1)
weekEnd = myRange(2)
testGetRange = weekStart
End Function
And in the Sub:
Sub CreationRapport()
Dim myOutput As String
Dim testRange1 As Range
Set testRange1 = Range("A5:B5")
myOutput = testGetRange(testRange1)
MsgBox myOutput
End Sub
The MsgBox is not mandatory, but the part myOutput = testGetRange(testRange1) is!
So, as advised by Doug, need to work more on the VB to see why it is. Thank you all :)

Type mismatch when trying to assign return value to variable (VBA Excel)

Long time listener, first time caller.
I am having an issue with VBA in Excel 2010. I am trying to compare various fields on a user form to determine if they are empty. I will then highlight red, and set focus on the first one on the list.
To do this, I have created functions for each type that can be blank/not selected. They are all declared AS BOOLEAN. The idea was to do the function call to highlight, pass the function calls with the appropriate sub functions, and use the results of those in the highlight function:
function Blah(DoThis01(Me.Object1), DoThis02(Me.Object2), ...) As Boolean
That gave me nojoy; the values always came back false. So I created three boolean variables to assign the values of those functions, and it started givinng me byRef errors. After declaring each variable rather than all on one line, that solved the byref, but now I get a type mismatch:
Dim foo As Boolean
foo = DoThis01(Me.Object1)
if Blah(foo, bar, ..)
It doesn't like that center line; that is where I am running into the error.
Below is the code snippet I am working with, along with an example of the functions I am trying to pass. I can't figure out why my As Boolean functions aren't compatible with an As Boolean variable. Also, the functions to determine whether or not the fields are filled are giving me some issues as well, not registering the correct values. Google had lots of answers, but none that worked for me so far. I appreciate any insight you can offer.
Thanks,
J
PS I realize some of the logic may be a bit off when triggering the message. That is ok, because I am not getting the right values anyways. I will clean it up if I can get the rest working.
Working Code:
Save Button:
Dim emptyRow As Long
Dim isDuplicate As Boolean, nameSelect As Boolean, comboSelect As Boolean, listSelect As Boolean
Dim listLength As Integer
Dim blah As Integer
' check for empty on form
nameSelect = IsSelectedName(Me.SignalNameTxtBox)
comboSelect = IsSelectedCombo(Me.ComboBox1)
listSelect = IsSelectedList(Me.ListBox1)
If HighlightEmpty(nameSelect, comboSelect, listSelect) Then
blah = MsgBox("Empty fields required!", vbOKOnly)
Exit Sub
End If
...More stuff
To Highlight Empty Form Objects:
Function HighlightEmpty(nameSelect As Boolean, comboSelect As Boolean, listSelect As Boolean) As Boolean
' Highlight empty fields
If Not nameSelect Then Enter_New_Form.SignalNameTxtBox.BackColor = RGB(255, 0, 0)
If Not comboSelect Then Enter_New_Form.ComboBox1.BackColor = RGB(255, 0, 0)
If Not listSelect Then Enter_New_Form.ListBox1.BackColor = RGB(255, 0, 0)
' Set focus to first empty field on form
If Not nameSelect Then
Enter_New_Form.SignalNameTxtBox.SetFocus
ElseIf Not comboSelect Then
Enter_New_Form.ComboBox1.SetFocus
ElseIf Not listSelect Then
Enter_New_Form.ListBox1.SetFocus
End If
' Return boolean to trigger message
HighlightEmpty = nameSelect Or comboSelect Or Not listSelect
End Function
My functions to determine if empty:
Function IsSelectedList(lst As ListBox) As Boolean
Dim I As Integer
For I = 0 To lst.ListCount - 1
IsSelectedList = lst.Selected(I)
If IsSelectedList Then Exit Function
Next I
End Function
Function IsSelectedCombo(cbo As ComboBox) As Boolean
If cbo.Value = "" Then IsSelectedCombo = False
End Function
Function IsSelectedName(nme As TextBox) As Boolean
If nme.Value = "" Then IsSelectedName = False
End Function
Your functions will always return false unless otherwise declared as true. Add an else statement like this:
Function IsSelectedName(nme As TextBox) As Boolean
If nme.Value = "" Then
IsSelectedName = False
Else
IsSelectedName = True
End If
End Function
As #Timwilliams pointed out you can reduce this to one line since nme.Value = "" will evaluate to True or False. You might find however, that you need to test if the cell is empty in other ways such as isBlank() and isEmpty(). In this case use the previous example.
Function IsSelectedName(nme As TextBox) As Boolean
IsSelectedName = Len(nme.Value) > 0
End Function
EDIT
To check the value just pass the text string to the function rather than the textbox object. Try This:
Private Sub CommandButton1_Click()
MsgBox (IsSelectedName(Me.SignalNameTxtBox.Text))
End Sub
Function IsSelectedName(nme As String) As Boolean
IsSelectedName = Len(nme) > 0
End Function

VBA Excel: not statement & formula

Is there a way of combining a not statement with a function in the following way:
I want it to work like this example
MsgBox not False
which returns True.
But if I add a function instead of the boolean statement
MsgBox not myFunction()
then it returns a mismatch error.
myFunction() is supposed to return True or False
Any suggestions?
You will need to give us more code, as the following works:
Public Sub test()
MsgBox Not myfunction()
End Sub
Function myfunction() As Boolean
myfunction = False
End Function
Simply
MsgBox Not Application.Evaluate(myFunction)
what is myFunction defined as? if its not :
Function myFunction(...) As Boolean
'...
End Function
that would be why, 'not' operator is reserved for Boolean (true/false)
if your function is like this and only recieves 1 and 0:
Function myFunction(...) As Integer
add this to the call :
MsgBox not CBool(myFunction(...))
or if you are actually trying to compare text:
Function myFunction(...) As String
then you will need:
EDIT: not string.compare, (StrComp(Str1, Str2, vbTextCompare) = 0)
hope that helps :)

Resources