Introducing Microsoft .NET (Pro-Developer)

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.)

A (relatively) advanced form example starts here.

Figure 5-8: The FancySchmancyWindowsFormsDemoVB sample application.

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.

Tips from the Trenches

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.

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

Категории