Having a DesktopCanvas problem

I need some help dealing with a graphic.

I thought I was close but I asked Grok for help to debug it. That was 2 days ago, and I think it’s taken me farther and farther from the right answer.

I have a DesktopCanvas on my window called Float_Piece. It starts invisible and located at (172, 398) — which isn’t meaningful except that I needed a place to put it before I use it.

First, I’m using it without any problem in a MouseDrag event.

In a window named winBoard, I’m dragging an object around that’s a DesktopRectangle object. It has a MouseDrag event that works perfectly:

if winBoard.isDragging then

winBoard.Float_Piece.Left = X - 15 + self.Left

winBoard.Float_Piece.Top = Y - 15 + self.Top

end if

But the problem is when I want to show it moving from one location to another in my window.

I’ve created a routine called Animate_Float_Piece.

Animate_Float_Piece(startLeft As Double, startTop As Double, endLeft As Double, endTop As Double, durationMs As Integer)

Dim currentLeft As Double = startLeft

Dim currentTop As Double = startTop

Dim targetLeft As Double = endLeft

Dim targetTop As Double = endTop

Dim animationSteps As Integer = 60

Dim stepLeft As Double = (endLeft - startLeft) / animationSteps

Dim stepTop As Double = (endTop - startTop) / animationSteps

Dim delayMicroseconds As Integer = (durationMs * 10000) \ animationSteps

Float_Piece.Visible = True

For currentStep As Integer = 0 To animationSteps - 1

currentLeft = startLeft + (stepLeft * currentStep)

currentTop = startTop + (stepTop * currentStep)

Float_Piece.Left = currentLeft

Float_Piece.Top = currentTop

Float_Piece.Refresh

Dim startTime As Double = System.Microseconds

While System.Microseconds < startTime + delayMicroseconds

App.DoEvents

Wend

Next

Float_Piece.Left = targetLeft

Float_Piece.Top = targetTop

Float_Piece.Refresh

Yes, I was too lazy to figure out using a timer again. Maybe in the future. But I don’t see the image and I don’t see it floating across the screen. Since I wasn’t sure if the problem is with my canvas or with the movement, I simplified the animation routine so I would just display the canvas in the middle of the window. I changed the code to:

Animate_Float_Piece(startLeft As Double, startTop As Double, endLeft As Double, endTop As Double, durationMs As Integer)

Float_Piece.Left = 400

Float_Piece.Top = 400

Float_Piece.Visible = True

Float_Piece.Refresh

If Float_Piece.Backdrop = Nil Then

MessageBox(“Backdrop is NIL! No image assigned.”)

End If

But I still don’t see the static image on the screen.

Grok told me that I had to add a Paint event to Float_Piece. Not sure why because it appears in the MouseDrag event but I added:

If g = Nil Then Return

// Handle Retina

Var scale As Double = If(Me.ScaleFactor > 0, Me.ScaleFactor, 1.0)

Var w As Double = Backdrop.Width / scale

Var h As Double = Backdrop.Height / scale

g.DrawPicture(Backdrop, 0, 0, w, h)

But the image always appears in position (0,0) of the window and not at position (400,400). I’ve looked at the values of Left and Top in the debugger in the Paint event, and they each = 400.

So I’m stuck and can’t figure out why the canvas is displaying at 0,0.

If I understand you correctly you want to move a Canvas around? I wouldn’t do that.

Make the Canvas static and draw the object on the Canvas.

Change in position of the drawn object is only visible after a redraw of the Canvas. (Refresh).

See example ‘Objects in Canvas’.

4 Likes

I’m looking at the example but I don’t get it.

How would you redo my setup? I’ve currently got a window with about 50 DesktopRectangles on it. And I have 50 DesktopCanvas objects that are on the DesktopRectangles so I can change the image as needed. And I have one DesktopCanvas called Float_Piece that’s also a child of the window. It starts invisible, and I can swap out the image on it. And I’m able to move it around with the MouseDrag event. But it sounds like this setup won’t work because I can’t move the canvas and have it display otherwise. But I’m looking at the Object in Canvas and don’t see what kind of object to replace Float_Piece with. It looks like the code adds objects with

