Most of this book so far has dealt with building forms. Previous lessons explained how to add, arrange, and handle the events of controls on a form. They explained how to work with specific kinds of controls such as Button
s, MenuStrip
s, ContextMenuStrip
s, and ToolStrip
s. Using these techniques, you can build some pretty nice forms that use simple code to manipulate properties. So far, however, you've only learned how to use a single form.
In this lesson you learn how to display multiple forms in a single program. You see how to add new forms to the project and how to display one or more instances of those forms. Once you've mastered these techniques, you can make programs that display any number of forms for all kinds of different purposes.
To add a new form to a project, open the Project menu and select Add Windows Form to see the dialog shown in Figure 9.1.
Leave the Windows Form template selected, enter a good name for the new type of form, and click Add. After you click Add, Visual Studio adds the new form type to the project. Figure 9.2 shows the new form in Solution Explorer.
Now you can add Label
s, TextBox
es, Button
s, MenuStrip
s, and any other controls you like to the new form.
When you add a new form to the project, you're really adding a new type of form, not a new instance of that type. If you add the MakeUserForm
type to a project and then run the program, you still only see the original startup form (with the catchy name Form1
) and MakeUserForm
is nowhere to be seen.
Form types such as Form1
and MakeUserForm
are classes. They're like blueprints for making copies of the class, which are called instances. These are important and sometimes confusing topics so I'm going to explain them briefly now and explain them again in greater detail later in the book in the lessons in Section IV.
A class defines the characteristics of any objects from that class. Your code can use the new
keyword to create objects of the class. Once you define the class you can make as many copies (instances) as you like, and every copy is identical in structure to all of the others. Different instances may have different property values but their overall features are the same.
For a form example, suppose you define a MakeUserForm
that has First Name, Last Name, Street, City, State, and ZIP Label
s and TextBox
es. Now suppose your program displays two instances of this form class. Both of the forms will have the same Label
s and TextBox
es, so they have basically the same structure. However, the user can type different values into the two forms.
Your code can also change different instances in various ways. For example, menu items, buttons, and other controls could invoke event handlers that modify the form: change its colors, move controls around, resize the form, or whatever. Here's one of the more potentially confusing features of classes: the code in the event handlers modify the form that is currently running the code.
For example, suppose you build a form that has three Button
s that change the form's BackColor
property to red, green, and blue, respectively, and then you display three instances of the form. When the user clicks the first form's Red button, the event handler makes the first form red but the other forms are unchanged. The code in the event handler is running in the first form's instance so that's the form it affects.
If you then click the Green button on the second form, the event handler changes that form's background color to green. The first form still has its red background and the third form still has its original background color.
Hopefully by now you think I've beaten this topic into the ground and you understand the difference between the class (MakeUserForm
) and the instance (a copy of MakeUserForm
visible on the screen). If so, you're ready to learn how to actually display forms.
The new
keyword creates a new instance of a form. If you want to do anything useful with the form, your code needs a way to refer to the instance it just created. It can do that with a variable. I'm jumping the gun a bit by discussing variables (they're covered in detail in Lesson 11) but, as was the case when I introduced the if
statement in Lesson 8, this particular use of the concept is very useful and not too confusing, so I feel only a little guilty about discussing it now.
In short, a variable is a named chunk of memory that can hold a piece of data. To declare a variable to refer to a form instance, you enter the form's type followed by whatever name you want to give the variable. For example, the following code declares a variable named newUserForm
of type MakeUserForm
:
MakeUserForm newUserForm;
At this point, the program has a variable that could refer to a MakeUserForm
object but right now it doesn't refer to anything. It's like an empty envelope that could hold a MakeUserForm
instance. At this point the variable contains the special value null
, which basically means it doesn't refer to anything.
You can use the new
keyword to create a new instance of the form class. You can then set the variable equal to the new form instance. For example, the following code creates a new MakeNewUser
form and makes the newUserForm
variable point to it:
newUserForm = new MakeUserForm();
Now the variable refers to the new form. The final step is to display the new form. You can do that by calling the new form's ShowDialog
or Show
method.
The ShowDialog
method displays the form modally. That means the form appears on top of the program's other forms and the user cannot interact with the other forms until this form closes.
This is the way dialogs normally work. For example, when you open the Project menu and select Add Windows Form, the Add New Item dialog displays modally so you cannot interact with other parts of the IDE (such as the Properties window, Solution Explorer, or menus) until you close the dialog by clicking Add or Cancel.
The following code displays the form referred to by the variable newUserForm
modally:
newUserForm.ShowDialog();
The Show
method displays the form non-modally. That means the form appears and the user can interact with it or with the program's other forms.
The following code displays the form referred to by the variable newUserForm
non-modally:
newUserForm.Show();
The UserForms example program shown in Figure 9.3 displays a main form with a New User button. Each time you click the button, the program displays a new MakeUserForm
non-modally. Figure 9.3 shows the main form and two MakeUserForm
s.
The following code shows how the UserForms program displays a new MakeUserForm
when you click its button:
private void newUserButton_Click(object sender, EventArgs e)
{
MakeUserForm newUserForm;
newUserForm = new MakeUserForm();
newUserForm.Show();
}
The code declares a variable to refer to the form, creates the new form instance, and displays the instance non-modally.
Each time you click the button, the event handler executes again. Each time it runs, the event handler creates a new version of the variable named newUserForm
, makes a new instance of the MakeUserForm
, and displays that instance, so each time you click the button, you get a new form.
When you create a new form and make a variable to refer to it, you can later use that variable to manipulate the form. There's just one catch: the techniques described so far don't keep the new form variable around long enough to be useful.
For example, the following code defines the newUserForm
variable, makes it point to a new form, and displays the form:
private void newUserButton_Click(object sender, EventArgs e)
{
MakeUserForm newUserForm;
newUserForm = new MakeUserForm();
newUserForm.Show();
}
When the program finishes executing the event handler, the event handler stops running. If the user clicks the button again, the event handler springs back into action.
Unfortunately, when the event handler stops running, it loses its grip on the newUserForm
variable. The next time the event handler runs, it creates a new variable named newUserForm
and works with that one.
This is bad for a program that wants to manipulate the new form later. Because the variable is gone, it can't refer to it so it can't manipulate the form.
The good news is that this is fairly easy to fix. If you move the variable's declaration out of the event handler, the variable exists throughout the program's lifetime. The event handler can make the variable point to a new form, and it can then use the variable later to manipulate that form.
The RemoteForm example program shown in Figure 9.4 uses the following main form code to manage a secondary ColorForm
:
// The remote form we will manipulate.
ColorForm remoteColorForm;
// Create and display the remote form.
private void Form1_Load(object sender, EventArgs e)
{
remoteColorForm = new ColorForm();
remoteColorForm.Show();
}
// Make the color form red.
private void redButton_Click(object sender, EventArgs e)
{
remoteColorForm.BackColor = Color.Red;
remoteColorForm.ForeColor = Color.Pink;
}
The code starts by declaring the variable remoteColorForm
outside of any event handler.
When the program displays the main form, its Load
event handler creates and displays a new ColorForm
.
When the user clicks the main form's Red button, its event handler changes the remote form's BackColor
and ForeColor
properties to red and pink, respectively. The startup form also contains green and blue buttons that have similar event handlers.
The remoteColorForm
variable is declared outside of the event handlers, so the event handlers have access to it. The form's Load
event handler initializes the variable and displays the remote form. The redButton_Click
event handler uses it. Because the variable is declared outside of the event handlers, they can all use it. (Lesson 13 has more to say about when and where variables are available to the code.)
In addition to modifying a remote form's properties, you can change the properties of the controls on that form. You refer to a control by using the form variable, followed by a dot, followed by the control's name.
For example, the bold line in the following code accesses the form referred to by the remoteColorForm
variable. It locates that form's messageLabel
control and changes its Text
property to “I'm red!”:
private void btnRed_Click(object sender, EventArgs e)
{
color_form.BackColor = Color.Red;
color_form.ForeColor = Color.Pink;
color_form.lblMessage.Text = "I'm red!";
}
There's one small catch to this technique: by default the controls on a form are private so the code in other forms can't manipulate at them. You can easily fix this by setting a control's Modifiers
property to Public
, either in the Form Designer or in code. Now other forms can see the control and change its properties.
In this Try It, you create an application similar to the one shown in Figure 9.5. When the user clicks the main form's buttons, the program displays the other forms non-modally.
In this lesson, you:
Load
event handler, add code to create the form instances but don't display the forms.Button
event handlers to display the corresponding secondary forms non-modally.ShowInTaskbar
properties to False
.Font
property and set its Size
subproperty to 12.Button
s. Center them as a group and set their Anchor
properties to None
.GettingThereForm
.
GettingThereForm
and click Add.ShowInTaskbar
property to False
.Label
, ListBox
, and Button
s. Set the ListBox
's Anchor
property to Top, Bottom, Left
. Set the Button
s' Anchor
properties to Bottom, Right
.GettingAroundForm
.
GettingAroundForm
.LodgingForm
.
LodgingForm
.FunStuffForm
.
FunStuffForm
. Leave the CheckBox
es' Anchor
properties with their default values Top, Left
.// The remote forms.
GettingThereForm gettingThereForm;
GettingAroundForm gettingAroundForm;
LodgingForm lodgingForm;
FunStuffForm funStuffForm;
Load
event handler, add code to create the form instances but don't display the forms.
// Initialize the forms but don't display them.
private void Form1_Load(object sender, EventArgs e)
{
gettingThereForm = new GettingThereForm();
gettingAroundForm = new GettingAroundForm();
lodgingForm = new LodgingForm();
funStuffForm = new FunStuffForm();
}
Button
event handlers to display the corresponding secondary forms non-modally.
Button Click
event handlers and make each call the corresponding form variable's Show
method:// Display the getting there form.
private void gettingThereButton_Click(object sender, EventArgs e)
{
gettingThereForm.Show();
}
// Display the getting around form.
private void gettingAroundButton_Click(object sender, EventArgs e)
{
gettingAroundForm.Show();
}
// Display the lodging form.
private void lodgingButton_Click(object sender, EventArgs e)
{
lodgingForm.Show();
}
// Display the fun stuff form.
private void funStuffButton_Click(object sender, EventArgs e)
{
funStuffForm.Show();
}
Brushes.Red
.Background
property.Label
's Foreground
property.Modifiers
property in WPF. (WPF controls don't have that property.)Label
's Modifiers
property to Public
.)ShowInTaskbar
property.)When you close the form, it is destroyed. When you click the button again, the program tries to display the destroyed form and that won't work.
To fix the program, give each of the secondary forms a FormClosing
event handler similar to the following:
private void LodgingForm_FormClosing(object sender,
FormClosingEventArgs e)
{
e.Cancel = true;
Hide();
}
The first statement cancels the close so the form stays open. The second statement makes the form invisible but keeps it alive.
Closing
event.)Button
that says “New Form.” When the user clicks the Button
, display a new non-modal instance of the same kind of form. (What happens when you click the new form's button? What happens if you close the new form? What happens if you make several forms and then close the original one?)Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose
and test the program again.TextBox
named valueTextBox
to the form. Before you display the new form, copy the main form's TextBox
value into the new form's TextBox
. (Hint: You don't need to set the TextBox
's Modifiers
property to Public
because the new form is the same kind as the old one. You need to do this only if a form of one type wants to peek at the controls on a form of a different type.)TextBox
and a “New Form” Button
. When the user clicks the Button
, display a new form of type MessageForm
modally.
The MessageForm
holds two Label
s. The first Label
says “You entered.” The second is blank. When it displays the MessageForm
, the main program should copy whatever is in its TextBox
into the MessageForm
's second label. (Hint: Now you need to set the label's Modifiers
property to Public
.)
PictureForm
showing the image at full scale. Use whatever images you like.
Hints:
PictureBox
es with ScaleMode
set to Zoom
.PictureBox
with Location = (0, 0)
on the PictureForm
. Set its SizeMode
property to AutoSize
.PictureForm
, use the following code to make it fit the PictureBox
it contains:
newPictureForm.ClientSize = newPictureForm.imagePictureBox.Size
PictureWindow
, set the size of the Image
control to match the size of the pictures.PictureWindow
fit the Image
control, set the window's SizeToContent
property to WidthAndHeight
.PictureForm
's background.
You can improve this program by making all four PictureBox
es use the same event handler and making the event handler figure out which image to use.
The event handler's sender
parameter is the control that raised the event, in this case, the PictureBox
that the user clicked. The data type of that parameter is object
, but it actually holds a PictureBox
. You can get a variable that refers to that PictureBox
by using the as
keyword.
The as
keyword tells the program to treat some value (in this case the sender
parameter) as if it were some other type (in this case a PictureBox
). The following code shows how you can get a variable that treats the sender
parameter as a PictureBox
:
PictureBox selectedPictureBox;
selectedPictureBox = sender as PictureBox;
Copy the program you built for Exercise 16. Modify the first event handler so it uses the as
keyword to get a reference to the PictureBox
that the user clicked and then uses that reference to display the correct picture. Then make all of the PictureBox
es share that event handler.