Where Are We?
This chapter deals with topics that are often dialog-
Chapter 4. Drawing Basics
As handy as forms are,
Note that all the drawing techniques discussed in this chapter and in the
One more thing worth noting before we begin is that the System.Drawing namespace is implemented on top of GDI+ (Graphics Device Interface+), the successor to GDI. The original GDI has been a mainstay in Windows since there was a Windows, providing an abstraction over screens and printers to make writing GUI-style applications easy.
GDI+ is a Win32 DLL (gdiplus.dll) that ships with Windows XP and is available for older versions of Windows. GDI+ is also an unmanaged C++ class library that wraps gdiplus.dll. Because the System.Drawing classes share many of the same
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
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
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,
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
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
To avoid this delay, use the Update method, which forces Windows to handle the Paint event immediately. Because
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
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,
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.