ControlSet controls with varying heights not lining up as hoping

I am using a ControlSet of two sets of canvas classes. I am getting them to add as expected, however, I am having a hard time with the top of the next one to be added. The problem is, some of these will have text written on one line, and others multiple lines by way of wrapping. When on one line, it looks fine, but when there are two or more lines, the spacing in between seems to be ignored, or at least not accounting for the new height of the canvas

Using the example Desktop>Controls>ControlSets

For i As Integer = 1 To controlsToAdd
  newLeft = new clsLeftCanvas1
  newLeft.visible = True
  clsLeftCanvas1(i).LCanText = leftCol(i - 1) = clsLeftCanvas1(i - 1).top + clsLeftCanvas1(i - 1).Height + 5
  newRight = new clsRightCanvas1
  newRight.visible = True
  clsRightCanvas1(i).RCanText = rightCol(i - 1) = clsRightCanvas1(i - 1).top + clsRightCanvas1(i - 1).Height + 5

Take note to the right column where there is text wrapping and two lines of text. I’d like these to have the same +5 spacing to the next canvas added

What is your code in the canvas paint event to draw the text and the borders?

Right. Should have included this

g.DrawingColor = myColor2

g.PenSize = 5
g.DrawRectangle(0, 0, g.Width, g.Height)

g.DrawingColor = Color.Black
g.FontName = "Arial"
g.FontSize = 18

dim height as Double
height = g.TextHeight(clsRightCanvas1(index).RCanText, g.Width - 20)

g.DrawText(clsRightCanvas1(index).RCanText, 10, 30, g.Width - 20, False)

clsRightCanvas1(index).Height = height + 25

Well you create all the canvasses (and their Top) according to some default height (not specified), and then the Paint events kick in and set their heights. Since for the wrapped ones the height is larger, the canvas gets taller. Although since you draw the border earlier, I don’t see how this can be, either.

Presumably the height is greater for the wrapped ones, since otherwise I don’t see the point of passing the wrapWidth. But I didn’t know it did that.

Perhaps you should put all the logic in the Paint event, although I would tend to only have drawing stuff in there.

Why do you have two classes? They look very much alike to me.

In this code, you should calculate the actual height of the text. You can create a picture object and use its Graphics object to measure the text. Then take the code out of Paint that changes the Height.

@TimStreater I bet the reason it works is by changing the Height of the canvas, it triggers another Paint. So he’s drawing it twice, once with the wrong rectangle size, and then again with the correct size.

I’m figuring this all out based on the example project. Does the default height go by the original control set’s height? And also then the original top/height, then you add in a spacer? I’ll have to test this out with changing the original’s height

Ha! Yea! This logic didn’t make sense to me either. I use canvases on another window (made before this one) in a similar fashion but without the control sets. I follow the same scheme in the paint event. Draw the border, calculate the text height, draw the text, reconfigure the height. I was surprised that it worked. Thought I would’ve had to do it calculate first then draw, but I let it go figuring it worked. @Tim_Hare ‘s comment that the last height call would cause the canvas to paint itself again makes sense if that’s the case

Two separate canvas classes, identical in nature, but one houses the right column of items, and the other the left column of items. This is for a matching component. Match something in column A to something in column B

Will be giving this a try tomorrow. Great suggestion. Hadn’t considered something like this

Thanks guys. Will post back if I run into any additional complications

Yes, that would be it. But wouldn’t that cause an infinite loop, with the code as it is? With each Paint event triggering another one?

Trouble is the doc doesn’t say enough about things like that. I’m sure my code has similar issues, as a result.

Yes, I’d say stick with two classes until it works satisfactorily. But then, even if only as an exercise, think about how you’d merge them.

I have a couple of base classes, one for a type of listbox, and another for buttons. I’ve pushed as many methods, event handlers, and properties as possible upwards into them, but leaving out things that cause the instances of these to behave differently. So Paint might be in the base class, so they all look alike, but doubleclick or drag handling in the instances when I want that instance to behave in a particular way.

Success! Creating a picture object was the way to go. I took the height calculation out of the canvas.paint, so this now only paints the border and the text, which is what it should be doing

I have my code below if this would help anyone else. I’m sure there are better ways of doing this, but this is my take, and it worked :slight_smile:

Thanks again for the suggestions!

dim newLeft as clsLeftCanvas1
dim newRight as clsRightCanvas1
dim tempLeftHeight as new Picture(380, 1000) 'canvases are 400 wide, but using 380 as max width to account for border and padding
dim tempRightHeight as new Picture(380, 1000)

dim heightLeft, heightLeftPrev, heightRight, heightRightPrev as Double

For i As Integer = 1 To controlsToAdd
  newLeft = new clsLeftCanvas1
  newLeft.visible = True
  clsLeftCanvas1(i).LCanText = leftCol(i - 1)  'set the text to the canvas
  tempLeftHeight.Graphics.FontSize = 16
  heightLeft = tempLeftHeight.Graphics.TextHeight(leftCol(i - 1), 380) 'get height of this text with wrapping at 380 = clsLeftCanvas1(i - 1).top + heightLeftPrev + 5 'top of this one is top of previous plus the height of the previous one added (first will be zero) plus a spacer
  newLeft.height = heightLeft + 25 'make the height of this canvas based on the calculated height
  heightLeftPrev = heightLeft + 25 'store this height into the previous height property to calculate for the next one
  newRight = new clsRightCanvas1
  newRight.visible = True
  clsRightCanvas1(i).RCanText = rightCol(i - 1)
  tempRightHeight.Graphics.FontSize = 16
  heightRight = tempRightHeight.Graphics.TextHeight(rightCol(i - 1), 380) = clsRightCanvas1(i - 1).top + heightRightPrev + 5
  newRight.height = heightRight + 25
  heightRightPrev = heightRight + 25

This doesn’t look like it will keep the left and right controls aligned. You’re setting the canvas top independently. You should measure the height of both left and right text and use the larger to set the Top of both canvases, so they stay in sync.

Thanks Tim. My goal was to just align the columns separately, which this is now doing. I wanted equal space between x1 and x2, and the for y1 and y2. If you look at my picture in the original post, you’ll see the left column items all have equal space because they’re all one liners, but in the right column, the first 3 are two liners, and there’s no gap between y1 and y2 and y3, but then the 4th is a single line, and has the appropriate 5px space to the next item. In some other matching categories, there are a few with 3 lines of text, which overlapped the next item. The code I posted above gives an equal 5px space between each of the items, regardless if one, two, or three lines of text. This was all I was after. I know it would look lopsided when one column is all one liners and the other has some two or three liners. That’s ok with me. When the student gets a correct match, the two items (one from each column) are removed. I’ll try to remember to post a pic of the result tomorrow

Again, thanks for the assistance and chiming in. Creating a picture object was just what I needed. Plus, you taught me that the canvas redraws when I had the calculation inside of paint. This was probably doing an indefinite loop. So thanks again for teaching me that :slight_smile:

Gotcha. I incorrectly assumed that there was more correspondence between items in the two columns. Since there isn’t, your code is good.

Forum for Xojo Programming Language and IDE. Copyright © 2021 Xojo, Inc.