Dim m As New CanvasObject

But you’re saying don’t use a canvas? And I can find an Image object in the library but that’s a project item and I can’t add it to the window.

I’m really lost. It’s been a long time since I wrote an app but I don’t remember doing anything like this before.

Have one canvas.

Have an array of classes which have properties such as top, left, width, height, color, and image

In the paint event of the canvas, iterate through these objects and get them to paint themselves on the canvas’ graphics.. You would need to give the class a ‘paintyourself’ method, passing the G parameter in.

ege

Canvas Paint(g as graphics)

for x as integer = 0 to 49

MyrectClass(x).PaintYourself(g)

next

The movable one, you handle mousedown, mousedrag, and mouseup events on the canvas, storing the variables on the window (or create a canvas subclass with properties for this)

In the Canvas’ paint event, you then ALSO draw the movable object, if it is visible.

for x as integer = 0 to 49

MyrectClass(x).PaintYourself(g)

next
MovableThing.Paintyourself(g)

To animate it, set start and end variables somewhere.

Start a timer with a suitable interval.

In the timer event, change the movablething’s location by an increment, and call Canvas.invalidate

That will draw all objects and the moveable thing.

When the timer has set the moveable thing to its final position, stop the timer.

3 Likes

Jeff is right. Do all the bookkeeping of the objects in an object class. Put them in an array with type object. Every timer tick you traverse the array, change the class properties and call canvas.refresh. In the paint (refresh) that array is traversed too and the objects get painted on the canvas.

2 Likes

I was short in time but had a few spare minutes to create a small example of what @Jeff_Tullin described. But i could not finish it. Hope it helps.
untitled.zip (5.6 KB)

1 Like

Thank you for your help — though it’s very painful. I thought this project was nearing the end but now it sounds like I have to start over where I was back in June, I’ll look at the sample once I get my laptop back a little later. But can you say more about the array of class objects? What are the objects if they aren’t classes? I understand it will be an object with a left, top, width, and height property as well as a backdrop or something that holds an image. And since they sit on top of my DesktopRectangle objects, those rectangle objects are where I manage the MouseUp, MouseDown, and MouseDrag events.

Your approach is far too complex and therefore potentially slower, and certainly more complicated to program.

Instead of placing 50 canvases in 50 rectangles, tracking the mouse across these 50 areas, and moving a 51st canvas, it’s much easier to implement one large canvas in which you fill 50 square areas and, if needed, draw a “loose” area on top.

You don’t need the rectangle objects.

You just need one canvas to rule them all.

I’ll take a 10 minute look at the zip file… back shortly

'I thought this project was nearing the end’

but I asked Grok for help

:slight_smile:

2 Likes

Try this.

It creates an array of 20 x 20 objects, of type class_rectangle

(This is a simple class that has x,y, and a background color, plus a DrawYourself() method)

When the canvas refreshes, they all draw.

Click the canvas, drag the mouse, and let go.

In the mousedown, I work out which rectangle was hit using maths.

I set bdragging to true, and invalidate the canvas

In the mousedrag, I change the position of the moveable object, which is a red rectangle.

Drag it about, let go, and it will tell you which rectangle it was dropped on.

The rectangles can be any size, they could be drawing pictures, they could be ovals.. its all done in the DrawYourself() method of each rectangle object.

I’ll send you a link to an app I sell that uses the same kind of logic , as PM

dragdrop.zip (6.6 KB)

I’ve been looking at the solution and trying to understand it,

I see that you’re adding the Class_Rectangle object to the window and giving it a background color. I think I get it. I want to be able to be able to display an image instead of a colored box, I add a Backdrop property as type Picture.

And, if I think about it, it seems I can use the Float_Piece object I already have and delete its Super so that it doesn’t inherit anything from Canvas and then add the properties that I want it to have. And then I think it will function the same as your Class_Rectangle object.

I’ve been looking at the solution and trying to understand it,

I see that you’re adding the Class_Rectangle object to the window and giving it a background color. I think I get it. I want to be able to be able to display an image instead of a colored box, I add a Backdrop property as type Picture.

And, if I think about it, it seems I can use the Float_Piece object I already have and delete its Super so that it doesn’t inherit anything from Canvas and then add the properties that I want it to have. And then I think it will function the same as your Class_Rectangle object.

But I wonder how will it be different with Float_Piece being a class object than it is now, being a class object inheriting its properties from Canvas. I won’t be able to test it out until tonight.

Exactly.

Then the DrawYourself() becomes something like this

(I assume we are still squares here.. you would need to add width and height if you want non-square stuff)

g.drawpicture me.background me.x, me.y, sqsize, sqsize, 0,0,me.background.width, me.background.height

Your New Class_Rectangle(…) or new Float_Piece(…)

will probably pass an image into the constructor, and assign that to background

Float_Piece will just be a class, it won’t need to inherit from Canvas. None of the Class_Rectangle items inherit anything, for example. The ‘floating’ one is the same kind of object as the fixed ones, but it isnt always drawn

I’m feeling overwhelmed again - there’s lots of things I don’t understand in the code.

• I’m looking at the Class_Rectangle methods. What is Constructor doing? Your reply said I’ll pass an image into the constructor, and the only thing in that sentence I recognize is an image. I don’t know what a constructor is, why I need one, and how to pass an image into it.

• I understand that in DrawYourself, I’m basically replacing the g.DrawingColor with g.drawpicture. Is me.background the property that contains the image? me.x and m.y and left and top of where to display it? Sqsize is the height and width of the Class_Rectangle object?. Then what’s the next two parameters: 0,0? And then end with the width and height of the image.

It also seems to me … what began this problem is that my current object is always displaying at (0,0) in the window instead of at (400,400). When I change Float_Piece so that it’s an independent class instead of a child of the DesktopCanvas, why won’t it have the same problem that it’s displaying at (0,0)? Is it because its paint event will call DrawYourself instead of relying on the Canvas’ native method to draw in the window? (I don’t think I need a constructor if I already have placed Float_Piece in the window using the layout editor, right?) And the Refresh event triggers the Paint which calls DrawYourself, right? So I’m hoping for better results and to see it move across the screen but I don’t see why it’ll happen that way yet.

if you are placing a canvas called Float_piece, you are not using the sample code.

if you are using the class, then it doesnt ‘exist’ at a position, it draws something on the canvas at a position.

If you set the x and y properties, it wont draw at 0,0

I’m looking at the Class_Rectangle methods. What is Constructor doing?

Your reply said I’ll pass an image into the constructor, and the only thing in that sentence I recognize is an image. I don’t know what a constructor is, why I need one, and how to pass an image into it.

(nearly) All Xojo classes have a hidden constructor() method so that you have the chance to set initial variables.

Constructor() is called when you create a new instance of the object.

If you create a new class, you add a method called Constructor() yourself.

If the constructor has parameters, you can pass some or all of the properties you want at the time you create an instance of the class.

For example, Class_Rectangle have the following parameters

Optional x As Integer = 0, y As Integer = 0, Width As Integer = 0, Height As Integer = 0

They currently assign a random color inside that constructor.

You could add a parameter c as color, and set the color at the time the NEW statement is called.

Or (for your needs), you could add a parameter p as picture, and then set the background in the constructor using background = p

=============

Consider a class called Person

It has properties Name as string, DateOfBirth as String, Gender as string

If you dont have a constructor, and you do this:

Dim Jim as new Person

then

Jim.name will be ““

Jim.DateOfBirth will be ““

Jim.Gender will be ““

If you add the Constructor() method, and give it these parameters

Constructor(theName as string, theDateOfBirth as String, TheGender as string)

You will no longer be able to do Dim Jim as new Person

Instead, you have to pass parameters

