Most developers think of forms as simply containers for controls, which do the actual work of interacting with the
user
. Sometimes this view is accurate, particularly in small applications. But more often, the form does a lot of significant work on its own that must, or at least should, be handled in its top- level central location. This section
briefly
describes the sorts of things that you can do while getting your hands dirty with forms. It refers to the sample application shown in Figure 5-8, called FancySchmancyWindowsFormsDemoVB. (A C# version is available in the sample code, as always.)
Drawing
A form is responsible for maintaining its own appearance. When it determines that a previously unshown portion of the window has become visible (for example, because of the movement or resizing of the form or of other
windows
), the operating system window manager generates a WM_PAINT message that is translated by the managed runtime into an
OnPaint
notification at the form level. The newly uncovered area of the form is said to be
invalid
. Its appearance was not
maintained
by the OS. Instead, it is up to you to write code that
paints
the portion of the form that needs it, as shown in Figure 5-9.
A form is responsible for drawing its own appearance in response to the
OnPaint
notification.
Figure 5-9:
Repainting the invalid area of a form.
You can place your form’s redrawing code in one of two places. You can either override the
OnPaint
method of your form’s class directly, or you can add an event handler for the form’s
Paint
event. While they seem identical from the programming environment’s point of view, they actually
differ
significantly under the hood. The former approach, which is the preferred method, is called directly when the managed code receives the operating system’s notification. The code you write with the latter approach is triggered by the
Control
base class’s implementation of
OnPaint
. This approach uses a delegate and a handler, requiring a certain amount of runtime effort on the part of your program, as we’ll discuss in Chapter 8. By overriding
OnPaint
rather than hooking up an event handler, you burn fewer microseconds. The event actually gets
fired
to any listeners when you pass the
OnPaint
call to your form’s base class, which you do by calling
MyBase.OnPaint
. (Note that IntelliSense may not show this method, but it does exist and you usually want to call it.) This means that by overriding
OnPaint
, you have the flexibility to run your painting code at different times: before invoking the set of registered
OnPaint
event handlers, after invoking them, some of your code before and some after, or even instead of them by not calling the base class at all.
You place your form’s code in the overridden
OnPaint
method of the base class.
The
OnPaint
method of my sample program is shown in Listing 5-5. The runtime
passes
a parameter of type
PaintEventArgs
to this method. I create output on the screen by calling the
methods
of the
Graphics
object that this parameter contains. Petzold managed to fill hundreds of pages describing the interesting things you can do with this object, so I won’t even bother trying to list its methods; but whatever type of drawing you want to do, this is where you go. My sample program draws a vertical line and a horizontal line, dividing the form into quadrants. Drawing a line requires using a pen, a graphics object that I create when my program starts. (See the complete sample code.) The form’s member variable
ClientSize
tells
me the width and height of the form’s client area, the area inside the borders and below the caption bar. Note that the client
size
doesn’t take into account any other controls on the form, such as
toolbars
,
menus
, or status bars. If your form carries these, you’ll have to add your own adjustments for the space they take up. My sample program ignores them.
Your
OnPaint
code draws on the screen by calling methods of the
Graphics
object it gets passed.
Listing 5-5:
Overridden
OnPaint
method of a form.
Protected Overrides Sub OnPaint( _ ByVal e As System.Windows.Forms.PaintEventArgs) ’ Draw vertical line e.Graphics.DrawLine(MyPen, CInt((Me.ClientSize.Width / 2)), 0, _ CInt((Me.ClientSize.Width / 2)), Me.ClientSize.Height) ’ Draw horizontal line e.Graphics.DrawLine(MyPen, 0, CInt((Me.ClientSize.Height / 2)), _ Me.ClientSize.Width, CInt((Me.ClientSize.Height / 2))) ’ Invoke base class. MyBase.OnPaint(e) End Sub
PaintEventArgs
also contains a
ClipRectangle
property. This is a rectangle that tells me which parts of the form need painting. A real-life application would probably check it and touch up only the
parts
of the window that need it, particularly if your drawing code is complex. Mine wasn’t, so I didn’t bother taking the time.
Invalidation
doesn’t occur only when windows move. It can also be done programmatically. My sample application wants to keep the lines centered on the screen in order to form even quadrants, so every time my window’s size changes, I need to erase my lines and
redraw
them. Simply changing my window’s size doesn’t cause that to happen. Dragging the window to make it smaller doesn’t trigger an
OnPaint—
because no new portion of the window has been exposed—so the window manager thinks that nothing has been made invalid. If I drag the window to make it larger, only the newly exposed portions are
considered
invalid, which means the rest of the window isn’t erased and the lines don’t look right. To make the invalidation behavior the way I want it for this sample, I overrode the
OnResize
notification, which is called when my form’s size changes for any reason. In this method (not shown), I call the method
Form.Invalidate()
. This causes the entire form to be invalidated, erasing the background, invoking my
OnPaint
, and triggering my drawing code. This process makes my window look the way I want it to.
Invalidating a window can also be done programmatically.
When you override a base class’s method, you lose its functionality unless you explicitly call the base class’s method in addition to your own. For example, the default functionality of
Form.Resize
repositions anchored child windows (such as the status bar) to their correct location on the newly
sized
form. Try commenting out the call in the sample and you’ll see what I mean. Omitting the call to the base class sometimes gives you the results that you want, but more often in Windows Forms it breaks something else in a complex underlying behavioral chain that you hadn’t thought of. Even though it’s not the default behavior, my
clients
report that they’re happiest when they form the habit of automatically
putting
in the call to their base class’s overridden method unless they’ve thought very
carefully
about it and understand the base class’s functionality thoroughly.
Mouse Handling
The graphical nature of Windows user interfaces means that your form will probably want to track and respond to the user’s mouse behavior in some manner. While this is often done through the use of other controls (such as
buttons
and check boxes), your form will at least occasionally want to handle mouse input on its own. The managed runtime
translates
operating system messages into method calls on your form’s base class
Control
whenever the mouse enters,
leaves
, moves, or clicks within your form. As with painting, you can handle these situations by overriding the base class’s method or by installing an event handler, and you will probably want to choose the former.
The operating system’s mouse messages are translated into managed method calls to tell the form about mouse happenings.
My sample program contains an override of the base class’s
OnMouseDown
method, which gets called when a mouse key is depressed (poor thing). The code is shown in Listing 5-6. The runtime passes my override function an object of type
MouseEventArgs
, describing the state of the mouse at the time the call was made. The object contains such items as the mouse button that the user clicked and the X and Y locations on the form in which the click took place. In the sample program, I check to see whether the button is the left one. If so, I draw a little X at the click location. This drawing is done outside the
OnPaint
handler, so it is erased when the form is invalidated. I get the
Graphics
object to use for drawing by calling the base class’s
CreateGraphics
method.
The operating system passes a
MouseEventArgs
object to tell your code what the user did with the mouse.
Listing 5-6:
OnMouseDown
overridden method to process mouse clicks.
Protected Overrides Sub OnMouseDown( _ ByVal e As System.Windows.Forms.MouseEventArgs) ’ If user clicked left mouse button, then draw a little X at the point If (e.Button = MouseButtons.Left) Then Dim gr As Graphics = CreateGraphics() gr.DrawLine(Pens.Black, e.X - 2, e.Y - 2, e.X + 2, e.Y + 2) gr.DrawLine(Pens.Black, e.X - 2, e.Y + 2, e.X + 2, e.Y - 2) ’ If user clicked right mouse button, then make adjustments ’ to context menu before form shows it ElseIf (e.Button = MouseButtons.Right) Then ’ Handle right mouse button click § End If End Sub
Menu Handling
The main menu is an important piece of any user interface. It is the primary means through which new users discover your application’s functionality, and it is the primary contact point throughout the lifetime of the beginner and intermediate users who make up 90 percent or more of your user population. It’s important to design your menu well. The main menu on a Windows form is a control that you select from the control toolbox, just like any other type of control. You place menu items on the menu using the editor, typing in their text and setting their properties, as shown in Figure 5-10. You can also do this programmatically at run time, as I’ll
demonstrate
later. The form property Menu specifies which menu (your application can contain more than one) is shown as the form’s main menu, so you can replace it at run time.
A form’s main menu is just another component that you place on the form using Visual Studio.
Figure 5-10:
Adding a menu and menu items to a form.
Each menu item is its own distinct object with its own properties. Think of it as its own separate child control within its parent—the main menu— which itself is attached to the form. The properties of a menu item include such old
friends
as
Text
,
Enabled
, and
Checked
. A menu item also contains a property named
Shortcut
, which allows you to
designate
a key combination that invokes the action of the menu item, such as Ctrl+X for Cut. A menu item fires the
Click
event when the user selects the item. You add a handler for this event to your form and put code in it, just as you would for any other event from any other control.
Each menu item is a separate child object of the main menu.
A context menu is a small pop-up menu that an application shows when the user clicks the right mouse button. It
generally
contains items that provide actions appropriate to the location on which the user clicked. A context menu is another type of control you can add to your form from the toolbox, adding menu items to it and setting their properties. A form automatically shows its context menu when the user clicks the right mouse button. The form’s
ContextMenu
property designates which of the (
potentially
many) context menus within the application the form should show. Because context menus can vary greatly from one location to another, you will often find yourself changing this property, changing the items within the context menu, or both. The sample program
demonstrates
both in Listing 5-7. When my form handles the
OnMouseDown
notification, it checks to see whether the right mouse button was pressed. It then figures out if the click was on the right or left half of the window and sets the
ContextMenu
property accordingly. It then figures out if the click was on the upper or lower half of the window and modifies the last item of the context menu
accordingly
. These modifications take place before the form’s base class
Control
automatically shows the context menu.
The form automatically shows a context menu when the user clicks the right mouse button.
Listing 5-7:
Code modifying context menu before form automatically shows it.
{% if main.adsdop %}{% include 'adsenceinline.tpl' %}{% endif %}
ElseIf (e.Button = MouseButtons.Right) Then ’ Figure out if we’re on left or right side of screen. ’ Set form’s context menu accordingly If (e.X < ClientSize.Width / 2) Then Me.ContextMenu = ContextMenu1 Else Me.ContextMenu = ContextMenu2 End If ’ Check for upper or lower half of window. ’ Modify last item of selected context menu accordingly If (e.Y < ClientSize.Height / 2) Then Me.ContextMenu.MenuItems(3).Text = "Upper" Else Me.ContextMenu.MenuItems(3).Text = "Lower" End If End If
Keyboard Handling
The runtime calls the
OnKeyDown
method of the window that has input focus when the user depresses a key, and it calls the
OnKeyUp
method when the user releases it. The runtime passes an object of type
KeyEventArgs
to tell you which key the user has pressed and whether any of the modifier keys such as Ctrl, Shift, or Alt was pressed as well. The key code covers all the keys on the standard Windows keyboard, thereby allowing you to differentiate between, say, the cursor movement keys on the numeric keypad and those on the inverted T to its left if you so
desire
. If the key translates to an ASCII character, you will also receive an
OnKeyPress
notification, containing a different set of arguments. The sample program processes the
OnKeyDown
notification and places descriptive information into the status bar. The code is shown in Listing 5-8.
The operating system calls your form’s
OnKeyDown
method when the user presses a key and your form has the input focus.
Listing 5-8:
Code handling
OnKeyDown
notification.
‘ User pressed a key Protected Overrides Sub OnKeyDown(ByVal e As _ System.Windows.Forms.KeyEventArgs) ’ Place information in status bar describing pressed key ’ and state of modifier keys. StatusBar1.Text = ("Received OnKeyDown, key = " + e.KeyCode.ToString _ + " modifiers = " + e.Modifiers.ToString) End Sub
Dialog Boxes
The main form will often want to pop up dialog boxes to interact with the user. Dialog boxes are
themselves
forms, which you create in the designer and populate with controls as you would for any other form. To make buttons provide the OK and Cancel functionality that the user expects of a dialog box, set the button property
DialogResult
to the appropriate value. To pop a dialog box up on the screen, the parent form creates an instance of the dialog form and calls the method
ShowDialog
. The return value of this method indicates the button that the user clicked to dismiss the dialog. If the user clicked OK, the parent reads the information from the controls on the dialog form. The code is shown in Listing 5-9:
You use forms as dialog boxes, popping them up via the method
Form.ShowDialog
.
Listing 5-9:
Code that displays a dialog box to the user and
reports
its results.
Private Sub MenuItem12_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MenuItem12.Click ’ Create new dialog box form Dim dlg As New Form2() ’ Pop up dialog box on screen If (dlg.ShowDialog() = DialogResult.OK) Then ’ If user clicks OK, read value that he entered ’ and show to him MessageBox.Show("User clicked OK, textBox contains: " + _ dlg.TextBox1.Text) Else ’ If user clicks Cancel, report that ’ fact to user. MessageBox.Show("User clicked Cancel") End If End Sub