Article source code: multipleforms4.zip
Introduction
In previous articles, we looked at some common problems that seem to have caused problems for many newcomers to VB.NET. In this article we will take a look at a specific area which appears to contribute more than its fair share of grief to many people in the early days of the .NET learning curve.
As with all the previous articles, this one is aimed squarely at .NET Newbies and tries to provide explanation and solutions with the least possible amount of techno-speak. Inevitably this sometimes means that strict technical accuracy has to take a back seat where a tricky concept is being explained, but it's all in a good cause!
The Background
Some of the previous articles have covered methods of accessing controls and data between multiple forms. On the whole, though, those articles have tended to use examples which get information from a second form in order to use it or show it in the Startup Form. This time we are going to concentrate on the reverse requirement – getting data or accessing a control from the startup form and using it in another form. Until you know how, this is not quite as easy as most of us would think it should be.
So, what is the big difference that causes us problems when we want to refer back to our startup form, Form1? Well, what those other forms need is a reference that they can use in order to get at that first form. As I've mentioned in previous articles, a lot of the trouble is caused by the way we've come to think of Forms as being different from other objects, due to our VB.Old upbringing. There's also a rather unhelpful smokescreen unwittingly caused by the way that this startup object (Form1) is automatically created for you in Visual Studio as the project fires up and runs.
You know that when you need to create an instance of Form2, you use code such as:
Dim F2 As New Form2() F2.Show()
and you'll be aware that F2 is a variable which holds a reference to that instance of a Form2 object. Once you have that reference in your object variable, you can access the properties or invoke the methods and events of that Form2 instance – BUT, and this is the key issue here – you can only do it via that F2 variable that you created.
At the risk of labouring it, the point I'm making here is that there is no
Form2.Show
in the example above. Form2 is the Class; F2 is a variable that holds a reference to an instance of that class. Just like any other object you would use in VB.NET.
So what we really need is the "Form1/F1" equivalent of this setup.
But where's the Form1 Variable?
But what can sometimes confuse us at this point is that the instance of Form1 that the project sets up and gets running for you is done somewhere behind the scenes. You don't see any similar code in your project that creates your startup form, Form1. There isn't a:
Dim F1 as New Form1 F1.Show
code snippet created for you automatically in Visual Studio that you can actually see. And yet, an instance of a Form1 object must have been created somehow as soon as you started to run the project.
Side-stepping the mechanism that does this for us, what we need to know is how we can create a reference to that instance of Form1 in the same way that we successfully did with Form2. We need a reference to that currently existing Form1. And of course we don't want to fall into the trap of creating another Form1 instance, as we might be tempted to do, with:
Dim F1 as New Form1
either. If you use code like this, you will get exactly what you asked for – a new Form1 – and you'll then have two of them bouncing about in your project, possibly causing all kinds of confusion and mayhem if you're not aware that this has happened.
The Solution
Having looked into the background in some detail, let's move on to the solution to the problem. By far the easiest way of doing this is to create a Friend variable in a module which holds a reference to a Form1 object. Why in a Module, do I hear you ask? The reason is that a variable located in a Module which has its scope declared as Friend can be "seen" – used and accessed - throughout this whole project or application. It makes life easier for you, the developer, and goodness knows we all want as much of that as we can get! (More on scope later).
Doing It The Module Way
Here's an example of what is needed:
1. First of all you will need a module; leave it as the default name of Module1.
Your project will already have a Form1. In order to make life easier for you, and for Intellisense to know of its existence, at this stage you should also add Form2 to the project. You can use the Add Windows Form menu item and accept the default name of Form2.
A Brief Look at Scope
In the Module, the first thing we should do is change the Scope of this module to Friend.
This simply means changing it so that it reads:
Friend Module Module1
Scope in an application is an aspect on which you should always keep a very watchful eye. In general terms, you should always aim to use the narrowest level of scope which will meet the application's needs.
In this case, our scope level of 'Friend' will mean that these variables and the forms they reference will be visible right across our current application, but no further.
Many Newbies are tempted to declare all variables as Public, or leave them at the default setting, which is often Public. It's a very easy trap to fall into. It's quick, it takes no thought, and it means you don't get those frustrating "xyz not declared" error messages at design time.
But – and it's a great big 'But' – if you fall into the habit of using wider scope than is needed, sooner or later it will come back to bite you.
An article covering many aspects of Variable Scope at the Module, Form and Method levels is planned for later publication, and this will go into much more detail of this whole area. For now, in its simplest terms, let's look at it like this: The wider the scope setting, the greater the chance that some part of the application is going to have access to it, quite possibly at a time you hadn't expected and with results you never intended. Or wanted.
So, bottom line: Keep Scope in declarations as narrow as possible.
Form Variables
What we are going to use this Module for is to create Variables that you can use to point to instances of each of the Forms in this application, one variable for each form.
The key point here is that this will include a form variable for Form1. And once we have this variable, we can treat our Form1 startup form in exactly the same way that we have become used to treating all other forms.
Friend F1 As Form1 Friend F2 As Form2 Friend F3 As Form3
Apologies for repeating myself, but it's crucial: do not use the 'New' keyword in these variable declarations. Notice also that the scope of each of the declarations is 'Friend'. (We've done this to avoid confusion. There are alternatives which produce the same result and these will be covered in the later article on Scope).
So at this point, you have declared a variable which will be used to hold a reference (i.e. a pointer) to an instance of a Form1 object. Right now, it is empty, has a value of nothing, nix, nowt, zilch. It has simply been declared, and when the time is right it will be given a reference to an instance of Form1 to hold in its care.
As it happens, in this case, that time is now.
2. In the Form_Load event of Form1, we now need to have our F1 variable hold the details of this instance of Form1. This is done with the following code:
F1 = Me
"Me" in this case referring to this current instance of Form1.
We now have a variable – F1 – which holds the reference to our current instance of Form1 (the instance that is automatically created for you at startup). Armed with this, we can get full access to what is in and on this first form from anywhere else in our project.
And we can now see this in action.
First though, let's just add some controls to Form1 which we can then use for demonstration purposes. Add the following controls to Form1:
A Button – Button1
A Label – Label1
A TextBox – TextBox1
Add this code to the various controls above:
Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click If F2 Is Nothing Then F2 = New Form2() F2.Show() End If End Sub
You'll probably have realized that the above code checks to see if there is already an instance of Form2 up and running, and creates one if not. One thing to note, though is this. We haven't used the usual
Dim F2 As New Form2
in the way that you normally might expect. It isn't necessary (and in fact would cause problems), because we already have the declaration of F2 in our Module – and any of the forms in the project can therefore get access to that variable.
So, to complete our small demonstration project, we need to code Form2 in a way that shows that we can access and even change data in Form1 by some action that we perform on Form2.
3. add the following two controls to this Form:
A Button - Button1
A Label – Label1
In the Form2 code window, include the following code:
Private Sub Button1_Click_1(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click ' Access the instance of Form1 and read it's textbox contents Me.Label1.Text = F1.TextBox1.Text ' Change Form1's Label1 text to prove that you ' can "write" to Form1 as well as get data from it F1.Label1.Text = "Form2's Button has been clicked" End Sub
Important points to note in the above code are the uses of 'F1' to refer to our Form1 instance. Nothing else will do in this example – this is usually the place where .NET Newbies get stuck. As you can see, we are able to take data from Form1 and show it in Form2. We are also able to change what is shown on Form1 by invoking instructions carried out in Form2. Pretty powerful and useful stuff.
The attached demo solution takes the idea a stage further and includes a Form3, but the principles remain the same. The demo also includes some code that tidies up various minor areas that may cause problems or confusion, so I recommend that you take a look at it if you are planning to use the methods described in this article.
Summary
The key to this whole issue is the use of the Friend variables in the Module. By placing them there in the Module and giving them Friend scope, any form in the project can access those variables, and through them, can access the other Form instances.
Don't overlook the initialization of the F1 variable when Form1 is first loaded. Without that line of code:
F1 = Me
there would be no value held in F1 and the other forms would not be able to get access to the Form1 instance.
I'm not going to pretend that it's the easiest concept in the world to get to grips with first time round. But trust me, as with most things .NET related, it all eventually falls into place. If I can get to understand it, then there's hope for everyone else.
The primary aim of this article is to resolve the mystery of how to refer to the project's startup form from other forms. However, as you will see from the demo solution attached, if you create a Friend variable in the Module for each of the forms in your project, you can easily access each and every one of them at will. This can be a very versatile and easy to use tool which not only allows access to large numbers of forms in complex applications, but also helps retain control and avoids unwanted duplicate instances of forms.
As some readers will of course already know, the above method is just one way of dealing with this problem. There are others, but I thought that this approach would be of most use to .NET Newbies and I hope that many of you will find it of some use.
评论