Chapter 13. Understanding Scope

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.

SCOPE WITHIN A CLASS

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.

Same-Named Variables

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.

Note

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.

Method Variable Lifetime

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.

Block Scope

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.

ACCESSIBILITY

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

Note

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

Public

Any code can see the variable.

Private

Only code in the same class can see the variable.

Protected

Only code in the same class or a derived class can see the variable. For example, if the Manager class inherits from the Person class, a Manager object can see a Person object's Protected variables. (You'll learn more about deriving one class from another in Lesson 25.)

Friend

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.

Protected Friend

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.

Note

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.

RESTRICTING SCOPE AND ACCESSIBILITY

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.

TRY 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.

FIGURE 13-1

Figure 13.1. FIGURE 13-1

Note

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.

Lesson Requirements

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.

Hints

  • 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.

Step-by-Step

  • Create the NewItemForm shown on the right in Figure 13-1.

    1. Arrange the controls as shown in Figure 13-1.

    2. 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.

        1. Set the OK button's Enabled property to False.

      • Provide public fields to let the main form get the data entered by the user.

        1. 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.

        1. 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.

        2. Set the OK button's Enabled property to True.

  • Create the main form shown on the left in Figure 13-1.

    1. Create the controls shown in the figure.

    2. To make the ListView display its items in a list:

      1. Set its View property to Details.

      2. 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.

        FIGURE 13-2

        Figure 13.2. FIGURE 13-2

  • 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.

    1. 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

Note

Please select Lesson 13 on the DVD to view the video that accompanies this lesson.

EXERCISES

  1. 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.

  2. 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.

  3. Build the conference schedule designer shown in Figure 13-3. Give the main form (on the left in Figure 13-3) the following features:

    FIGURE 13-3

    Figure 13.3. FIGURE 13-3

    • 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

Note

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..

FIGURE 13-3
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset