Drawing to the Screen


No matter what kind of drawing you're doing, the underlying abstraction that you're dealing with is the Graphics class from the System.Drawing namespace. The Graphics class provides the abstract surface on which you're drawing, whether the results of your drawing operations are displayed on the screen, stored in a file, or spooled to the printer. The Graphics class is too large to show here, but we'll come back to it again and again throughout the chapter.

One way to obtain a Graphics object is to use CreateGraphics to create a new one associated with a form:

 
 Dim drawEllipse As Boolean = False Sub drawEllipseButton_Click(sender As Object, e As EventArgs) _       Handles drawEllipseButton.Click   ' Toggle whether or not to draw the ellipse   drawEllipse = Not(drawEllipse)   Dim g as Graphics = Me.CreateGraphics()   Try       If drawEllipse Then          g.FillEllipse(Brushes.DarkBlue, Me.ClientRectangle)       Else          g.FillEllipse(SystemBrushes.Control, Me.ClientRectangle)       End If   Finally       g.Dispose()   End Try End Sub 

After we have a Graphics object, we can use it to draw on the form. Because we're using the button to toggle whether or not to draw the ellipse, we either draw an ellipse in dark blue or use the system color as the background of the form. That's all fine, but you may wonder what the Try-Finally block is for.

Because the graphics object holds an underlying resource managed by Windows, we're responsible for releasing the resource when we're finished, even in the face of an exception, and that is what the Try-Finally block is for. Even though .NET implements a global garbage collection mechanism for cleaning up objects, there is still a need for programmers to explicitly release resources when they are finished. The .NET garbage collector releases only managed resources, or resources controlled by the runtime. Resources that are managed by Windows itself, however, are not managed in the .NET sense, and therefore the garbage collector can leave them in an indefinite state by cleaning up the objects that use them without releasing them explicitly.

The Graphics class, like many classes in .NET, implements the IDisposable interface. When an object implements the IDisposable interface, that's a signal for the client of that object to call the IDisposable Dispose method when the client is finished with the object. This lets the object know that it's time to clean up any resources it's holding, such as a file or a database connection. In this case, the Graphic class's implementation of IDisposable.Dispose can release the underlying graphics object that it's maintaining. Then, when the garbage collector cleans up the Graphics object, the underlying unmanaged resource has already been released.

Handling the Paint Event

After we've got the Graphics resources managed properly, we have another issue: When the form is resized or covered and uncovered, the ellipse is not automatically redrawn. To deal with this, Windows asks a form (and all child controls) to redraw newly uncovered content via the Paint event, which provides a PaintEventArgs argument:

 
 Class PaintEventArgs   Public Property ClipRectangle() As Rectangle   Public Property Graphics() As Graphics End Class 

The PaintEventArgs comes with two properties: a rectangle describing the area Windows would like us to draw, and a graphics object that Windows itself will manage, relieving us of that responsibility. Because of this event, conventionally we manage drawing code for a form in the Paint event itself by using the state of the form to govern what's drawn:

 
 Dim drawEllipse As Boolean = False Sub drawEllipseButton_Click(sender As Object, e As EventArgs) _   Handles drawEllipseButton.Click   drawEllipse = Not(drawEllipse) End Sub Sub DrawingForm_Paint(sender As Object, e As PaintEventArgs) _   Handles MyBase.Paint   If Not(drawEllipse) Then Exit Sub     Dim g As Graphics = e.Graphics     g.FillEllipse(Brushes.DarkBlue, Me.ClientRectangle) End Sub 

By the time the Paint event is fired , the background of the form has already been drawn, [3] so any ellipse that was drawn during the last Paint event will be gone; this means that we must draw the ellipse only if the flag is set to true. However, even if we set the flag to draw the ellipse, Windows doesn't know that the state of the flag has changed, so the Paint event won't be triggered and the form won't get to chance to draw the ellipse. To avoid the need to draw the ellipse in the button's Click event as well as the form's Paint event, we must request a Paint event and let Windows know that the form needs to be redrawn.

[3] A form or control can draw its own background by overriding the OnPaintBackground method.

Triggering the Paint Event

To request a Paint event, we use the Invalidate method:

 
 Sub drawEllipseButton_Click(sender As Object, e As EventArgs)   DrawEllipse = Not(drawEllipse)   Me.Invalidate(True) ' Ask Windows for a Paint event                        ' for the form and its children End Sub 

Now, when the user toggles the flag, we call Invalidate to let Windows know that a part of the form needs to be redrawn. However, because drawing is one of the more expensive operations, Windows will first handle all other events ”such as mouse movements, keyboard entry, and so on ”before firing the Paint event, just in case multiple areas of the form need to be redrawn at the same time.

To avoid this delay, use the Update method, which forces Windows to handle the Paint event immediately. Because invalidating and updating the entire client area of a form are so common, forms also have a Refresh method that combines the two:

 
 Sub drawEllipseButton_Click(sender As Object, e As EventArgs) _   Handles drawEllipseButton.Click   drawEllipse = Not drawEllipse   ' Can do one or the other   Me.Invalidate() ' Ask Windows for a Paint event   Me.Update() ' Force the Paint event to happen now   ' Or both at once   Me.Refresh() ' Invalidate + Update End Sub 

However, if you can wait, it's best to let Windows handle the Paint event in its own sweet time. It's delayed for a reason: It's the slowest thing that the system does. Forcing all paints to happen immediately eliminates an important optimization.

If you've been following along with this simple example, you'll be pleased to see that pressing the button toggles whether or not the ellipse is shown on the form and that covering and uncovering the form draws as expected. However, if you resize the form, you'll be disappointed, as shown in Figures 4.1 and 4.2.

Figure 4.1. Ellipse Form before Resizing

Figure 4.2. Ellipse Form after Resizing

Notice that in Figure 4.2, it seems as if the ellipse is being drawn several times, but incompletely, as the form is resized. What's happening is that as the form is being expanded, Windows is drawing only the newly exposed rectangle, assuming that the existing rectangle doesn't need to be redrawn. Although we're redrawing the entire ellipse during each Paint event, Windows is ignoring everything outside the clip region ” that part of the form that needs redrawing ”and that leads to the strange drawing behavior. Luckily, you can set a style to request that Windows redraw the entire form during a resize:

 
 Public Sub New  ()  ' This call is required by the Windows Form Designer.  InitializeComponent()  ' Trigger a Paint event when the form is resized   Me  .SetStyle(ControlStyles.ResizeRedraw,  True  )  End Sub 

Forms (and controls) have several drawing styles (you'll see more in Chapter 6: Advanced Drawing). The ResizeRedraw style causes Windows to redraw the entire client area whenever the form is resized. Of course, this is less efficient, and that's why Windows defaults to the original behavior.



Windows Forms Programming in Visual Basic .NET
Windows Forms Programming in Visual Basic .NET
ISBN: 0321125193
EAN: 2147483647
Year: 2003
Pages: 139

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net