I am outlining an approach that I am taking to this conversion to help anyone facing this particular issue, to encourage more elegant solutions, and invite criticism of my decision. My thanks to previous contributors to this thread. This is more complex than just doing all the drawing to a Picture Object but has the advantage of creating slightly cleaner graphics and possibly being faster. (See McGrath above)
Converting custom Canvas from old (directly address Graphics property of canvas) to the new (use Canvas Paint Event).
The original (old) project had ~ 30 methods that were part of the custom canvas (cnGraph). These methods instructed various “things” to be drawn on the canvas. For illustrative purposes, consider a simplified example: (a customCanvas with two methods). cnGraph has these two methods
Public DrawBlueLineOld()
graphics.ForeColor=RGB(0,0,255)
graphics.DrawLine(70,70,80,200)
graphics.ForeColor=RGB(0,0,0)
End Sub
Public DrawRedLineOld()
graphics.ForeColor=RGB(255,0,0)
graphics.DrawLine(0,0,70,70)
graphics.ForeColor=RGB(0,0,0)
End Sub
Imagine a window on which we have a canvas (cnTest) which is an instance of the custom canvas (cnGraph). We have a button (OldBlue) and a button (OldRed). The action events are
Sub Action() Handles Action
cnTest.DrawBlueLineOld
End Sub
Sub Action() Handles Action
cnTest.DrawRedLineOld
End Sub
It works great but this is deprecated. So trying to move to the preferred way which is to have the code in the Paint event of the custom canvas. But how am I going to handle having ~ 30 methods to do different things all living in the Paint event? So I try this:
- Add an integer property to the custom canvas cnGraph named whichMethod
- Add two methods to the custom canvas cnGraph
Public Sub DrawBlueLineNew01(g As Graphics)
g.ForeColor=RGB(0,0,255)
g.DrawLine(70,70,80,200)
g.ForeColor=RGB(0,0,0)
End Sub
Public Sub DrawRedLineNew02()
g.ForeColor=RGB(255,0,0)
g.DrawLine(0,0,70,70)
g.ForeColor=RGB(0,0,0)
End Sub
3.Add a Paint event handler to the custom canvas cnGraph. So the paint event is just going to be a Case/Select directing the Paint event to the desired method.
Select Case whichMethod
Case 1
Me.DrawBlueLineNew01
Case 2
Me.DrawRedLineNew02
End Select
Make two new buttons (NewBlue) and a button (NewRed). The action events are
Sub Action() Handles Action
cnTest.whichMethod = 1 // draw blue line
Self.cnTest.Invalidate // force Paint event
End Sub
Sub Action() Handles Action
cnTest.whichMethod = 2 // draw red line
Self.cnTest.Invalidate // force Paint event
End Sub
Well, this is a little more complex. We have to associate each method with an integer so the Select/Case in the Paint Event will fire off the “correct” method. But it works. We push the NewBlue button and a blue line appears. We push the NewRed button and a red line appears.
New Problem: Persistance. With the old way of directly addressing the graphics property of the canvas, we would get a blue line and then a red line and both would be visible. With the new way, when I push the NewRed button the existing blue line disappears. So somehow, we have to “remember” all the graphic methods that have been called on the canvas so when the canvas is “redrawn” all these elements are redrawn.
So lets create a property that is a stack of all the graphic methods that have been called. An property that is an integer array and we’ll call it aiDrawStack. This will “remember” all the methods that have been applied. Change the Paint event handler to run through all of these. And change the code in the NewBlue and NewRed buttons slightly.
[code]Sub Paint(g As Graphics, areas() As REALbasic.Rect) Handles Paint
For nIndex As Integer = 0 To aiDrawStack.Ubound
Select Case aiDrawStack(nIndex)
Case 1
Me.DrawBlueLineNew01(g)
Case 2
Me.DrawRedLineNew02(g)
End Select
Next nIndex
End Sub[/code]
Sub Action() Handles Action
Self.cnTest.aiDrawStack.Append(1) // add a blue line to the stack (Graphic Method 1)
Self.cnTest.Invalidate // force Paint event
End Sub
Sub Action() Handles Action
Self.cnTest.aiDrawStack.Append(2) // add a red line to the stack (Graphic Method 2)
Self.cnTest.Invalidate // force Paint event
End Sub
New Problem: Parameters
That is all well and fine for these very simplistic Graphic Methods. But what if those Methods require parameters? We are going to have to figure out a way to “pass” those parameters and “remember” them. This gets a little convoluted. I have tried this approach.
Create a new class called ParameterStore. This has three properties and could have more if the need arises. What I personally have to keep track of is integer, double and string parameters in the ~ 30 graphic methods that I created for my custom canvas. Those three properties of ParameterStore are each arrays: dP(), iP(), sP() - array of double, array of integer, and array of string. In the custom canvas (cnGraph), create a new array property that will exist in parallel with the integer array property aiDrawStack(). I will call it aoPara(). Now when you call a Graph Method, you add its id number to the aiDrawStack() and any parameters to the aoPara().
So we will make a slightly more complex Graph Method to draw a green line with the endpoints of the line passed as parameters
Public Sub DrawGreenLineNew03(g As Graphics, X1 As Integer, Y1 As Integer, X2 As Integer, Y2 As Integer)
g.ForeColor=RGB(0,255,0) // Draw a Green line
g.DrawLine(X1,Y1,X2,Y2) // Parameters specify where
g.ForeColor=RGB(0,0,0)
End Sub
Change the Paint event code
[code]Sub Paint(g As Graphics, areas() As REALbasic.Rect) Handles Paint
For nIndex As Integer = 0 To aiDrawStack.Ubound
Select Case aiDrawStack(nIndex)
Case 1 // blue line
Me.DrawBlueLineNew01(g)
Case 2 // red line
Me.DrawRedLineNew02(g)
Case 3 // green line
Dim X1, Y1, X2, Y2 As Integer
X1 = Self.aoPara(nIndex).iP(0)
Y1 = Self.aoPara(nIndex).iP(1)
X2 = Self.aoPara(nIndex).iP(2)
Y2 = Self.aoPara(nIndex).iP(3)
Me.DrawGreenLineNew03(g, X1, Y1, X2, Y2)
End Select
Next nIndex
End Sub[/code]
Change the code in the buttons that initiate the drawing
Sub Action() Handles Action
Dim oPara As New ParameterStore
Self.cnTest.aiDrawStack.Append(1) // add a blue line to the stack (Graphic Method 1)
Self.cnTest.aoPara.Append(oPara) // have to append even if there are no parameters to keep two arrays in sync
Self.cnTest.Invalidate // force Paint event
End Sub
Sub Action() Handles Action
Dim oPara As New ParameterStore
Self.cnTest.aiDrawStack.Append(2) // add a red line to the stack (Graphic Method 2)
Self.cnTest.aoPara.Append(oPara) // have to append even if there are no parameters to keep two arrays in sync
Self.cnTest.Invalidate // force Paint event
End Sub
[code]Sub Action() Handles Action
Dim whichDraw As Integer = 3 // means green line
Dim oPara As New ParameterStore // 70, 70, 200, 220 are the parameters we want to pass
oPara.iP.Append(70)
oPara.iP.Append(70)
oPara.iP.Append(200)
oPara.iP.Append(220)
Self.cnTest.aiDrawStack.Append(whichDraw) // whichDraw is 3 which means green
Self.cnTest.aoPara.Append(oPara)
Self.cnTest.Invalidate // force Paint event
End Sub[/code]
Finally, to “clear” the graphic you need only empty the aiDrawStack and aoPara arrays
A button the clear the graphic would contain the following code
Sub Action() Handles Action
ReDim Self.cnTest.aiDrawStack(-1)
ReDim Self.cnTest.aoPara(-1)
Self.cnTest.Invalidate // force Paint event
End Sub
And now that you have this “memory” of all the graphic methods you have applied to the canvas, you can do cute things like removing just a particular line by removing its representation in the aiDrawStack and aoPara arrays. You could paint on top of or under elements by changing the “order” of these parallel arrays.
This is undoubtedly more complex because the code is keeping track of more things and when I create a new Graphic Method for my custom canvas, I have to assign it an integer and add it to the Case/Select of the PaintEvent.