noi_max 02-16-2005, 12:46 PM This is a tutorial/reference for working with functions in Visual Basic 6.
Special thanks to:
OnErr0r
herilane
Deadalus
Dennis DVR
MikeJ
NateO
loquin
Iceplug
Mike_R
lebb
and others for their support and feedback in the making of this tutorial. :)
-----------------------------------------------------------------------
This tutorial is split up into different sections with some quick links to each one.
-----------------------------------------------------------------------
Introduction (http://www.xtremevbtalk.com/showpost.php?p=944964&postcount=2)
The difference between a Sub and a Function (http://www.xtremevbtalk.com/showpost.php?p=944966&postcount=3)
Calling a Function (http://www.xtremevbtalk.com/showpost.php?p=944969&postcount=4)
Arguments/Parameters - the basics (http://www.xtremevbtalk.com/showpost.php?p=944971&postcount=5)
ByVal vs. ByRef (http://www.xtremevbtalk.com/showpost.php?p=944973&postcount=6)
Optional Parameters (http://www.xtremevbtalk.com/showpost.php?p=944977&postcount=7)
Passing Controls and Objects (http://www.xtremevbtalk.com/showpost.php?p=944984&postcount=8)
Passing/returning Arrays - by herilane (http://www.xtremevbtalk.com/showpost.php?p=944986&postcount=9)
Enums and User Defined Types (http://www.xtremevbtalk.com/showpost.php?p=944991&postcount=10)
ParamArray (http://www.xtremevbtalk.com/showpost.php?p=944997&postcount=11)
Scope (http://www.xtremevbtalk.com/showpost.php?p=945001&postcount=12)
noi_max 02-16-2005, 12:49 PM Coming from a purely Visual Basic background, I got used to the fact that everything I was doing at first was related to events. Click a button and something happens. While that's still true, it isn't very practical to have all your code in your controls' event handlers (such as cmdButton_Click).
Also, it became clear to me that some bits of code could be re-used by different events or procedures.
Take one of the built in conversions for example:
Dim lngNumber As Long
LngNumber = CLng(Text1.Text)
This is an example of a function. You don’t need to write the code for the CLng() function, its already done. You just need to use it when it’s needed. This comes in real handy when it’s time to write your own functions. You write the code to do something once, and then just call it when it’s needed. How convenient is that?
After this small tutorial is through, you should be able to create your own functions without any problems and hopefully make your programming life easier. I’ll try to cover some things that can or will trip you up if you’re in unfamiliar territory. I’ll also try to make this interesting for those just looking for a refresher.
noi_max 02-16-2005, 12:51 PM There is one major difference between a sub procedure and a function. A function returns a value. Some programming languages use functions exclusively, always with a return value unless specified as “void”. Visual Basic, however has the sub procedure, which suggests no return value at all.
Private Sub myProcedure()
'Code for myProcedure
End Sub
This will basically run whatever code is inside the procedure, producing no return value. Here’s an example of a function that does return a value.
Private Function GetCircumferenceFeet(ByVal DiameterInInches As Single) As Single
Const PI = 3.14159
Const IPF As Integer = 12 'twelve inches per foot
GetCircumferenceFeet = (DiameterInInches * PI) / IPF
End Function
In that example I’ve included a parameter in the function (ByVal DiameterInInches As Single) and passed an argument, which I will cover a little later on. Notice the data type declaration after the parenthesis. The return is a Single data type. This becomes important later when calling the function.
Private Function GetCircumferenceFeet(ByVal DiameterInInches As Single) As Single
You return the value by assigning it to the function name.
GetCircumferenceFeet = (DiameterInInches * PI) / IPF
This will be covered in more detail in the next article when we talk about the different ways a function can be called.
Stay tuned. :)
noi_max 02-16-2005, 12:55 PM Calling a function can be done in a few different ways. When you're storing the return value of the function the format looks a little like this:
CircumferenceFeet = GetCircumferenceFeet(myDiameter)
In this example, we’re assigning the return value of the function to a variable, passing to it the one required argument. Here’s the whole thing at a glance.
Private Sub Command1_Click()
Dim CircumferenceFeet As Single, myDiameter As Single
myDiameter = 48 '48 inches
CircumferenceFeet = GetCircumferenceFeet(myDiameter)
MsgBox CircumferenceFeet
End Sub
Private Function GetCircumferenceFeet(ByVal DiameterInInches As Single) As Single
Const PI = 3.14159
Const IPF As Integer = 12 'twelve inches per foot
GetCircumferenceFeet = (DiameterInInches * PI) / IPF
End Function
There may be cases where you’re not assigning the return value. In this instance, you would simply call the function, passing the argument like so:
GetCircumferenceFeet myDiameter
This is the same as it’s done in a regular sub procedure. You’ll notice that when you are getting a return value, it’s the same syntax as using VB’s built in functions. Remember the CLng() conversion function from earlier on? Same thing.
lngNumber = CLng(Text1.Text)
What if you have no parameters in your function?
Private Function GetCircumferenceFeet() As Single
Const PI = 3.14159
Const IPF As Integer = 12 'twelve inches per foot
Dim DiameterInInches As Single
DiameterInInches = 48
GetCircumferenceFeet = (DiameterInInches * PI) / IPF
End Function
Notice the parenthesis are empty. This means there are no required arguments to pass to the function, so calling it can be done in either of these two ways:
SurfaceFeet = GetCircumferenceFeet()
SurfaceFeet = GetCircumferenceFeet
The parenthesis when calling the function are not required if there are no arguments, so either choice will do, however the first method shown is clearer. The parenthesis suggest that the variable is being assigned to a function rather than to another variable.
For functions that have no return value, calling them can be done with the Call statement, or by simply using the function name.
Here’s an example of a function that has two parameters but doesn't use a return value.
Private Function ChangeBackColor(ByVal lColor As Long, ByRef obj As Object)
obj.BackColor = lColor
End Function
And here’s an example of calling the function:
Call ChangeBackColor(vbRed, Me)
ChangeBackColor vbRed, Me
Notice the required parenthesis when using the Call statement.
More on using brackets here:
http://www.xtremevbtalk.com/showthread.php?t=15728
Parameters are a very important part of a function as are the arguments you pass. The next few articles will be devoted to function parameters and the passing of arguments to a function. Hope you’re still with me! ;)
noi_max 02-16-2005, 12:59 PM When you’re creating a function it’s important to keep in mind the type of arguments it will receive. You can build your function with some parameters that will receive these arguments when the function is called.
Let’s say for example you needed a function to quickly tell you if two numbers added together were more than one hundred. It’s possible. ;) Now were working with two possible parameters. Number1 and Number2. How about the return type? Well, this kind of fits in a Yes/No, True False category so I’ll use a Boolean return value.
Here’s the prototype:
Private Function MoreThan100(ByVal Num1 As Integer, ByVal Num2 As Integer) As Boolean
Probably the most important things to point out here are the explicit declarations of data types (ByVal Num1 As Integer, ByVal Num2 As Integer) and the commas between the parameters. If the data types are not called out in this fashion, VB will assume a Variant data type, which could get you into trouble.
Here’s the entire function…
Private Function MoreThan100(ByVal num1 As Integer, ByVal num2 As Integer) As Boolean
If num1 + num2 > 100 Then
MoreThan100 = True
Else
MoreThan100 = False
End If
End Function
…and here’s an example of calling the function
Private Sub Command1_Click()
Dim myNum1 As Integer, myNum2 As Integer, blnReturn As Boolean
myNum1 = 50
myNum2 = 45
blnReturn = MoreThan100(myNum1, myNum2)
MsgBox blnReturn
End Sub
When you’re evaluating the return value of a function, you would put the arguments passed in parenthesis, just as you would in assigning the return value.
Here’s an alternate example of calling the same function.
If MoreThan100(myNum1, myNum2) = True Then
MsgBox "More than 100!"
Else
MsgBox "Less than 100"
End If
Using parameters in a function is very handy and can help keep the amount of global variables you have down. Do the variables passed to the function as arguments change at all? We’ll find out in the next topic when we discuss the ByVal and ByRef keywords. :)
noi_max 02-16-2005, 01:02 PM Anatomy of a Function - Parameters – ByVal and ByRef
When you pass a variable as an argument to a sub or function you can pass it in two different ways. One way uses the address of the variable in memory, thus allowing us to work with the variable directly. This method is known as ByRef.
Consider a small function to increment a number:
Private Function IncrementNumber(num As Integer) As Integer
num = num + 1
IncrementNumber = num
End Function
ByRef is assumed as a default. But, to be more explicit, you should use the keyword in your parameter. Here’s what it would look like:
Private Function IncrementNumber(ByRef num As Integer) As Integer
The ByVal keyword by contrast will use the actual value of the variable. This allows us to manipulate a copy of it, without altering the original variable. In short, we'll use just the value and leave the variable unchanged. This is better illustrated by using these functions. The first one passes the variable ByRef, thus changing the actual variable:
Private Sub Command1_Click()
Dim myNumber As Integer, myResult As Integer
myNumber = 2 'Initialize variables.
myResult = 0
myResult = IncrementNumber(myNumber)
'Display results.
MsgBox "myNumber is now " & myNumber & vbCrLf & _
"myResult is now " & myResult
End Sub
Private Function IncrementNumber(ByRef num As Integer) As Integer
num = num + 1
IncrementNumber = num
End Function
If you run this, you'll notice that the original variable myNumber has been changed to 3. Now let’s look at the same function using the ByVal keyword.
Private Sub Command1_Click()
Dim myNumber As Integer, myResult As Integer
myNumber = 2 'Initialize variables.
myResult = 0
myResult = IncrementNumber(myNumber)
'Display results.
MsgBox "myNumber is now " & myNumber & vbCrLf & _
"myResult is now " & myResult
End Sub
Private Function IncrementNumber(ByVal num As Integer) As Integer
num = num + 1
IncrementNumber = num
End Function
This is the exact same function but with the ByVal keyword used. If you run this, you’ll see that the variable myNumber remains at 2. This is because a copy of that variable's address was used and not the original.
I hope that demonstration helped to sort it all out.
noi_max 02-16-2005, 01:08 PM It's sometimes useful to have paramters that are not required; meaning that if an argument isn't passed, a default value is assumed.
Optional parameters come with a few rules:
• You need to declare a default value.
• The default value must be a Constant expression. (not a variable)
• Optional parameters have to come at the end of your parameter list. In other words you cannot have any required parameters listed after your optional ones.
• You cannot use optional parameters if you're using ParamArray
An example would be useful here. Here's a simple function that changes the .BackColor property of a form:
Private Function ChangeFormBackColor(ByRef frm As Form, Optional ByVal lCOlor As Long = 0) As Boolean
frm.BackColor = lCOlor
ChangeFormBackColor = True
End Function
And here we'll call the function:
Dim blnReturn As Boolean
blnReturn = ChangeFormBackColor(Me, vbRed)
In the second example, I've added a conditional statement based on the optional parameter.
Here, we're drawing a simple blue box using the .Line method. The optional boolean parameter tells the function whether or not the box we draw will be filled with color:
Private Function DrawBlueBox(ByRef ctrl As Control, ByVal X As Long, ByVal Y As Long, _
ByVal lWidth As Long, ByVal lHeight As Long, _
Optional ByVal Fill As Boolean = False) As Boolean
On Error GoTo ErrHandler
If Fill Then
ctrl.Line (X, Y)-(lWidth, lHeight), vbBlue, BF
Else
ctrl.Line (X, Y)-(lWidth, lHeight), vbBlue, B
End If
DrawBlueBox = True
Exit Function
ErrHandler:
DrawBlueBox = False
End Function
I've added an error handler, which will give the function a False return value if there's an error.
Here's an example of calling this function:
Dim blnReturn As Boolean
lReturn = DrawBlueBox(Me, 100, 100, 1000, 1000, True)
If blnReturn = False Then
MsgBox "Unable to render box"
End If
The error handling in the previous function was provided in case the control passed to it did not support the .Line method.
The next topic will cover using controls and objects like this as parameters in a function, and what to watch out for.
noi_max 02-16-2005, 01:14 PM This is something that you'll need to do from time to time. Create a function that uses objects or controls for parameters.
Early/Late binding and Intellisense
For purposes of this tutorial, we will consider parameters that are explicitly declared as Early Bound. VB knows in advance what we're working with and Intellisense for the properties and methods of the control or object is available.
For example:
Private Function ChangeFormBackColor(ByVal frm As Form) As Boolean
By contrast, parameters declared as Control or Object will be considered Late Bound. VB does not know in advance what we're working with and will have to determine what it's getting at run-time.
Private Function ChangeBackColor(ByVal Ctrl As Control) As Boolean
Controls and objects by nature are one in the same, but for sake of simplicity, I will begin with specific controls first, then move on to control arrays and then finally objects. :)
Controls
Let's begin with a simple function to get the width of a textbox.
Private Function GetWidthProperty(ByVal txt As TextBox) As Single
GetWidthProperty = txt.Width
End Function
Now we'll call the function and pass to it a regular text box
Private Sub Command1_Click()
Dim sWidth As Single
sWidth = GetWidthProperty(Text1)
Debug.Print sWidth
End Sub
We've used early binding in this example by declaring txt As TextBox. Using the dot notation allowed us to see the properties and methods of the text box via Intellisense.
Now, let's try re-writing the function to get the width property of any control that supports it.
Private Function GetWidthProperty(ByVal ctrl As Control) As Single
On Error GoTo errhandler
GetWidthProperty = ctrl.Width
Exit Function
errhandler:
Debug.Print "Control does not support the .Width property"
End Function
Now we're late binding. If you write this function in VB, you'll notice that the Intellisense feature does not work in this case. This is because VB has no idea what control we'll be using in the function until run-time. I've also added error handling in case the control we pass to it does not support the .Width property. Calling the function remains the same as before:
Private Sub Command1_Click()
Dim sWidth As Long
sWidth = GetWidthProperty(Text1)
Debug.Print sWidth
End Sub
However, since our new function takes it's argument As Control, we can pass a picturebox, or command button.. etc. If the control has a width property, the function will return the appropriate value.
Control Arrays
If you need to pass a control array as an argument to a function, you need to define your parameters As Object (Late binding). It is reccomended that error handling be used like in the previous examples in case a control array you're passing doesn't support the properties or methods you're working with.
Here's an example of a function that is meant to work with text boxes specifically
Private Function ClearTextBox(ByVal ctrl As Object) As Boolean
On Error GoTo errHandler
Dim i As Integer
'Loop through the control array and clear each text box.
For i = ctrl.Lbound To ctrl.Ubound
ctrl(i).Text = vbNullString
Next i
Exit Function
errHandler:
'If a single textbox is passed (NOT a control array),
'the .Lbound and .Ubound properties are not supported.
If TypeOf ctrl Is TextBox Then
ctrl.Text = vbNullString
Else
Debug.Print "Control passed is not a text box"
'Return true if function failed
ClearTextBox = True
End If
End Function
Again, I had no Intellisense to guide me here due to declaring the parameter As Object (Late binding). Here's an example of calling the function
Private Sub Command1_Click()
Dim blnResult As Boolean
blnResult = ClearTextBox(Text1)
End Sub
Objects
Once you understand how passing controls to a function works you'll find that objects are very similar. Here I've made up a class module (clsObject) and a method called EndProcess. If you refer to your object explicitly (using early binding as shown here) the dot notation will show any exposed properties or methods.
Private Function DestroyObject(ByVal obj As clsObject) As Boolean
obj.EndProcess
Set obj = Nothing
DestroyObject = True
End Function
Here's an example of returning an object
Private Function InitObject(ByVal strProperty1 As String, _
ByVal strProperty2 As String) As clsObject
Dim NewInstance As New clsObject
NewInstance.strProperty1 = strProperty1
NewInstance.strProperty2 = strProperty2
'Return the new object
Set InitObject = NewInstance
Set NewInstance = Nothing
End Function
Private Sub Command1_Click()
Dim myObject As clsObject
Set myObject = InitObject("Hello", "World")
Debug.Print myObject.strProperty1
Debug.Print myObject.strProperty2
End Sub
There are some nuances when it comes to passing object variables ByVal vs ByRef that you may want to know about. At first glance their behavior is identical, but when it comes to setting object variables to new instances of an object there is a difference. More on ByVal vs ByRef in regards to objects here:
http://www.windowsdevcenter.com/pub/a/oreilly/windows/ron/objects.html
(Thanks to Deadalus for providing the link)
Creating your own objects with their own properties and methods is beyond the scope of this tutorial, however I felt that using them as parameters in a function needed mentioning.
Now that we've moved variables and objects around it's time to move on to arrays :)
noi_max 02-16-2005, 01:16 PM This is where I could put my feet up and relax as our own herilane has already done a terrific job explaining how to pass and return arrays to/from a function.
She was kind enough to offer a link to this article and save me some work. Very much appreciated!
http://www.thecodenet.com/articles.php?id=6
:D
noi_max 02-16-2005, 01:22 PM Enums:
This is a personal favorite. One thing I enjoy about working in the Visual Basic IDE is the Intellisense feature, and one place where it really shines is when using Enums as parameters in a function.
Let's take a look at the ChangeFormBackColor function once again; this time with an Enum in the parameter list.
Option Explicit
Private Enum eColors
RED = vbRed
BLUE = vbBlue
GREEN = vbGreen
BLACK = vbBlack
End Enum
Private Sub Command1_Click()
Dim blnReturn As Boolean
blnReturn = ChangeFormBackColor(Me, BLUE)
End Sub
Private Function ChangeFormBackColor(ByRef frm As Form, Optional lColor As eColors = BLACK) As Boolean
frm.BackColor = lColor
ChangeFormBackColor = True
End Function
One thing that's difficult to illustrate is how using an Enum in your parameter list really helps you. Of course the main objective is to enumerate the argument choices, in this case the four different color choices with black as a default. But it's better seen when it comes time to call the function. This pic attached will provide a better illustration:
(see below for attachment)
Enums are very useful if your function parameter expects a certain constant expression as an argument form a list of possible values, and as you can see, when it comes time to call the function the list is conveiniently displayed.
This works for returning an Enum as well. Here's an example of reversing a direction based on four values
Option Explicit
Private Enum eDirection
NORTH = 0
SOUTH = 1
EAST = 2
WEST = 3
End Enum
Private Sub Command1_Click()
Dim myDirection As Integer
myDirection = ReverseDirection(NORTH)
End Sub
Private Function ReverseDirection(Direction As eDirection) As eDirection
Select Case Direction
Case NORTH
ReverseDirection = SOUTH
Case SOUTH
ReverseDirection = NORTH
Case EAST
ReverseDirection = WEST
Case WEST
ReverseDirection = EAST
End Select
End Function
When assigning the return value to the function name, Intellisense once again provides us with the list of choices from the Enum.
User Defined Types
User defined types used as parameters is very similar to using objects or controls. Like objects and controls, user defined types are passed ByRef. Intellisense will provide you with a list of 'members' using dot notation.
Here's an example of a sub routine that initializes a baseball score.
Option Explicit
Private Type tBaseballScore
Runs As Integer
Innings As Integer
Outs As Integer
End Type
Private Sub Command1_Click()
Dim BaseballScore As tBaseballScore
InitScore BaseballScore
Debug.Print BaseballScore.Innings
End Sub
Private Sub InitScore(ByRef Score As tBaseballScore)
With Score
.Runs = 0
.Outs = 0
.Innings = 1
End With
End Sub
Here the data type in the parameter is our own custom type we've created.
Here's the same InitScore routine except now we're returning the user type in a function.
Private Sub Command2_Click()
Dim BaseballScore As tBaseballScore
BaseballScore = InitScore2(0, 1, 0)
Debug.Print BaseballScore.Innings
End Sub
Private Function InitScore2(ByVal intRuns As Integer, ByVal intInnings As Integer, _
ByVal intOuts As Integer) As tBaseballScore
'Return user type using the function name.
'Intellisense will work here to list your members.
With InitScore2
.Runs = intRuns
.Innings = intInnings
.Outs = intOuts
End With
End Function
As you can see, this isn't much different than working with
controls and objects. :)
noi_max 02-16-2005, 01:26 PM ParamArray is a strange one. It is basically an array of optional parameters. The parameters are a Variant data type.
Here are some rules to ParamArray
• Cannot be used with other Optional parameters.
• Cannot specify ByVal or Byref; arguments are passed ByRef only.
• Must be put at the end of your parameter list.
Paramarray is useful for an arbitrary number of arguments. Say for example you had a function that added numbers, but you weren’t sure how many numbers you needed to add together in advance.
Private Function AddNumbers(ParamArray NumbersToAdd()) As Integer
Dim i As Integer 'Loop variable
Dim Count As Integer
'Loop through the ParamArray and add to the 'Count'
For i = LBound(NumbersToAdd) To UBound(NumbersToAdd)
Count = Count + NumbersToAdd(i)
Next i
'Return the final result
AddNumbers = Count
End Function
Here's an example of calling the function:
Private Sub Command1_Click()
Dim intResult As Integer
intResult = AddNumbers(5, 5, 6, 5)
Debug.Print intResult '21
End Sub
In this example, I could have passed any number of arguments(numbers) to add together and the function would have returned the correct result.
Of course you should consider what the function is going to do before using ParamArray, as it uses the Variant data type. Had I replaced one of the arguments with a String, I would have a type mismatch error. Also if I had provided a precision number, it would have rounded the number. :whoops:
noi_max 02-16-2005, 01:30 PM This is a quick reference for function scope. It is reccomended that your function's scope is explicitly declared.
Private Function ChangeFormBackColor(ByRef frm As Form, ByVal lColor As Long) As Boolean
Here are the different ways in which a function or sub can be declared:
Private
When your function or sub is declared as Private, it is accessible only to the module in which it was declared, be it a form, module or class module.
Public
When your function or sub is declared as Public, it is accessible from all modules in your project.
Friend
Used in class modules. Declares the function or sub as public to all modules in your project, but not as a public interface to an object. This is useful when making your own ActiveX components. Perhaps a little beyond the scope of this tutorial. ;)
Static
When your function or sub is declared as Static, all of the variables declared in the function or sub will retain their values between calls.
Going back to the AddNumbers example:
Static Function AddNumbers(ParamArray NumbersToAdd()) As Integer
Dim i As Integer 'Local variables
Dim Count As Integer
'Loop through the ParamArray and add to the 'Count'
For i = LBound(NumbersToAdd) To UBound(NumbersToAdd)
Count = Count + NumbersToAdd(i)
Next i
'Return the final result
AddNumbers = Count
End Function
Since the vaiable 'Count' is not initialized within the function, it's value is preserved after every call.
By contrast, the variable 'i' is initialized in the For/Next loop.
For i = LBound(NumbersToAdd) To UBound(NumbersToAdd)
That pretty much wraps up this little reference on scope. For more on scope, refer to the .. :)
|