Article source code: multipleforms.zip
_________________________________
Authors' Note February 2006:
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.
Method 1: Show More Than One Instance of a Second Form
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:
Private Sub ShowForm2Button_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles ShowForm2Button.Click Dim F2 As New Form2() F2.Show() End Sub
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:
Method 2: Show Second Form Modally
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:-
Private Sub ShowForm2Button_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles ShowForm2Button.Click Dim F2 As New Form2() F2.ShowDialog(Me) End Sub
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.
Method 3: Allow Only One Instance of Second Form.
Type this into the main body of the form (i.e. outside any Subs, Events or Methods)
Dim F2 As New Form2()
And use this code in the button's click event:
Private Sub ShowForm2Button_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles ShowForm2Button.Click F2.Show() End Sub
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.
Method 4: Showing/Hiding a Second Form.
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:
- Change Form2's ControlBox Property to
False
. (This removes the ability of the user to close this form by using the little x). - Add a button to Form2. Call this button
FinishedButton
. - Use the following code in the button's click event:
Private Sub FinishedButton_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles FinishedButton.Click Me.Hide() End Sub
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!
Method 5: Showing/Hiding a Second Form (Alternative)
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.
Private Sub Form2_Closing(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing ' Bypass the instruction to Close this form e.Cancel = True ' But hide it from the user. Me.Hide() End Sub
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:
If Not IsNothing(F2) Or Not F2.IsDisposed Then F2.Close()
And it will take Form2 away with it when it closes.
Method 6: Check Current Status First
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:
Private WithEvents F2 As Form2
And this code goes in the button's click event:
Private Sub ShowForm2Button_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles ShowForm2Button.Click ' If the instance still exists... (ie. it's Not Nothing) If Not IsNothing(F2) Then ' and if it hasn't been disposed yet If Not F2.IsDisposed Then ' then it must already be instantiated - maybe it's ' minimized or hidden behind other forms ? F2.WindowState = FormWindowState.Normal ' Optional F2.BringToFront() ' Optional Else ' else it has already been disposed, so you can ' instantiate a new form and show it F2 = New Form2() F2.Show() End If Else ' else the form = nothing, so you can safely ' instantiate a new form and show it F2 = New Form2() F2.Show() End If End Sub
Method 7: Flip-Flop Form1 and Form2
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:
Private Sub ShowForm2Button_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles ShowForm2Button.Click ' Note the "Me" in brackets. Very important! Dim F2 As New Form2(Me) ' Instantiate it F2.Show() ' Show it Me.Hide() ' Hide Form1 End Sub
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
:
Public Sub New(ByVal caller As Object) MyBase.New() InitializeComponent() ' Note which form has called this one CallingForm = caller End Sub
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) :
Private CallingForm As Object
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.
Private Sub Form2_Closing(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing e.Cancel = True Me.Hide() ' If the CallingForm still exists then show it now If Not Is Nothing(CallingForm) Then CallingForm.Show() ' then dispose of it's reference here. CallingForm = Nothing End Sub
(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.
Summary
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
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!
Accessing Another Form's Controls
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.
Example 1: Reading Another Form's TextBox
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:
- Create Two Windows Forms (Form1 and Form2*)
- Form1: Add a Button (named 'ShowButton') and a Label (named 'UserLabel').
- Form2: Add a TextBox (named 'UsersName') and a Button (named 'CloseButton').
(* 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.
Public Class Form1 Inherits System.Windows.Forms.Form Dim F2 As New Form2() ' Object variable
In the Click_Event handler for the ShowButton, enter this code:
F2.ShowDialog(Me) ' Show Form2
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.
Public Class Form2 Inherits System.Windows.Forms.Form Public Shared NuNameTB As TextBox
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:
Private Sub CloseButton_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles CloseButton.Click ' Assign this form's TextBox to the Object Variable NuNameTB = UsersName ' Now close the form Me.Close() End Sub
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:
Me.UserLabel.Text = "Current User : " & F2.NuNameTB.Text
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'.
Example 2: Using a Property Procedure
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:
- Create Two Windows Forms (Form1 and Form2)
- Form1: Add a Button (named 'ShowButton') and a Label (named 'UserLabel').
- Form2: Add a TextBox (named 'UsersNameTextBox').
As in the previous example, we need an Object Variable In Form1 to allow us to show the second form.
Public Class Form1 Inherits System.Windows.Forms.Form Dim F2 As New Form2() ' Object variable
And we can add this code to Form1's Load event in order to have both forms on view at the same time:
F2.Show ' Show Form2
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:
Public ReadOnly Property UserName() As String Get Return UsersNameTextBox.Text End Get End Property
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:
Private Sub ShowButton_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles ShowButton.Click Me.UserLabel.Text = F2.UserName End Sub
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.
评论