Chapter 2 points out that the .NET Framework is a huge collection of thousands of classes that are made available to you. The granddaddy of all these classes is the System class. Virtually everything in the .NET Framework is derived from this one class.
However, in an attempt to make this massive collection of classes more manageable, Visual Basic .NET divides the System class into smaller groups of classes, each with its own namespace. The namespace, therefore, is the way you reference classes in Visual Basic .NET to avoid ambiguity. You have already seen examples of this when you have used forms in your sample programs. The components you have used in this chapter are part of the System.Windows.Forms namespace. If you look near the top of Listing 8.6, you see this line:
This line is necessary because you want to use the functionality provided by the Visual Basic .NET forms and the objects you can use with forms. Therefore, in Listing 8.6, you are making the classes that are part of the System.Windows.Forms namespace available to you.
It might not be obvious that Visual Basic .NET automatically creates a namespace for every executable program file you create. The namespace has the same name as your project. For example, suppose you create a new project named MyNewProject . Further assume that your program is somewhat large, and you have three forms in the program. To keep this example simple, assume that these forms are named InputForm , DisplayForm , and PrintReportForm .
Now assume that you have a variable named MyPrinter that is defined with module scope in the PrintReportForm module. You can use this variable anywhere in the PrintReportForm module by simply using the name MyPrinter . But to the other two forms, the variable is not even visible because it is out of scope. Remember that module scope for a variable extends from its point of definition to the end of the class definition.
The Purpose of Namespaces
Suppose you were presenting a paper on a program you wrote, and you wanted to say something about the MyPrinter variable. To do this properly and without ambiguity, the reference would have to be: MyNewProject.PrintReportForm.MyPrinter . Notice that this reference format clearly identifies where the MyPrinter variable exists. As you can see, the purpose of namespaces is to reduce ambiguity and prevent name collisions.
A name collision occurs when you attempt to define a second variable with the same name and at the same scope level as another variable. (A name collision produces a duplicate definition error, as you saw earlier in this chapter, in the section "Scope and Duplicate Definition Errors.") The proper use of a namespace, however, helps prevent name collisions. For example, how many forms have you created that have btnExit buttons on them? A bunch, and that's just an estimate. Why isn't there a name collision for btnExit ? Even though you have in many different programs a button object that uses the same name, it's not a problem because the full namespace for the btnExit object is prefixed with the namespaces for both the form that holds the button and the project that holds the form.
In the hypothetical program mentioned in the preceding section, even if the InputForm , DisplayForm , and PrintReportForm modules all have btnExit buttons, there is no name collision. The reason is that the namespace for each button is unique (for example, MyNewProject.InputForm.btnExit versus MyNewProject.DisplayForm.btnExit ). Even though the btnExit buttons share the same name, Visual Basic .NET uses the full namespace to reference each one, so there is no collision.
You saw earlier in this chapter that if you move the definition of MyVariable outside any procedure, it has module scope. You also saw that, by default, the scope of MyVariable is limited to the module in which it is defined. But what if you want other elements of the program outside the module in which MyVariable is defined to have access to MyVariable ? You can make that happen by changing the definition of MyVariable and giving it namespace scope.
To illustrate namespace scope, you can add a second form to the LocalScope project. To add a second form, select Project, Add Windows Form. When Visual Basic .NET prompts you for the form name, type frmForm2 . Then add a label with the text MyVariable: and a text box named txtValue . If you do this correctly, your screen should look similar to Figure 8.2.
Figure 8.2. The IDE screen for the second form in the LocalScope project.
Now you can double-click the form to activate the Code window for frmForm2 . Add the following code to the form's Load() event for frmForm2 :
Private Sub frmForm2_Load(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles MyBase.Load MyVariable = 10 End Sub
You need a way to invoke the second form shown in Figure 8.2. Therefore, you should add the following lines of code to the btnCalc object's Click() event of the frmLocalScope form:
Dim F2 As New frmForm2() MyVariable = 20 F2.ShowDialog()
The Dim statement defines an instance of the second form. This means that F2 is an instance of the frmForm2 class. The second line simply assigns the value 20 to MyVariable . The third line calls the ShowDialog() method for the F2 object. The impact of this statement is that it shows frmForm2 onscreen. Now if you compile the program, you get this error message:
Name 'MyVariable' is not declared.
This error message is telling you that Visual Basic .NET has no clue what the variable named MyVariable that is referenced in the frmForm2 form's Load() event is. It is undefined. Hmmm. What if you try using the namespace for the first form? Modify the line in the frmForm2 form's Load() event so it looks like this:
frmLocalScope.MyVariable = 10
Then try to compile the program. Now you get a different (and more promising ) error message:
'LocalScope.frmLocalScope.MyVariable' is not accessible in this context _ because it is 'Private'
Visual Basic .NET is telling you that it found the variable named MyVariable in frmLocalScope , but the variable is defined with module scope that is private. This is exactly as it should be, remember? Only data items in the module in which the definition occurs have access to the variables with Private module scope.
What if you changed the Private access specifier in Listing 8.6 to Public ? You can change the third line in Listing 8.6 to this and then recompile the program:
Public MyVariable As Integer
The compile still didn't work, but what's the problem? You get this error message:
Reference to a non-shared member requires an object reference.
The error message is saying that MyVariable is defined in such a way that it is not accessible to the code in the second form. You have to remember that Visual Basic .NET has the creative thinking ability of a box of rocks. You and I know that MyVariable is defined in the frmLocalScope form, but Visual Basic .NET can't make the logical connection between the two forms by itself. Could it be that because we had to create an instance of the frmForm2 form in the first form, you need to do the same thing in the second form?
Listing 8.7 shows the code for frmForm2 .
Listing 8.7 The Code for frmForm2
Public Class frmForm2 Inherits System.Windows.Forms.Form Dim OtherObject As New frmLocalScope() Private Sub frmForm2_Load(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles MyBase.Load txtValue.Text = CStr(OtherObject.MyVariable) End Sub End Class
Notice the definition of OtherObject near the bottom of Listing 8.7:
txtValue.Text = CStr(OtherObject.MyVariable)
This creates an instance of the frmLocalScope object that you can reference in frmForm2 . The line simply uses the instance of the frmLocalScope object named OtherObject to access the MyVariable member. Now if you compile the program, it works without error. The program compiles now because you have created an instance of the frmLocalScope object that contains MyVariable in frmForm2 .
Figure 8.3 shows a sample run of the program.
Figure 8.3. A sample run of the modified LocalScope project.
As you can see, the program executes and does load and show the second form.
But there's a problem. When you modified the btnCalc object's Click() event code, you assigned MyVariable the value 20 and then called the second form with the F2.ShowDialog() statement. Given that you did all that, why does Figure 8.3 show the value for MyVariable to be and not 20 ? You need to go back to the drawing board.
Sharing Variables Between Forms
Whenever you create an instance of an object, you also create new instances of all the data items that are part of that object. In the preceding section, you created a new instance of the frmLocalScope object, which means you got your own brand-new version of the MyVariable variable, too. In other words, the lvalue of OtherObject.MyVariable is not the same as the lvalue for frmLocalScope.MyVariable . Even though they share the common name MyVariable , they are in different namespaces. Therefore, they are different variables with different lvalue values and cannot be expected to have the same rvalue values.
But what if you want them to be the same variable? What if you want the variable MyVariable in the frmLocalScope object to be the same MyVariable in the frmForm2 object? In other words, what if you want the forms to share a single lvalue for MyVariable ? Well, if that's what you want, you need to tell Visual Basic .NET about it. (More rocks, remember?) To do that, you change the third line in Listing 8.6 from its present form:
Public MyVariable As Integer
Public Shared MyVariable As Integer
This change tells Visual Basic .NET that you want the variable named MyVariable to be available to other objects in the program (via the Public keyword), and you also want only a single lvalue for MyVariable defined for all instances of the frmLocalScope object. Stated in different terms, you want the scope of MyVariable to extend to all objects in the project.
If you now recompile and run the program, you find that MyVariable in the second form now has the value 20 .