Recent

Author Topic: [SOLVED] How to force Event StringGrid.OnPrepareCanvas to be called for all Rows  (Read 853 times)

Hartmut

  • Hero Member
  • *****
  • Posts: 736
My program has a StringGrid which shall display the names of several fonts in column 2 and a demo-text using this font in column 6 (see attached screenshot). To manage this, I use this Event:

Code: Pascal  [Select][+][-]
  1. const X_Name   = 2; {column for Font-Name}
  2.       X_Demo   = 6; {column for Demo-Text}
  3.       max_width: integer = 0; {max. width for Demo-Text column}
  4.  
  5. procedure TForm1.SG1PrepareCanvas(Sender: TObject; aCol, aRow: integer;
  6.                                   aState: TGridDrawState);
  7.    var w: integer;
  8.    begin
  9.    if aRow < 1 then exit;       {if title row}
  10.    if aCol <> X_Demo then exit; {if not column for Demo-Text}
  11.  
  12.    SG1.Canvas.Font.Name:=SG1.Cells[X_Name,aRow]; {set font for column 'X_Demo'}
  13.  
  14.    w:=SG1.Canvas.TextWidth(SG1.Cells[X_Demo,aRow]); {compute width of demo-text}
  15.    if w > max_width then max_width:=w;              {store max. value}
  16.    end;

The problem is: when I call SG1.AutoSizeColumns to set the width for all columns, then the width for column 6 (the demo-text) is wrong (see screenshot). I assume this is, because this procedure is not usable, if a column uses different fonts. So I think, I must compute the maximum width for column 6 by myself. But the problem is, that above Event is at the beginning only called for the 1st page of the StringGrid. What I need is, that this Event is called for *all* Rows at once. Is this possible? How?

I use Lazarus 2.0.10 with FPC 3.2.0. Thanks in advance.
« Last Edit: December 03, 2023, 10:55:58 am by Hartmut »

wp

  • Hero Member
  • *****
  • Posts: 11785
The problem is: when I call SG1.AutoSizeColumns to set the width for all columns, then the width for column 6 (the demo-text) is wrong (see screenshot).
This reads as if OnPrepareCanvas is not executed when AutoSizeColumns is running, and looking at the source in TCustomStringGrid.AutoAdjustColumn, I see that, in fact, this is what is happening.

Please try this:
  • Open grids.pas (in folder lcl of the Lazarus installation), and find the procedure TCustomStringGrid.AutoAdjustColumn(...).
  • After the final "end" of the block "if (C<>nil)...end else begin...end;" and before the block "if (i=0) and (FixedRows > 0) and (C <> nil) then..." insert the following code
Code: Pascal  [Select][+][-]
  1. ...
  2.      if C<>nil then begin
  3.         if i<FixedRows then
  4.           tmpCanvas.Font := C.Title.Font
  5.         else
  6.           tmpCanvas.Font := C.Font;
  7.       end else begin
  8.         if i<FixedRows then
  9.           tmpCanvas.Font := TitleFont
  10.         else
  11.           tmpCanvas.Font := Font;
  12.       end;
  13.  
  14.       if Assigned(FOnPrepareCanvas) then           // <--- ADDED
  15.         FOnPrepareCanvas(self, aCol, i, []);       // <--- ADDED
  16.  
  17.       if (i=0) and (FixedRows>0) and (C<>nil) then
  18.         aText := C.Title.Caption
  19.       else
  20.         aText := Cells[aCol, i];  
  21. ...

This should fix most of the issue. A discrepancy will remain when your OnPrepareCanvas switches to bold or italic in some combinations of the State parameter because the modified routine does not transfer a State parameter to OnPreparCanvas.
« Last Edit: December 02, 2023, 12:19:31 pm by wp »

Hartmut

  • Hero Member
  • *****
  • Posts: 736
Dear wp, hello again. Thank you for that suggestion. I tried it and it works. Congratulations! But I considerable hesitate to edit/change the LCL for a long list of reasons. And you wrote, that there will be problems with bold and italics, which I want to expand in the future. So I'm hoping for another solution which needs no changes to the LCL.

My imagination is a separate procedure, which calls Event 'SG1PrepareCanvas' in a loop for all Rows and computes the maximum width for the demo-text column. But this should be in a way or "context", that the assignment of all the fonts, which is done in the Event for column 6 (Demo-Text), can be "remembered" later, when the StringGrid is scrolled, so that this time-consuming task is not computed twice for many fonts. How could something like that be realized?

paweld

  • Hero Member
  • *****
  • Posts: 955
sample in attachment
Best regards / Pozdrawiam
paweld

wp

  • Hero Member
  • *****
  • Posts: 11785
