A variable's scope is the code that can "see" or access that variable. It determines whether a piece of code can read the variable's value and give it a new value.
In this lesson you learn what scope is; why restricting scope is a good thing; and how to determine a variable's scope.
A Visual Basic class (and note that Form types are classes, too) contains three main kinds of scope: class scope, method scope, and block scope. (If you have trouble remembering what a class is, review Lesson 9's section "Understanding Classes and Instances.")
Variables with class scope are declared inside the class but outside of any of its methods. (A method is a routine containing statements, such as an event handler or a subroutine that you write.) These variables are visible to all of the code throughout the instance of the class and are known as fields.
Variables with method scope are declared within a method. They are usable by all of the code that follows the declaration within that method.
Variables with block scope are declared inside a block of code defined by some other program element such as an If Then
statement or a For
loop. The section "Block Scope" later in this lesson says more about this.
For example, consider the following code that defines a field, and some variables inside event handlers:
Public Class Form1 ' A field. Dim a As Integer = 1
Private Sub btnClickMe_Click() Handles btnClickMe.Click ' A method variable. Dim b As Integer = 2 MessageBox.Show("a = " & a.ToString() & ", b = " & b.ToString()) End Sub Private Sub btnClickMeToo_Click() Handles btnClickMeToo.Click ' A method variable. Dim c As Integer = 3 MessageBox.Show("a = " & a.ToString() & ", c = " & c.ToString()) End Sub End Class
The field a
is declared outside of the two methods (btnClickMe_Click
and btnClickMeToo_Click
), so it has class scope. That means the code in any of the methods can see and use this variable. In this example, the two Click
event handlers each display the value.
The variable b
is declared within btnClickMe_Click
, so it has method scope. Only the code within this method that comes after the declaration can use this variable. In particular, the code in the other methods cannot see it.
Similarly, the code in the btnClickMeToo_Click
event handler that comes after the c
declaration can see that variable.
Two variables with the same name cannot have the same scope. For example, you cannot create two variables named a
at the class level, nor can you create two variables named b
inside the same method.
Although you cannot give two variables the same name within the same scope, you can give them the same name if they are in different methods or one is a field and the other is declared inside a method. For example, the following code defines three variables, all named count
:
Public Class Form1 ' A field. Dim count As Integer = 0 Private Sub btnClickMe_Click() Handles btnClickMe.Click ' A method variable. Dim count As Integer = 1 MessageBox.Show(count.ToString()) End Sub Private Sub btnClickMeToo_Click() Handles btnClickMeToo.Click ' A method variable. Dim count As Integer = 2 MessageBox.Show(count.ToString()) End Sub End Class
In this example, the method-level variable hides the class-level variable with the same name. For example, within the btnClickMe_Click
event handler, its local version of count
is visible and has the value 1
. The class-level field with the value 0
is hidden.
You can still get the class-level value if you prefix the variable with the executing object. Recall that the special keyword Me
means "the object that is currently executing this code." That means you could access the class-level field while inside the btnClickMe_Click
event handler like this:
' A method variable. Dim count As Integer = 1 MessageBox.Show(count.ToString()) MessageBox.Show(Me.count.ToString())
Usually it's better to avoid potential confusion by giving the variables different names in the first place.
A variable with method scope is created when its method is executed. Each time the method is called, a new version of the variable is created. When the method exits, the variable is destroyed. If its value is referenced by some other variable, the value might still exist, but this variable is no longer available to manipulate it.
One consequence of this is that the variable's value resets each time the method executes. For example, consider the following code:
Private Sub btnClickMe_Click() Handles btnClickMe.Click ' A method variable. Dim count As Integer = 0 count += 1 MessageBox.Show(count.ToString()) End Sub
Each time this code executes, it creates a variable named count
, adds 1
to it, and displays its value. The intent may be to have the message box display an incrementing counter but the event handler uses a new version of count
each time, so the result is actually the value 1
every time the user clicks the button.
To save a value between method calls, you can change the variable into a field declared outside of any method. The following version of the preceding code displays the values 1, 2, 3
, and so on when the user clicks the button multiple times:
' A field. Dim count As Integer = 0 Private Sub btnClickMe_Click() Handles btnClickMe.Click ' A method variable. count += 1 MessageBox.Show(count.ToString()) End Sub
Note that a parameter declared in a method's declaration counts as having method scope. For example, the preceding event handler has two parameters named sender
and e
. That means you cannot declare new variables within the event handler with those names.
A method can also contain nested blocks of code that define other variables that have scope limited to the nested code. This kind of variable cannot have the same name as a variable declared at a higher level of nesting within the same method.
Later lessons explain some of these kinds of nesting used to make decisions (Lesson 19), loops (Lesson 21), and error handlers (Lesson 21).
One example that you've seen before is the If Then Else
statement. For example, consider the following code:
Private Sub btnClickMeToo_Click() Handles btnClickMeToo.Click ' A method variable. Dim count As Integer = 1 MessageBox.Show(count.ToString()) If count = 1 Then Dim i As Integer = 2 MessageBox.Show(i.ToString()) Else Dim i As Integer = 3 MessageBox.Show(i.ToString()) End If End Sub
This method declares the variable count
at the method level and displays its value.
The code then uses an If Then Else
structure. It declares the variable i
between the Then
and Else
, and displays its value. Note that the code could not create a second variable named count
inside this block because the higher-level method code contains a variable with that name.
After the first block ends, the code contains a second block between the Else
and End If
statements. It makes a new variable i
within that block and displays its value. Because the two inner blocks are not nested (neither contains the other), it's okay for both blocks to define variables named i
.
A field's scope determines what parts of the code can see the variable. So far I've focused on the fact that all of the code in a class can see a field declared at the class level outside of any methods. In fact, a field may also be visible to code running in other classes depending on its accessibility.
A field's accessibility determines which code is allowed to access the field. For example, a class might contain a public field that is visible to the code in any other class. It may also define a private field that is visible only to code within the class that defines it. The following code shows how a class might declare a public variable named NumberOfUsers
:
Public NumberOfUsers As Integer
Most developers use Pascal casing for public items declared at the class level, so in this case the variable is NumberOfUsers
instead of numberOfUsers
. Many developers even use Pascal casing for all items declared at the class level, even if they are not public.
Also note that declaring fields to be public is considered bad programming style. It's better to make a public property instead. Lesson 23 explains why and tells how to make properties. Public fields do work, however, and are good enough for this discussion of accessibility.
Accessibility is not the same as scope, but the two work closely together to determine what code can access a field.
Table 13-1 summarizes the field accessibility values. Later when you learn how to build properties and methods, you'll be able to use the same accessibility values to determine what code can access them.
Table 13.1. TABLE 13-1
ACCESSIBILITY VALUE | MEANING |
---|---|
| Any code can see the variable. |
Only code in the same class can see the variable. | |
| Only code in the same class or a derived class can see the variable. For example, if the |
| Only code in the same assembly can see the variable. For example, if the variable's class is contained in a library (which is its own assembly), a main program that uses the library cannot see the variable. |
| The variable is visible to any code in the same assembly or any derived class in another assembly. |
If you omit the accessibility value for a field, it defaults to Private
. You can still include the Private
keyword, however, to make the field's accessibility obvious. (In fact, I recommend always using an accessibility keyword to make the code easier to understand.)
The Private
keyword sometimes causes confusion. A Private
field is visible to any code in any instance of the same class, not just to the same instance of the class.
You cannot use accessibility keywords with variables defined inside a method. For example, you cannot declare a public variable inside an event handler. No code outside the event handler can ever see its variables.
For example, suppose you build a Person
class with a private field named Salary
. Not only can all of the code in an instance see its own Salary
value, but any Person
object can see any other Person
object's Salary
value.
It's a good programming practice to restrict a field's scope and accessibility as much as possible to limit the code that can access it. For example, if a piece of code has no business using a form's field, there's no reason to give it the opportunity. This not only reduces the chances that you will use the variable incorrectly, but also removes the variable from IntelliSense so it's not there to clutter up your options and confuse things.
If you can use a variable declared locally inside an event handler or other method, do so. In fact, if you can declare a variable within a block of code inside a method, such as in an If Then
statement, do so. That gives the variable very limited scope so it won't get in the way when you're working with unrelated code. It also keeps the variable's declaration closer to the code that uses it so it's easier to see the connection.
If you need multiple methods to share the same value or you need to preserve a value between method calls, use a private field. Only make a field public if code in another form (or other class) needs to use it.
In this Try It, you build the program shown in Figure 13-1. You use fields to allow two forms to communicate and to perform simple calculations. You also get to try out a new control: ListView
.
You can download the code and resources for this Try It from the book's web page at www.wrox.com
or www.vb-helper.com/24hourvb.html
. You can find them in the Lesson13 folder in the download.
In this lesson:
Create the NewItemForm
shown on the right in Figure 13-1.
Make the OK button initially disabled.
Provide public fields to let the main form get the data entered by the user.
Calculate and display Total Price when the user clicks the Calculate button, and enable the OK button.
Create the main form shown on the left in Figure 13-1.
Make the program display the NewItemForm
when the user clicks the Add Item button. If the user clicks OK, display the entered values in the ListView
control and update the Grand Total.
Because the main form's Grand Total must retain its value as the user adds items, it must be a field.
To let the main form see the values entered by the user on the NewItemForm
, use public fields.
Create the NewItemForm
shown on the right in Figure 13-1.
Arrange the controls as shown in Figure 13-1.
Set the form's AcceptButton
property to the OK button and its CancelButton
property to the Cancel button. The OK button will always close the form, so set its DialogResult
property to OK
.
Make the OK button initially disabled.
Set the OK button's Enabled
property to False
.
Provide public fields to let the main form get the data entered by the user.
Declare public fields for the program to use in its calculations:
' Public fields. (They should really be properties.) Public ItemName As String Public PriceEach, Quantity, TotalPrice As Decimal
Calculate and display Total Price when the user clicks the Calculate button, and enable the OK button.
Use the techniques described in Lesson 11 to get the values entered by the user and calculate the item's Total Price. Use the fields you created in the previous step.
Set the OK button's Enabled
property to True
.
Create the main form shown on the left in Figure 13-1.
Create the controls shown in the figure.
To make the ListView
display its items in a list:
Set its View
property to Details
.
Select its Columns
property and click the ellipsis to the right to open the ColumnHeader Collection Editor shown in Figure 13-2. Click the Add button four times to make the four columns. Use the property editor on the right to set each column's Name
and Text
properties, and to set TextAlign
to Right
for the numeric columns.
Make the program display the NewItemForm
when the user clicks the Add Item button. If the user clicks OK, display the entered values in the ListView
control and update the Grand Total.
Use code similar to the following.
' A private field to keep track of grand total ' across multiple calls to the event handler. Private GrandTotal As Decimal = 0 ' Let the user add a new item to the list. Private Sub btnAddItem_Click() Handles btnAddItem.Click Dim frm As New NewItemForm() If (frm.ShowDialog() = DialogResult.OK) Then ' Get the new values. Dim lvi As ListViewItem = lvwItems.Items.Add(frm.ItemName) lvi.SubItems.Add(frm.PriceEach.ToString("C")) lvi.SubItems.Add(frm.Quantity.ToString()) lvi.SubItems.Add(frm.TotalPrice.ToString("C")) ' Add to the grand total and display the new result. GrandTotal += frm.TotalPrice txtGrandTotal.Text = GrandTotal.ToString("C") End If End Sub
Please select Lesson 13 on the DVD to view the video that accompanies this lesson.
Use a design similar to the one used in the Try It to let the user fill out an appointment calendar. The main form should contain a ListView
with columns labeled Subject, Date, Time, and Notes. The NewAppointmentForm
should provide textboxes for the user to enter these values and should have the public fields AppointmentSubject, AppointmentDate, AppointmentTime
, and AppointmentNotes
to let the main form get the entered values. Instead of a grand total, the main form should display the number of appointments.
Build a form that contains a ListBox, TextBox
, and Button
. When the user clicks the Button
, display a dialog that lets the user enter a number. Give the dialog a public field to return the value to the main form.
If the user enters a value and clicks OK, the main form should add the number to its ListBox
. It should then display the average of its numbers.
Build the conference schedule designer shown in Figure 13-3. Give the main form (on the left in Figure 13-3) the following features:
Create private fields named SessionIndex1, SessionIndex2
, and so forth to hold the indexes of the user's choices.
When the user clicks an ellipsis button, display the session selection dialog shown on the right in Figure 13-3.
After creating the dialog but before displaying it, set its Text
property to indicate the session time in the dialog's title bar as shown in the figure.
Also before displaying the dialog, use code similar to the following to tell the dialog about the user's previous selection for this session. (The SessionIndex
and SessionTitle
variables are public fields defined by the dialog and are discussed shortly.)
frm.SessionIndex = SessionIndex1
If the user clicks OK, use code similar to the following to save the index of the user's choice and to display the session's title:
SessionIndex1 = frm.SessionIndex txtChoice1.Text = frm.SessionTitle
Give the dialog the following features:
Set the ListView
's FullRowSelect
property to True
and set its MultiSelect
property to False
.
Use the Properties window to define the ListView
's column headers.
Use the Properties window's editors to define the ListView
's items. Select the ListView
, click its Items
property, click the ellipsis to the right, and use the editor to define the items. Set the Text
property to determine an item's text. Click the SubItems
property and then click the ellipsis to the right to define the sub-items (Room and Speaker).
Use the following code to create public fields to communicate with the main form:
' Public fields to communicate with the main form. Public SessionIndex As Integer Public SessionTitle As String
Create a Load
event handler that uses the following code to initialize the dialog. This code selects the proper session in the ListView
control and then makes the control scroll if necessary so the session is visible:
' Initialize the selection. Private Sub PickSessionForm_Load() Handles MyBase.Load lvwSessions.SelectedIndices.Add(SessionIndex) ' Ensure that the selection is visible. lvwSessions.SelectedItems(0).EnsureVisible() End Sub
In the OK button's Click
event handler, use the following code to save the selected item's index and title for the main form to use:
' Save the user's selection. Private Sub btnOk_Click() Handles btnOk.Click SessionIndex = lvwSessions.SelectedIndices(0) SessionTitle = lvwSessions.SelectedItems(0).Text End Sub
You can find solutions to this lesson's exercises in the Lesson13 folder inside the download available on the book's web site at www.wrox.com
or www.vb-helper.com/24hourvb.html
..