Canvas.Paint

From Xojo Documentation

Event


Canvas.Paint(g as Graphics, areas() As Realbasic.Rect)

Supported for all project types and targets.

The area needs to be redrawn, such as when it has been covered by another window and then uncovered. Use the g parameter for all drawing. The areas parameter contains the areas of the Canvas that need redrawing. If areas is empty then the entire Canvas needs to be redrawn.

Notes

fa-exclamation-circle-32.png
Directly accessing the Graphics property of the Canvas has been removed as of 2018r3. Use this event instead for all drawing.

The g parameter gives you access to the Graphics class methods for drawing. Use the Paint event handler for all drawing. You can also use a method called from the Paint event handler, but be sure to pass the Graphics object from the Paint event to your method.

You can continue to have code outside the Paint event handler draw directly to your own Picture properties. To display the Picture, simply draw it to the Canvas in the Paint event handler (using g.DrawPicture).

On Windows only, the EraseBackground property determines whether the entire Canvas background is erased before repainting.

Do not call Invalidate or Refresh from within the Paint event as this can cause an infinite loop and eventual app crash.

Redrawing Areas

By redrawing just the specific areas that have changed, you can significantly improve your drawing performance. When you call Invalidate (specifying the rect to invalidate), the areas parameter contains the coordinates for the rectangles that need redrawing. You can then choose to redraw just those parts of the Graphics or you can continue to redraw everything as you have done in the past. This parameter may also contain coordinates that do not come directly from Invalidate calls as the operating system is ultimately in control of drawing.

When this event is called as a result of an Invalidate call, the drawing is clipped to only the invalidated regions regardless of whether you use the areas parameter.

Sample Code

This code draws a 3D rectangle with a raised look:

Sub Paint(g As Graphics)
Const White = &cffffff
Const DarkGray = &c8c8c8c
Const LightGray = &cefefef

g.ForeColor = White
g.DrawLine(1, 1, Me.Width, 1)
g.DrawLine(1, Me.Height - 1, 1, 1)
g.ForeColor = DarkGray
g.DrawLine(Me.Width - 1, 2, Me.Width - 1, Me.Height)
g.DrawLine(1, Me.Height - 1, Me.Width, Me.Height - 1)

//fill in the light gray rectangle
g.ForeColor = LightGray
g.FillRect(2, 2, Me.Width - 3, Me.Height - 3)

This code uses the Paint event handler of a Canvas control to draw the text "The quick brown fox" in Helvetica bold, italic, 18 point, 50 points from the top of and 10 points from the left side of the control:

Sub Paint(g As Graphics)
g.Bold = True
g.Italic = True
g.TextFont = "Helvetica"
g.TextSize = 18
g.DrawString("The quick brown fox", 10, 50)

This code assigns the color of a particular point in a Canvas control to a variable:

Sub Paint(g As Graphics)
Dim c As Color
c = g.Pixel(10, 10)

This code sets a particular point of a Canvas control to a specific RGB value:

Sub Paint(g As Graphics)
g.Pixel(10, 10) = RGB(100, 105, 225)

This is code draws a triangle in a Canvas. It is placed in the Paint event. The parameter g as Graphics is passed into this event:

Sub Paint(g As Graphics)
Dim points(6) As Integer
points(1) = 10 // X of Point 1
points(2) = 10 // Y of Point 1
points(3) = 75 // X of Point 2
points(4) = 30 // Y of Point 2
points(5) = 10 // X of Point 3
points(6) = 125 // Y of Point 3
g.ForeColor = RGB(100, 200, 255)

This code uses the Clip method to define child Graphics items within the parent Canvas. The code is in the Paint event of a Canvas. The two clippings define regions at the top of the canvas and the DrawOval method draws object in each one. Notice that the first call tries to draw an oval that is wider than the region. It is truncated in the drawing.

Sub Paint(g As Graphics)
Dim myClip As Graphics = g.Clip(0, 0, 150, 15)
Dim myClip2 As Graphics = g.Clip(150, 0, 150, 15)

// draw the border of the Canvas..
g.ForeColor = &c000000
g.DrawRect(0, 0, g.Width, g.Height)

// draw into the first area...
myClip.ForeColor = &cff0000
myClip.DrawRect(0, 0, myClip.Width, myClip.Height) // draw the border of the area..
myClip.DrawOval(0, 0, 200, 15) // the oval does not appear outside the region despite the call

// draw into the second area...
myClip2.ForeColor = &c0000ff
myClip2.DrawRect(0, 0, myClip2.Width, myClip2.Height) // draw the border of the area
myClip2.DrawOval(0, 0, 150, 15)

Creating a Custom Control

This simple Canvas control changes from black to white when you click on it.

Drag a Canvas control into a window. Add a Boolean property (mBlack) to the Window. This is used toggle the color of the Canvas when it is clicked.

The code for the Canvas MouseDown event handler looks like this:

Function MouseDown(X As Integer, Y As Integer) As Boolean
mBlack = Not mBlack // Toggle the color

Me.Invalidate(False) // Tell the Canvas to draw itself
End Function

This code toggles the mBlack value when the Canvas is clicked and then tells the Canvas to redraw itself. In the Paint event handler, you draw the rectangle setting the color based on the value of mBlack:

Sub Paint(g As Graphics)
If mBlack Then
g.ForeColor = &c000000
Else
g.ForeColor = &cffffff
End If

g.FillRect(0, 0, g.Width, g.Height)
End Sub

Optimization to draw only in the given areas

Add the following functions to a class or module:

Function IsWithinRect(left As Integer, top As Integer, width As Integer, height As Integer, rect As REALbasic.Rect) As Boolean
left = Max (left, rect.Left)
top = Max (top, rect.Top)
Dim right As Integer = Min(left + width, rect.Left + rect.Width)
Dim bottom As Integer = Min(top + height, rect.Top + rect.Height)

Return (left < right) And (top < bottom)
End Function

Function IsWithinRects(left As Integer, top As Integer, width As Integer, height As Integer, rects() As REALbasic.Rect) As Boolean
For Each r As REALbasic.Rect In rects
If IsWithinRect(left, top, width, height, r) Then
Return True
End If
Next
End Function

With that, you can test if an object you want to draw within a particular position needs drawing at all, possibly speeding up the drawing process:

Sub Paint(g As Graphics, areas() As REALbasic.Rect)
// Draw an image at position 100 / 100, but only if needed
If areas.Ubound < 0 Or IsWithinRects(100, 100, theImage.Width, theImage.Height, areas) Then
g.DrawPicture(theImage, 100, 100)
End If

See Also

Window.Paint event