Dear wp, hello again. Thank you for that suggestion. I tried it and it works. Congratulations! But I considerable hesitate to edit/change the LCL for a long list of reasons.
Of course. Do you think that we should have this change in the LCL of the next release coming after v3.0? The bold/italic uncertainty probably can be overcome by being more elaborate on the State parameter. On one side I think that having an AutoSizeColumns method at all means that it should work under all - well: most? - circumstances. On the other hand, it may add some load to the grid because OnPrepareCanvar is a user function, and nobody knows what he puts in there not knowing that it is not used for drawing only.

Hartmut

  • Hero Member
  • *****
  • Posts: 736
Thank you very much paweld for your demo. It works. There is only one disadvantage: when I scroll, then this is slow, because the "cache" (I assume there is one), which normally is filled by Event 'sgPrepareCanvas', is not filled by your procedure 'GetMaxColWidthWithSampleText'. That means, if I scroll, Event 'sgPrepareCanvas' must compute the same column '4' again, which already had been computed by procedure 'GetMaxColWidthWithSampleText'. Is this possible to improve?

For others, who did not download your project, here the concerned code:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.sgPrepareCanvas(Sender: TObject; aCol, aRow: Integer; aState: TGridDrawState);
  2. var
  3.   fss: TFontStyles;
  4. begin
  5.   if (aRow > 0) and (aCol = 4) then
  6.   begin
  7.     fss := [];
  8.     if chbb.Checked then fss := fss + [fsBold];   {CheckBox "Bold"}
  9.     if chbi.Checked then fss := fss + [fsItalic]; {CheckBox "Italic"}
  10.     sg.Canvas.Font.Style := fss;
  11.     sg.Canvas.Font.Size := sefs.Value;            {SpinEdit "Font size"}
  12.     sg.Canvas.Font.Name := sg.Cells[1, aRow];
  13.   end;
  14. end;
  15.  
  16. procedure TForm1.GetMaxColWidthWithSampleText;
  17. var
  18.   i, w, maxw: Integer;
  19.   fname: String;
  20.   fss: TFontStyles;
  21. begin
  22.   sg.BeginUpdate;
  23.   fname := sg.Canvas.Font.Name;
  24.   fss := [];
  25.   if chbb.Checked then fss := fss + [fsBold];   {CheckBox "Bold"}
  26.   if chbi.Checked then fss := fss + [fsItalic]; {CheckBox "Italic"}
  27.   sg.Canvas.Font.Style := fss;
  28.   sg.Canvas.Font.Size := sefs.Value;            {SpinEdit "Font size"}
  29.   maxw := 0;
  30.   //
  31.   for i := 1 to sg.RowCount - 1 do
  32.   begin
  33.     sg.Canvas.Font.Name := sg.Cells[1, i];
  34.     w := sg.Canvas.TextWidth(sg.Cells[4, i]);
  35.     if w > maxw then
  36.       maxw := w;
  37.     sg.RowHeights[i] := Max(22, sg.Canvas.TextHeight(sg.Cells[4, i]) + 4);
  38.   end;
  39.   sg.Canvas.Font.Name := fname;
  40.   sg.EndUpdate(False);
  41.   sg.Columns[4].Width := maxw + 4;
  42. end;



Do you think that we should have this change in the LCL of the next release coming after v3.0?

I think this would be a really interesting improvement. But it can slow down procedure 'AutoAdjustColumn' much because of the additional calls of Event 'SG1PrepareCanvas'. So I would highly vote for a boolean variable in class 'TCustomStringGrid' which controls, if your new code is excecuted or not. The default should be 'False' for compatibility reasons. Thanks again for your help.

wp

  • Hero Member
  • *****
  • Posts: 11785
I don't know of any "cache" in the string grid.

In my impression, paweld's procedure GetMaxColWidthWithSampleText is the correct way to do it: when the grid is populated it iterates over all rows and calculates the width of the column with the sample text. Later, when the grid is scrolled, only the visible lines are drawn, no more width calculation is needed. Its only problem is that it is tailored to the case that the font name to be used is written explicitly in one specific column.

In the more general case, something like OnPrepareCanvas must be used to tell the width measuring routine the font to be used. My patch for TCustomStringGrid.AutoAdjustColumn shown in reply #1, however, is not working. Because OnPrepareCanvas operates directly on the grid's Canvas, but AutoAdjustColumn evaluates an auxiliary canvas, tmpCanvas. But modifying the patch as follows can fix this issue:
Code: Pascal  [Select][+][-]
  1.       if Assigned(FOnPrepareCanvas) then
  2.       begin
  3.         FOnPrepareCanvas(self, aCol, i, []);
  4.         tmpCanvas.Font := Canvas.Font;
  5.       end;
With this modification, there is no difference in execution time between paweld's GetMaxColWidthWithSampleText and the grid's AutoSizeColumn(4).

Hartmut

  • Hero Member
  • *****
  • Posts: 736
Thank you wp for clarification and this improvement.

 

TinyPortal © 2005-2018