Rendering PaintBox using PaintBox.Canvas.ClipRect is buggy lazarus / 39-Graphics ---------------------------------------------------------------------- flowCRANE · 2023-04-04 20:10 I have a PaintBox embedded in a ScrollBox that can be stretched. The PaintBox can be large in size (e.g. 256×4096), which makes the ScrollBox.VerticalScrollBar useful for scrolling. In PaintBox, I render the grid, but for efficient rendering, instead of repainting the entire component, I only repaint the grid cells that fit in PaintBox.Canvas.ClipRect — of course, the rendering is in the PaintBox.OnPaint event. Everything works very efficiently, but there is a problem — if I grab the ScrollBox slider with my mouse and move it up/down quickly, holes fall out in the PaintBox. The faster I scroll the ScrollBox, the bigger the holes fall out. Interestingly, the background is rendered (clMenu color), but the cells are clipped. Each cell is a separate TPortableNetworkGraphic, so painting the cell is limited to PaintBox.Canvas.Draw call. The PaintBox.OnPaint code looks like this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TFormMain.PaintBoxStampsPaint(ASender: TObject);var  PaintBox: TPaintBox absolute ASender;var  VisibleRect:      TRect;  VisibleCellFirst: Integer;  VisibleCellLast:  Integer;var  ItemIndex: Integer;  ItemRect:  TRect;  ItemStyle: TTextStyle;begin  // Get the rect that has to be repainted.  VisibleRect      := PaintBox.Canvas.ClipRect;    // Calculate the index of the first and last cell (checked, calculations are valid)  VisibleCellFirst := (VisibleRect.Top div FontsEditor.Buffers.CellHeight) * FontsEditor.Grid.SizeX;  VisibleCellLast  := VisibleCellFirst + (VisibleRect.Height div FontsEditor.Buffers.CellHeight) * FontsEditor.Grid.SizeX - 1;   if VisibleRect.Bottom mod FontsEditor.Buffers.CellHeight > 0 then    VisibleCellLast += FontsEditor.Grid.SizeX;   VisibleCellLast := Min(VisibleCellLast, FontsEditor.Grid.ItemIndexLast);   // Paint the background under all cells that have to be repainted.  PaintBox.Canvas.Brush.Color := clMenu;  PaintBox.Canvas.FillRect(VisibleRect);   // Paint all cells that intersects with the "PaintBox.Canvas.ClipRect" rect.  for ItemIndex := VisibleCellFirst to VisibleCellLast do  begin    // Get calculated rect of the cell (checked, calculations are valid)    ItemRect := FontsEditor.Grid.ItemRect[ItemIndex];    // Get the cell graphic and draw it.    PaintBox.Canvas.Draw(ItemRect.Left, ItemRect.Top, FontsEditor.Buffers.Cell[ItemIndex]);     // Render the item index inside the cell.    ItemStyle := PaintBox.Canvas.TextStyle;    ItemStyle.Alignment := taCenter;    ItemStyle.Layout := tlCenter;    PaintBox.Canvas.TextRect(ItemRect, 0, 0, ItemIndex.ToString(), ItemStyle);  end;end;  You can see the results of this code in the attachments: After the program window appears on the screen (correct appearance).After scrolling with the mouse wheel (also correct appearance).After scrolling with the ScrollBox slider — it contains empty bars with the background color (clMenu), but the graphics are not completely painted?Why do these things happen? ---------------------------------------------------------------------- flowCRANE · 2023-04-04 20:19 For the test, I replaced the TPaintBox with a TPanel, but the effect is exactly the same. ---------------------------------------------------------------------- Josh · 2023-04-04 20:22 Hi Sounds like the paint event is being called in rapid succession, to the point where it has not finished running; before its called again. what happens if you limit the re-entrent by using a global var ie. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i var Am_Painting:Boolean=false; procedure PaintBoxStampsPaint(ASender: TObject);....begin  if Am_Painting then exit;  Am_Painting:=true; .....  Am_Painting:=false;end; ---------------------------------------------------------------------- KodeZwerg · 2023-04-04 20:26 Or doing "Invalidate" after your painting? ---------------------------------------------------------------------- flowCRANE · 2023-04-04 20:31 Quote from: KodeZwerg on April 04, 2023, 10:26:39 pmOr doing "Invalidate" after your painting? This fixed the problem.  Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TFormMain.PaintBoxStampsPaint(ASender: TObject);var  PaintBox: TPaintBox absolute ASender;var  VisibleRect:      TRect;  VisibleCellFirst: Integer;  VisibleCellLast:  Integer;var  ItemIndex: Integer;  ItemRect:  TRect;  ItemStyle: TTextStyle;begin  VisibleRect    := PaintBox.Canvas.ClipRect;  VisibleCellFirst := (VisibleRect.Top div FontsEditor.Buffers.CellHeight) * FontsEditor.Grid.SizeX;  VisibleCellLast  := VisibleCellFirst + (VisibleRect.Height div FontsEditor.Buffers.CellHeight) * FontsEditor.Grid.SizeX - 1;   if VisibleRect.Bottom mod FontsEditor.Buffers.CellHeight > 0 then    VisibleCellLast += FontsEditor.Grid.SizeX;   VisibleCellLast := Min(VisibleCellLast, FontsEditor.Grid.ItemIndexLast);   PaintBox.Canvas.Brush.Color := clMenu;  PaintBox.Canvas.FillRect(VisibleRect);   for ItemIndex := VisibleCellFirst to VisibleCellLast do  begin    ItemRect := FontsEditor.Grid.ItemRect[ItemIndex];    PaintBox.Canvas.Draw(ItemRect.Left, ItemRect.Top, FontsEditor.Buffers.Cell[ItemIndex]);     ItemStyle := PaintBox.Canvas.TextStyle;    ItemStyle.Alignment := taCenter;    ItemStyle.Layout := tlCenter;    PaintBox.Canvas.TextRect(ItemRect, 0, 0, ItemIndex.ToString(), ItemStyle);  end;   PaintBox.Invalidate();end; ---------------------------------------------------------------------- flowCRANE · 2023-04-04 21:01 Hmm, it is not obvious to me why, but I've changed the calculations to this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TFormMain.PaintBoxStampsPaint(ASender: TObject);var  PaintBox: TPaintBox absolute ASender;var  VisibleRect:      TRect;  VisibleRowFirst:  Integer;  VisibleRowLast:   Integer;  VisibleCellFirst: Integer;  VisibleCellLast:  Integer;var  ItemIndex: Integer;  ItemRect:  TRect;  ItemStyle: TTextStyle;begin  VisibleRect    := PaintBox.Canvas.ClipRect;  VisibleRowFirst  := VisibleRect.Top div FontsEditor.Buffers.CellHeight;  VisibleRowLast   := VisibleRect.Bottom div FontsEditor.Buffers.CellHeight;  VisibleRowLast   += Ord(VisibleRect.Bottom mod FontsEditor.Buffers.CellHeight > 0);  VisibleCellFirst := VisibleRowFirst * FontsEditor.Grid.SizeX;  VisibleCellLast  := VisibleCellFirst + (VisibleRowLast * FontsEditor.Grid.SizeX) + FontsEditor.Grid.SizeX - 1;  VisibleCellLast  := Min(VisibleCellLast, FontsEditor.Grid.ItemIndexLast);   PaintBox.Canvas.Brush.Color := clMenu;  PaintBox.Canvas.FillRect(VisibleRect);   for ItemIndex := VisibleCellFirst to VisibleCellLast do  begin    ItemRect := FontsEditor.Grid.ItemRect[ItemIndex];    PaintBox.Canvas.Draw(ItemRect.Left, ItemRect.Top, FontsEditor.Buffers.Cell[ItemIndex]);     ItemStyle := PaintBox.Canvas.TextStyle;    ItemStyle.Alignment := taCenter;    ItemStyle.Layout := tlCenter;    PaintBox.Canvas.TextRect(ItemRect, 0, 0, ItemIndex.ToString(), ItemStyle);  end;end;  and now calling the PaintBox.Invalidate is not needed. Even more, if I call it at the end, the rendering loops forever. Strange. ---------------------------------------------------------------------- wp · 2023-04-04 21:07 Quote from: furious programming on April 04, 2023, 11:01:54 pmEven more, if I call it at the end, the rendering loops forever. Strange. Not at all. The OnPaint event is fired when the Paintbox is told by the OS to repaint itself. But when you call Paintbox.Invalidate you tell the OS to repaint the Paintbox. Therefore, the PaintBox gets into the OnPaint handler again where it calls Paintbox.Invalidate. Etc... ---------------------------------------------------------------------- flowCRANE · 2023-04-04 22:03 Quote from: wp on April 04, 2023, 11:07:21 pmNot at all. I checked it — after changing the calculation code, the rendering looped endlessly, just because of the PaintBox.Invalidate call at the end of this event. After removing it, the problem went away. I didn't like it from the start because I use the Invalidate method whenever I want to repaint the control (and never inside the OnPaint event). I have no idea why there was a problem with under-rendered areas in previous calculations, and why adding Invalidate fixed this problem, or why after changing the calculation code, Invalidate not only is it not needed, it is also harmful. But that's not important. It is important that the rendering works now.