updating code from old canvas.graphics to Paint for drawing

You’ll have to pardon me because I’m just getting back into Xojo after being away many years. I popped open a VERY old (like early 2000s REALBasic old – don’t judge me!) project to begin updating it. Most of it has been pretty straightforward but I’m a little confused about switching from using Canvas for redrawing things to now using Paint.

This is basically a spreadsheet app. The app’s main window’s Paint event only calls a “RedrawGrid” method. This RedrawGrid method consists of a bunch of smaller methods for drawing each element separately: lines, text in each cell, row / column headings, etc. When I compile my old code I get alerts that my old code (Spreadsheet.graphics.ForeColor, .FillRect, .TextFont, etc.) has been deprecated and I should be using Paint instead of canvas.graphics.

What’s the most straightforward way of updating my code? I don’t have to actually move any of this code into the Window’s Paint event, do I? Do I add Paint subroutines to my old methods, or pass some parameter to them, or what?

I know the answer is going to be spectacularly easy, but like I said I’ve been away a while and I just keep getting stuck. Searching for some before and after RB code examples hasn’t turned much up either. Any help is greatly appreciated.

The Paint event receives a reference to a Graphics object (g As Graphics) and you should replace all calls to Canvas.Graphics with calls to this (special, optimized) Graphics object which represents the drawable area of the Canvas. Don’t store the special graphics object, but you can pass it to other methods called from within Paint.

This may or may not require significant changes to your existing design.

Xojo also passes an array of Rect objects which describe the parts of the Canvas that need updating, so you could optimize your design to draw only what needs redrawing (or you can ignore the areas parameter.)

Joe, you need to draw only in response to a Paint event. That’s what it boils down to. I’ve seen a lot of code that draws in response to other events, such as MouseDown. It is often quite difficult to just move stuff and make it work, and as Andrew said, may require you to rethink your design.

Keep this in mind. When the framework calls your Paint event, it is asking you to render the Canvas the way it should look right then. So you’ll need to look at the state of your data and decide how/what to render. This is a little backwards from drawing incrementally every time the state changes.

Keep in mind that you’ll need to call canvas.invalidate(x,y,width,height) when an area needs updating or just canvas.invalidate(). The paint event will then fire with all of the rects you’ve invalidated (or none if it’s the entire control) at the next opportunity. RefreshRect is available, but forces the update and can slow down the overall performance of the app. So you should be able to change to call RedrawGrid(g as graphics) to draw the data from inside the paint event and where you had been calling redrawgrid, call invalidate.

Hello guys,

How was your problem fixed ? I do have a project wrote in RealBasic that i have to convert to XOJO and it seems that it is a lot of headache to make the conversion, and in the debugger does not give to much details just that i have to replace the Canvas.Graphics with the Paint but no clear details and all the test that i tried non of them working. It would be nice to have some clear docs about this or at least an conversion guide.

Thanks , any updates are welcomed.

Start by finding all the places where you are doing canvas1.graphics.drawpicture or similar , and replace them with canvas1.invalidate

inside the canvas1.paint event, draw everything
Draw the lines, text, etc
you may need to have pictures and object2d items as properties of the window in order to get at them from inside the paint event.

Or you may create a canvas subclass that has properties such as thePicture, theText, theArrayOfLines()
and in the paint event draw them to the g object you are passed.

There is still a way to use drawing out of Paint, akin to Canvas.graphics :

  • Add a picture property to the window (p as picture)
  • In the Canvas Open, place this :

Sub Open() p = new picture(me.width,me.height) me.backdrop = p End Sub

Then you can draw in p.graphics the way you did in Canvas.Graphics in the past, out of the Paint event. Don’t forget to call invalidate afterward to update. That should help you use your older code with a minimum of pain.

Sub Action() p.graphics.FillRect(0,0,100,100) Canvas1.Invalidate End Sub

thanks guys,

Well it seems that it was an old customised calendar made of a canvas and i have a lot of methods that have a lot of

me.graphics.foreColor = &c000000
me.Graphics.Bold = False
me.Graphics.TextFont = “smallSystem”
me.Graphics.TextSize = 11
me.Graphics.DrawString(dayTitle, 14 + (i-1) * 17, 28)
me.graphics.foreColor = &cBBD1E7
me.graphics.fillPolygon
me.graphics.drawPicture(p, 0, 0)