Dim Jim as new Person(“James Kirk”,”4 April 2034”,”Male”)

The Constructor will look like this:

Constructor(theName as string, theDateOfBirth as String, TheGender as string)

name = thename

DateOfBirth = theDateofBirth

Gender = theGender

end sub

and after Dim Jim as new Person(“James Kirk”,”4 April 2034”,”Male”)

your Jim object will show:

Jim.name will be “James Kirk“

Jim.DateOfBirth will be “4 April 2034“

Jim.Gender will be “Male“

Dim Holmes as new Person(“Sherlock Holmes”,”27 Jun 1899”,”Male”)

sets up a different person.

I’m hoping for better results and to see it move across the screen but I don’t see why it’ll happen that way yet.

I dont think you have refactored your code to work this way yet.

But the big picture is ‘the rectangles and images will not be canvases or controls’

You have an array of classes that know where they should exist. They have a method that draws the stuff onto a passed graphics context. That is supplied by the Paint event of the one and only canvas on the window.

1 Like

I was following along until that next to last paragraph.

I’m about to change Float_Piece from a subclass of Canvas to an independent class object. But what do you mean it won’t be a control?

I’m going around in circles — the graphics property has me baffled.

So far, I’ve created a new class Float_Piece. It has properties Top, Left, Image, and Visible.
• I’m not sure the best way to implement visibility. I’m guessing when I add a new class, it doesn’t automatically understand a Visible property. How can I implement that? I’m thinking that if Visible = true then my DrawYourself routine will display a Nil image otherwise it displays the picture stored in Image.
• But I see that g is passed as a graphic. Now I’m stuck. Where do I call DrawYourself and what is the graphic that I’m passing to it? When I instantiate an object of Float_Piece (call it Train_Piece) and then refresh it, is that what triggers DrawYourself? But what is the graphic g?

You have confused yourself. Either do a class with the super of canvas or you need to give g back to the calling class which then does the drawing.

Your problem is one that EVERYONE has before they GET object-oriented programming. Until you get it you’ll make every mistake a beginner makes, and people assume far too much to be helpful.

There is an excellent article in xDev 12.5 called “Expanding Objects” that explains how to do it.

You should only use ONE canvas.

Everything drawn in the canvas is managed by the canvas. But you don’t actually draw in the Canvas, you draw in the GRAPHICS ‘property’ g of the canvas (or the graphics context, to give it its proper name). And all that g is actually drawing is a picture of what should be on the the canvas.

Because if you do everything in the paint event of the canvas then that code is run 60 times per second. It is MUCH better to draw everything into a separate picture and then draw the finished picture with drawPicture (eg flicker free, especially on Windows).

To make an efficient graphics framework you start with making a basic abstract class cCanvasObjects that handles what happens when you click something, select or deselect it, drag it, resize it etc - everything that is common to all your drawing items is in that parent class.

Then for all your items you have abstract classes cCircle, cRectangle, cOval, cPicture, cText etc that are subclasses of the cCanvasObjects and in addition to all the goodies they get from their parent class (draw selected/deselected, move, resize, etc) also know how to draw themselves (a rectangle uses drawRectangle in its draw event, a circle uses drawCircle etc).

All that needs to be done is for the Canvas to manage it. To be able to do that the Canvas has a list of cCanvasObjects (like a rect, a circle, etc) that have been put on the canvas by you.

For example to draw a rectangle you add an instance of cRectangle to the Canvas’ list of cCanvasObjects.

var rect as new cRectangle(desiredX, desiredY, desiredW, desiredH, … any other properties you want to set)
arrayOfCanvasObjects.add rect

So when you click somewhere on the canvas the canvas knows where you clicked. So it sends the coordinates to all items in its list. Each item checks if the coordinates are within itself and deals with it accordingly (eg selects or deselects, changes colour, changes border width etc - what you can do is only limited by your imagination) and draws itself on the separate picture property.

All the Canvas has to do is to print that picture in its graphics context g.

I’m sending you a message too otherwise this gets too long..

2 Likes