Article source code: multipleforms.zip
This article was written in June 2003 and was aimed at users of the VB.NET 2002 and 2003 editions who were upgrading from VB6. Now that VB2005 has arrived, much of the content has been overtaken by improvements in that new edition and won't be applicable if this is the version you are using.
________________________________
Introduction
One of the first hurdles you're going to come up against when you move from Classic VB to VB.NET is getting to grips with the new ways of dealing with forms.
Your approach to forms in VB.NET is fundamentally different and hinges on the fact that the Framework implements forms as classes, so now you need an object variable to instantiate it. This article is aimed at helping you through some of those basic steps.
Instead of loading and showing a form in the old way (eg. Form2.Show, what you have to do is create an instance of the form and you can then manipulate this form object.
Here's one way. Let's assume that you have a project that contains a Form1 and a Form2. You have already set Form1 to be the startup form and it contains a button which, when clicked, will display an instance of Form2 to the user. Assuming this button is called ShowForm2Button
, the code you need is as follows:
If you try this code out, you'll find that it works fine, as far as it goes. But you will also discover that it is possible to have several instances of Form2 displayed at the same time if you click the ShowForm2Button
before you have closed down the current instance of Form2. Each click of the button does exactly what the code tells it to do - create a new instance of a Form2 Class object and show it.
Of course, this may not be what you need in your project. You may want to have only a single instance available at any one time.
This can be achieved in several ways. Each has advantages and disadvantages, and some may yield unexpected results to the OOP-unwary developer. The following way might be an acceptable fix for you in many situations:
There is a potentially easy way round the problem. Show the second form modally (a concept you'll be familiar with from VB.OLD, I'm sure). The .NET syntax is:-
Now, as you'd expect, the user can't get focus back to Form1 to click that pesky button again until they've closed down this instance of Form2.
However, there will probably be times when you want the best of both worlds, that is you want the user to only have one instance of your second form instantiated at any one time, but you want them to be able to see and use both Form1 and Form2 at the same time.
The solutions for showing forms in this way are easy. Where the problems start to kick in are that you may get side effects that are not so welcome if you don't take steps to deal with them.
Type this into the main body of the form (i.e. outside any Subs, Events or Methods)
And use this code in the button's click event:
As promised, this will only show the single instance of Form2, because it isn't instantiated afresh every time you click the button. That's the up side.
The down side is that if you close Form2, then try and display it again by clicking on the button you will generate an exception.
The error message tells you why - you instantiated the Form2 object when Form1 was first created and you disposed of it when you closed it by clicking on the little x.
Trying to show it again will not succeed now because clicking on the button doesn't actually create another instance - it's simply trying to show a non-existent instance.
Let's find a couple of workarounds for this little glitch.
Instead of closing Form2, why don't we simply hide it? In many situations this may be an acceptable solution.
The code for Form1 is the same as for the above method. Your Form2 will need to be rigged so that it hides itself. Here's the easiest way:
False
. (This removes the ability of the user to close this form by using the little x). FinishedButton
. What we've done here is to create a situation where there is only one instance of Form2 and this instance is shown when the ShowForm2Button
is clicked and hidden when the FinishedButton
is clicked. The user is no wiser as to whether a form is closed or hidden.
So, does this solve all possible requirements? Of course not - this is programming; there's always another problematic scenario just around the corner!
What if you need to allow the user to have access to the Minimize or Maximize buttons in the ControlBox? As far as I know, although you can disable them, you can't have just these two buttons visible without also showing the exit button.
My fix for this involves cutting out the middleman. If there has to be a ControlBox and it has to contain the little x, then let's short circuit that little x. Here's how:
Leave Form2 with it's ControlBox available and dispense with the FinishedButton
. What we'll do is change the code that gets fired when the user clicks on that little x. This is the form's Closing
event. Add this code to Form2.
Be aware of a not so obvious knock-on effect here, though, if Form1 is not the startup form for your application. In this situation, when you close Form1 it will NOT automatically close Form2 (which is what always happens if Form1 is the startup form). So, something to keep in mind there - if you're happy to have Form2 still open once Form1 has closed, then that's fine; if not, then add this code to Form1's Closing
event:
And it will take Form2 away with it when it closes.
There is another way. Well, there's almost always another way, isn't there? This is based very closely on code provided by DevCity.NET member DrDave. It steps through a checklist of the possible states of the second form and takes the appropriate action depending on what it finds.
I've tried to make the steps as clear as possible with the commenting, but if you're like me you'll probably have to read it and try it a few times before it all clicks into place.
Here's the code:
Declaration outside of any methods:
And this code goes in the button's click event:
Another possible scenario might be where you need Form1 hidden while Form2 is on view, and for Form1 to be redisplayed when Form2 is closed.
This gets a bit more complicated, but only a bit. In the same way that we needed an object variable to reference our Form2s in the examples above, we will need a reference back to the instance of Form1 which we can use in Form2.
There are various ways of doing this, but we're going to overload the Form's constructor to achieve our aim. Less technically, this simply means that we'll create an alternative version of the 'New' Sub that all forms contain. This one will make a note of which form called it into existence.
Starting with Form1, add this code to the form:
Don't worry if you get a wiggly blue line error at this stage in Form1's code. The next bit of code in Form2 will resolve that. Form2 needs this code:
Add an additional Sub New
:
Note that it doesn't replace the code already in the "Windows Form Designer generated code" region; this is an additional (overloaded) constructor.
Next, Form2 needs this declaration in the usual declarations area (ie. outside of any other code blocks) :
For this example, I have used the variation which bypasses the Closing
event and simply hides Form2, but this is optional if it doesn't suit your purposes. The logic is the same if you do want Form2 to be closed, not merely hidden.
(If you don't want to bypass the Closing
event as shown above, then you can put a button on your Form2 and use the code above, but without the e.Cancel = True
line.)
A happy side effect of using this approach is that it also gets round the problem of unwanted multiple instances of Form2. Obviously, because Form1 is hidden whenever Form2 is on display, the user can't get to the button to fire up another Form2 instance.
This article has only covered a sample cross-section of some of the ways you can open multiple forms. There are other techniques (MDI, 'Form within a form' and owned forms, for example) that haven't been included here. But hopefully there is enough information here for most purposes in the early days of your journey up that long and tiring .Net learning curve.
If you do want to see some working code for MDI or owned forms, then you can check out the attached solution. It includes all seven methods above, plus these two.
Of course, opening forms is only part of the story. You will also need a grasp of how to pass data between forms and also how to access one form's controls from another form. And finally you will also need to learn a few safe and trusted ways of closing down the forms you want closed, and leaving open those you still need available. Disposal of finished forms is an important part of good development technique. It's planned that these topics will be covered in later parts of this series.
Article source code: multipleforms2.zip
One of the key mindset changes you have to make as part of your move from VB6 to VB.NET is the status of Windows Forms. In the early days of VB, Forms were King. Now, VB.NET changes the picture and - although they are still important in many applications - Windows Forms should in many cases be viewed as simply another class and be dealt with accordingly.
If you keep this in mind, many of the early learning curve problems of the move to VB.NET are easier to overcome. We dealt with how to load, show, hide and close multiple forms in Multiple Forms in VB.NET: Part 1. You saw there that the answer to most VB6 to VB.NET misunderstandings in this area was to deal with each form as an object of the Form Class.
In this article, we will take an initial look at some ways of accessing other form's controls and passing data between forms; and our approach to this is going to be pretty much the same as in the earlier article.
This is written for fellow .NET Newbies and so it avoids some of the more advanced topics and skills. It is intended only to give you a hand to get started with the basics of accessing one form and its data from another the .NET way, using an object oriented approach. As always with .NET, there's lots of scope for further study!
When you are dealing with Windows Forms it's quite likely that sooner or later you are going to want to pass the data held in a control on one form over to another form. For example, the text a user has entered in a text box, a selection from a combobox, a checkbox's Checked property, etc. You may also want to insert data into a control on another form.
Remembering that everything in VB.NET is an object, and that we want to leave our VB.Old ways behind us and think OOP, the trick here is to deal with the control we want to access as an object in its own right.
So let's take one of the examples from the first paragraph above and see how we go about achieving this. I'm going to take the first one - passing the text from a textbox in Form2 back to Form1 - as this is relatively common and straightforward.
In this example we'll assume that it's acceptable for the second form to be opened modally (that is, the user can't do anything with the first form until the second form has been closed.) In many cases, this will be fine.
Here are the steps:
(* It's not usually considered a good idea to use names that don't reveal any information - such as Form1, TextBox1, etc. However, I've kept to Form1 and Form2 in this example as it's useful to make it clear which form is doing the calling and which is being called.)
And now to start the coding. In Form1, we need an Object Variable to manipulate the second form.
In the Click_Event handler for the ShowButton, enter this code:
There is some more code needed for that button click event, but we'll come back to it in a moment.
In Form2, we create another Object Variable, but this time it will hold a TextBox control, not a Form.
Notice that the scope of this variable is 'Public Shared'. This is important as this scope declaration makes the variable visible to the other form (and in fact the rest of the application).
Still in Form2, we put the following code in the Click event of the button:
With that very simple code, we have made the properties of our TextBox in Form2 available to the user back in Form1. NuNameTB is now effectively a TextBox Object, whose Text property is the same as Form2's UsersName.Text property.
I said we'd have to come back to the code block for the ShowButton Click in Form1. If you have entered all the above code and have run it, you will be back with Form1 on view, but there's no evidence that what I have just said is true. So, here's the proof:-
At the bottom of the Click event for Form1's ShowButton, add this:
Run this now and, once you've closed down Form2, you will see that whatever you typed into the TextBox on Form2 is available and displayed in the label on Form1.
Note that although this example uses the most obvious property of the TextBox (the Text property), you can in fact access any of that TextBox's properties if you need to. By the way, if you're wondering why we've done it this way with an object variable and not accessed the textbox directly, then the plain answer is 'Think OOP'.
So that was a fairly uncomplicated example which will do the job for you in many circumstances. Let's rack up the OOP factor another notch and this time use a Property Procedure as our means of passing the data around. This fits in with one of the cornerstones of OOP - Abstraction.
In my non-technical way, I like to think of Abstraction as the provision of a general purpose front end that continues to function properly even if we tweak about with the code at the 'back end'. In the example that follows, for instance, we could remove the textbox from Form2 and use some other means of feeding data into its UserName Property. The code in Form1 would be oblivious to the change and still work perfectly well.
I am again going to use a textbox as the control we are focusing on, but don't let this give you the mistaken impression that you are limited to textboxes. They just happen to be easy to use as examples. Set up your project as follows:
As in the previous example, we need an Object Variable In Form1 to allow us to show the second form.
And we can add this code to Form1's Load event in order to have both forms on view at the same time:
So far, this example and the previous one are very similar. We start to diverge at this point. Let's create that Property on Form2 which will hold the contents of TextBox1 (or whatever else we later choose to store in the UserName property).
In Form2, we will create a ReadOnly Property called UserName. Why Read Only? Well, it's not strictly necessary, but for the purposes of this simple example it will ensure that data cannot be passed back from Form1 and used to populate the TextBox in Form2.
Here's the code to go into Form2:
As you can see, all this Property does is hold the value of the text that the user enters into the textbox on Form2.
Now, what we want to happen is that when the user clicks on the button in Form1, the label on Form1 will be updated with the contents of the textbox on Form2. (Actually, to be more accurate, the contents of the UserName Property from Form2, remembering what I said earlier about Abstraction).
So in Form1, in the Click_Event handler for the ShowButton, enter this code:
To see this example working, run the project and make sure that you have positioned the two forms so that you can see both on the screen at the same time. Enter a name into the textbox on Form2. Click on the button on Form1. The label on Form1 will be updated with whatever you have entered into that textbox.
Change the textbox entry, click the button again and the label will change accordingly.
OK, now no-one's pretending that this is anything like a real-world scenario; it's a very artificial project, designed only to demonstrate that you can pass the data from the control on one form to a control on another form. Structurally it has more holes than a piece of Gruyere cheese, but it serves its purpose. Hopefully, you can see more realistic ways in which you can employ this basic technique.
The attached Solution includes the two examples from this article.
In the next article, we will look at rather more dynamic ways of getting hold of this cross-forms data. By this I mean the kind of scenarios where Form1 can be automatically updated when data in Form2 changes - no more of this 'pressing a button to make something happen' nonsense. We'll be tying our Form Properties in with Events and Event Handlers for a much slicker, user friendly and professional looking application. But in the meantime the two examples in this article have at least got the basics under our belts.
Article source code: multipleforms3.zip
As with the previous articles in this series, this one is also aimed at .NET Newbies and Upgraders. It tries to explain concepts as simply as possible, with the greatest use of plain English and minimum use of technical terms. The aim is to get the core ideas across as quickly as possible, so you can achieve the results you desire now; the technical details can follow in time. I know that many will not agree with this approach, but as a relative .NET Newbie myself, I know just how frustrating it can be trying to plough through a mass of technical detail in the early days when all you really want to do is, well, get started!
In this article, we are going to look at another topic on ways of dealing with multiple forms. As promised at the end of Part 2, we are first going to take a look at a way of passing data between multiple forms, but this time the user doesn't need to click a button to fire up the event.
In our example project, we will have two forms. As the user enters data into a textbox in one form, this data will be copied to a label in the second form. Once again, although we're using a simple textbox for this example of the technique, the core idea can be extended for use in many much more sophisticated ways.
So, create two Windows Forms and name them EventsForm1 and EventsForm2. We'll be using EventsForm1 as the 'main' form, which is the one that has the label to receive the input data, and EventsForm2 as the form with the input textbox.
Here's the code we need to create an instance of EventsForm2. First, in EventsForm1, we declare a Form variable.
Note that this has been declared WithEvents
. This is important and if you leave it out, the main form will not be aware of what we are about to do in the second form.
And I thought that as we are being more dynamic in this Part, we would also instantiate and show the second form without user intervention. Or, in other words, we'll put it in the first form's load event and both forms will appear to the user at the same time.
Apart from the couple of lines at the end of that block, which just ensure that the two forms can be seen side by side, the instantiation code is the "test before instantiating" code which we covered in Part 1. (The code in Part 1 is quite well commented if you want to see the details.)
If you were to run the project at this point, you will be rewarded with two control-free forms side by side on your screen. Time to add some GUI items then.
In EventsForm1, add a Label. The size isn't critical, but you should allow for several lines of text to be visible. We'll name this label lblData
.
In EventsForm2, add a Textbox. Delete the default Text
property (usually this will be 'TextBox1') so that the user will see an empty textbox. Also set its MultiLine
property to True
. Finally, rename it as TB1.
Just to quickly recap what we are trying to do here: When the user types something into the textbox TB1 in EventsForm2, we want the text also to be displayed in the label in EventsForm1.
Let's add in the code to achieve this. We first need to declare an Event. We'll call this event TextHasChanged
.
Here's the declaration; note that it has to be placed outside any code blocks, subs, etc.
Now, we need something to fire up this event. The obvious candidate in this particular scenario is the built-in TextChanged
event of the textbox, so let's put some code in there to raise the warning flag that we are creating here.
Select the textbox TB1 in the code window and put this code in its TextChanged
event handler:
All this does is to flag up the fact that text has changed by raising the Event that we declared and called TextHasChanged
.
What will happen each time the user enters any character into the textbox is that the TextHasChanged
event is raised. (This includes any editing of the text with the Delete or BackSpace keys too).
However, there's no point in waving the flag around (raising the event) if there's no lifeguard (in this case EventsForm1) looking out for it. So, finally, we'll tell EventsForm1 what to do if the TextHasChanged
event in EventsForm1 is raised. (Technically, this is known as receiving the event.)
Back in EventsForm1, if you go into the Code Window and click on the left hand combo, you will see that the Object we have named f2 is available in that drop down list of Objects. Select it and then click on the right hand list of Events. You will see that our newly created event TextHasChanged
is now included in the list of events, along with all the usual Windows Forms events that you'd expect to see there.
So let's select that item from the list and we will be able to put some code in the TextHasChanged
Event's Event Handler:
You'll understand that all this code really does is to copy the text from the textbox on the other form into the label on this form. But because we have linked this event to react to every change of character in that textbox, the user will see the text displayed in the label dynamically as it is being typed. If you run the project now and type in some text, you will see this in action.
There are of course always alternative ways of achieving the same result, but I took the example of a textbox used here as a simple demonstration of the sort of thing you can do with passing events between multiple forms. It's sometimes difficult to find examples that aren't too artificial, but hopefully this basic example will be enough to show you how to use the technique in a variety of more advanced and exciting ways, according to your needs in projects and applications. I hope you find it a useful introduction to the very basics of Events, as well as simply showing you another way of passing data between multiple forms.
My thanks to fellow vbCity Member George Poth for proofreading this article and for creating the attached Visual Studio Solution from the narrative.
Article source code: multipleforms4.zip
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!
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:
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 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:
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:
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.
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).
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.
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:
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.
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.
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:
"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:
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
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:
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.
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:
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.