me being the canvas so in total i have 44 like this, most of them use either forecolor, or the ones mentioned above, so i`m looking if it easy to convert than i will look on this otherwise try to create another calendar from scratch and to use other controls .

Unfortunately the docs don`t help to much so i have to ask help here.

and by the way, Happy Holidays to everybody and thanks for your help.

[quote=155032:@Aurelian Negrea]thanks guys,

Well it seems that it was an old customised calendar made of a canvas and i have a lot of methods that have a lot of

me.graphics.foreColor = &c000000
me.Graphics.Bold = False
me.Graphics.TextFont = “smallSystem”
me.Graphics.TextSize = 11
me.Graphics.DrawString(dayTitle, 14 + (i-1) * 17, 28)
me.graphics.foreColor = &cBBD1E7
me.graphics.fillPolygon
me.graphics.drawPicture(p, 0, 0)

me being the canvas so in total i have 44 like this, most of them use either forecolor, or the ones mentioned above, so i`m looking if it easy to convert than i will look on this otherwise try to create another calendar from scratch and to use other controls .

Unfortunately the docs don`t help to much so i have to ask help here.

and by the way, Happy Holidays to everybody and thanks for your help.[/quote]

From the example I gave you, all you need to do is a replace of me.graphics to p.graphics. Plus add me.invalidate at the end of the methods to update the UI.

It should work immediately. That said, if as the title of this thread indicate, what you want to do is instead move the code into the Paint event, starting from scratch is probably a better bet.

Well i have one method for example that is used to draw background where i have something like that :

Method Name : drawBackground

dim rowIdx as integer
dim g as graphics
dim p as picture
g = me.graphics
p = new picture(g.width, g.height, 32)

p.graphics.foreColor = &cDADADA

for rowIdx = 0 to 5
p.graphics.fillRoundRect(2, 32 + (rowIdx * 16), 138, 14, 14, 14)
next

me.graphics.drawPicture(p, 0, 0)

where me is the canvas , so it is little bit confusing, the p is already declared and all where it gives error is the me part .

Some of them like Draw Day method does not have declared the picture part so if i change all me with p and press command K it throws me errors for all the p that are not found and i get more confuse .

[quote=155033:@Michel Bujardet]From the example I gave you, all you need to do is a replace of me.graphics to p.graphics. Plus add me.invalidate at the end of the methods to update the UI.

It should work immediately. That said, if as the title of this thread indicate, what you want to do is instead move the code into the Paint event, starting from scratch is probably a better bet.[/quote]

Well i guess it will be the best solution instead of getting the headache of fixing something , i prefer to make another one and try to make it future proof also.

Thanks again.

From the little you posted, the older code looks confused enough. A new project will be a lot cleaner, hopefully… Good luck :slight_smile:

Hello Michel, Well it seems that writing the full project again it takes more than expected so i will try to patch it as much as i can.

It seems that some of the issues are in the PreviewCanvas 1.1, By Alex Restrepo and i dont have any update from the internet about this being translated to XOJO, the part is that most of the code is about canvas invoking the Graphics part, is there a way of using that Deprecated part or not ? or to invoke Graphics from outside the Paint part. so far the preview let-s say that i use your example and put a picture there and edit the rest, that would go, but for the calendar part i have a lot of drawing and methods that are invoking that part and it seems to be a small hell to convert old RB in new XOJO, at least it was nice to have like a converter helper to do that , but anyway we come back to the old issues that i dont want to remember again.

What you can do is create a Canvas subclass with it’s own method named Graphics. Then, in your old code base that’s littered with the likes of “myCanvas.Graphics.ForeColor = &c000000”, switch the Canvas supers to this new class. Now myCanvas.Graphics is calling your method which is fine and not deprecated. The implementation returns the Graphics property of a Picture used to back the Canvas and drawn in Paint. The same thing Michel Bujardet is talking about except you don’t have to rewrite to use p.

Here’s the proof of concept I tested. With a regular Canvas the code “MyCanvas.Graphics.FillRect(9,9,9,9)” is marked deprecated but still does run (for me). Switching MyCanvas’ super to OldCanvas doesn’t flag deprecation and runs the same. Well, there’s a difference in refreshing. With the deprecated Graphics the drawing shows up automatically, using this idea you need to signal that that needs to happen. You can do that by uncommenting the Invalidate below, and it works in my test, but may not catch all cases/uses. It might be better to just find in your code where drawing is ‘done’ and add Invalidates there.

[code]Class OldCanvas inherits Canvas

Property theBackingPic As Picture

Sub Open() //Event Handler
initBacking
End Sub

Sub Paint(g As Graphics, areas() As REALbasic.Rect) //Event Handler
if theBackingPic = nil or theBackingPic.Width <> self.Width or theBackingPic.Height <> self.Height then initBacking
g.DrawPicture(theBackingPic, 0, 0)
RaiseEvent Paint(g, areas)
End Sub

Function Graphics() As Graphics
if theBackingPic = nil then initBacking
//Invalidate
return theBackingPic.Graphics
End Function

Private Sub initBacking()
dim temp As Picture = theBackingPic
theBackingPic = new Picture(self.Width, self.Height)
if temp <> nil then theBackingPic.Graphics.DrawPicture(temp, 0, 0)
End Sub

Sub Paint(g As Graphics, areas() As Realbasic.Rect) //Event Definition

End Class[/code]

There is yet another way. Let us say I have an old canvas called Canvas1 which I draw to using Canvas1.Graphics.xxx.

  • Rename Canvas1 to TheCanvas1 (or whatever you like)
  • Add a Canvas1 Picture property to your window or a module
  • In the open event of TheCanvas1, put :

Canvas1 = New Picture(TheCanvas1.Width, TheCanvas1.Height,32) // 32 not required on Mac Me.Backdrop = Canvas1

Then automagically, all your code that wrote directly to Canvas1.Graphics will instead write to the backdrop picture. Normally you will not need to modify the older code, except to add TheCanvas.Invalidate at the end of the drawing processes, to update the canvas.

I just tested it, and the result is amazingly simple.

It’s been a long time since I used Canvas.Graphics, the way I remember using it was I had to juggle all my draw routines from those places that might dirty it, that drawing was on top of old drawing so you needed to clear or fill before a new drawing, but resizing would erase the contents. I think this version more closely matches that behavior (if that’s the behavior to match) and is simpler using Backdrop.

[code]Class OldCanvas inherits Canvas

//Event Definitions
Sub Open()
Sub Paint(g As Graphics, areas() As Realbasic.Rect)

//Event Handlers
Sub Open()
Backdrop = new Picture(Width, Height)
RaiseEvent Open()
End Sub

Sub Paint(g As Graphics, areas() As REALbasic.Rect)
if Backdrop = nil or Backdrop.Width <> Width or Backdrop.Height <> Height then
Backdrop = new Picture(Width, Height)
end
RaiseEvent Paint(g, areas)
End Sub

//Method
Function Graphics() As Graphics
if Backdrop = nil then Backdrop = new Picture(Width, Height)
Invalidate //*maybe don’t use
return Backdrop.Graphics
End Function

End Class

//Test in a window

//Canvas1 event (super is OldCanvas)
Sub Open()
draw //initial drawing
End Sub

//Window event
Sub Resizing()
draw //Canvas contents erased, redraw
End Sub

//Pushbutton1 event
Sub Action()
y = y + 1 //change drawing parameter
draw //redraw
//Invalidate //*will need to invalidate here if not invalidating in OldCanvas
End Sub

//Window method
Private Sub draw()
Canvas1.Graphics.ClearRect(0, 0, Canvas1.Width, Canvas1.Height)
Canvas1.Graphics.FillRect(20, 100+80*sin(y), 90, 90)
End Sub

//Window property
Property y As Integer[/code]

Thanks guys, that was helpful, i`m updating the code now and i will let you know how it goes.

As a different subject, i`m checking now on the forum for it, any idea how to interpret Crash Logs from apple ? is there any tool in Xcode to do that or other utility ? or you have to just look on that dump log and guess what is the problem ?

Thanks again.

[quote=156925:@Michel Bujardet]here is yet another way. Let us say I have an old canvas called Canvas1 which I draw to using Canvas1.Graphics.xxx.

Rename Canvas1 to TheCanvas1 (or whatever you like)
Add a Canvas1 Picture property to your window or a module
In the open event of TheCanvas1, put :[/quote]

Bonjour,
Je n’ai pas très bien compris la question, je suppose qu’il s’agisse de dessiner et d’effacer les figures graphique sur un canevas.
Si c’est le cas, j’aimerais en savoir plus avec, un exemple si possible.
merci.

Maybe you should ask your own question in the Franais Channel.