Lazarus

Programming => LCL => Topic started by: LacaK on January 26, 2021, 07:03:28 pm

Title: TStringGrid with stretched rows heights according to content of cells
Post by: LacaK on January 26, 2021, 07:03:28 pm
I would like to show content of table like it is done in HTML pages. Rows are stretched to accomodate its content.
So table row height is set to greater height of cell in the given row.

Is there any effective way how to achieve similar behavior in any components placed on TForm?
For example using TStringGrid, where multiline content in cells is wrapped and row heights are adjusted to show whole content (without clipping)
Title: Re: TStringGrid with stretched rows heights according to content of cells
Post by: dsiders on January 26, 2021, 09:08:35 pm
I would like to show content of table like it is done in HTML pages. Rows are stretched to accomodate its content.
So table row height is set to greater height of cell in the given row.

Is there any effective way how to achieve similar behavior in any components placed on TForm?
For example using TStringGrid, where multiline content in cells is wrapped and row heights are adjusted to show whole content (without clipping)

This discussion might be of interest:

https://forum.lazarus.freepascal.org/index.php?topic=35488.0
Title: Re: TStringGrid with stretched rows heights according to content of cells
Post by: jamie on January 27, 2021, 12:38:41 am
maybe you can implement the onDrawCell event where you can adjust cell size to min requirement..
Title: Re: TStringGrid with stretched rows heights according to content of cells
Post by: LacaK on January 29, 2021, 06:59:09 am
Thank you for your suggestions.
I'm done with this so far:

Calculate height of a row:
Code: Pascal  [Select][+][-]
  1.  { TStringGridHelper }
  2.  
  3.   TStringGridHelper = class helper for TStringGrid
  4.     protected
  5.       procedure AutoSizeRow(aRow: integer);
  6.   end;
  7.  
  8. { TStringGridHelper }
  9.  
  10. procedure TStringGridHelper.AutoSizeRow(aRow: integer);
  11. var
  12.   aCanvas: TCanvas;
  13.   aCol, maxRowHeight:integer;
  14.   aText: String;
  15.   textRect: TRect;
  16. begin
  17.   aCanvas := GetWorkingCanvas(Canvas);
  18.   maxRowHeight := DefaultRowHeight;
  19.   for aCol:=0 to ColCount-1 do begin
  20.     aText := Cells[aCol,aRow];
  21.     textRect := Rect(0, 0, ColWidths[aCol], MaxInt);
  22.     DrawText(aCanvas.Handle, PChar(aText), Length(aText), textRect, DT_CALCRECT or DT_WORDBREAK);
  23.     if maxRowHeight < textRect.Height then
  24.       maxRowHeight := textRect.Height
  25.   end;
  26.   if aCanvas<>Canvas then FreeWorkingCanvas(aCanvas);
  27.   RowHeights[aRow] := maxRowHeight+2;
  28. end;          
  29.  

and then draw cells:
Code: Pascal  [Select][+][-]
  1. procedure TMyForm.MyStringGridDrawCell(Sender: TObject; aCol, aRow: Integer; aRect: TRect; aState: TGridDrawState);
  2. var
  3.   aText: String;
  4.   textStyle: TTextStyle;
  5. begin
  6.   with MyStringGrid do begin
  7.     aText := Cells[aCol,aRow];
  8.  
  9.     textStyle:=Canvas.TextStyle;
  10.     textStyle.Layout:=tlTop;
  11.     textStyle.SingleLine:=False;
  12.     textStyle.Wordbreak:=True;
  13.  
  14.     Canvas.FillRect(aRect);
  15.     Canvas.TextRect(aRect, ARect.Left+1, ARect.Top+1, aText, textStyle);
  16.   end;
  17.  

I think, that this automatic row height sizing will be useful extension to TStringGrid  8)
Title: Re: TStringGrid with stretched rows heights according to content of cells
Post by: PasCoder on October 22, 2022, 03:18:41 pm
Dear Lacak, Greetings to you!

I'm a newbie in Pascal. Can you help know how I can use your code in my Project.
It seems its a class but I've not known how methods, Properties and Procedures are called in Pascal. It seems to be a little bit different from Microsoft .Net Framework style. Please, can you help me? A simple Project would be of a big advantage to me.
Thanks
Title: Re: TStringGrid with stretched rows heights according to content of cells
Post by: jamie on October 22, 2022, 06:14:02 pm
You can use the OnPrepairCanvas event of the StringGrid to calculate the required row height.
Code: Pascal  [Select][+][-]
  1. procedure TForm1.StringGrid1PrepareCanvas(sender: TObject; aCol, aRow: Integer;
  2.   aState: TGridDrawState);
  3. begin
  4.   if (aRow = 2)and(aCol =0) then TStringGrid(Sender).RowHeights[2]:=30;
  5. end;              
  6.  
  7.  

 here I simply set a blank grid's row2 to 30 pixels high.

 you should calculate the required height once so that all columns will share the same value.

 I assume you make want to box in some text ?
Title: Re: TStringGrid with stretched rows heights according to content of cells
Post by: PasCoder on October 22, 2022, 06:28:30 pm
Dear Jamie,
Thank you for your response. However, your solution looks to be more MANUAL than AUTOMATIC. I would like the entire StringGrid to automaitcally adjust each row's size depending on the data/text in various cells. So, I thought that the Class (TStringGridHelper) that Lacak posted does that. Can you look at it and point me in the right direction of using it?

Thank you
Title: Re: TStringGrid with stretched rows heights according to content of cells
Post by: wp on October 22, 2022, 07:00:54 pm
Forget the stringgrid helper - it's just a formal thing. Try to understand the algorithm to use for your request: You must iterate over all cells in a row, and for each cell you measure the height of the cell rectangle with the word-wrapped text. This can be done by calling the LCLIntf function DrawText with the flags DT_CALCRECT and DT_WORDBREAK (read the Windows API for this function: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-drawtext). Then determine the maximum of all cell heights in this row and assign this to the RowHeight of that particular grid row. Then repeat with the next row, until all rows have been handled.

Code: Pascal  [Select][+][-]
  1. uses
  2.   LCLIntf, LCLType;
  3.  
  4. // Measures the height of the rectangle of a word-wrapped cell
  5. function CalcCellHeight(AGrid: TStringGrid; ACol, ARow: Integer): Integer;
  6. var
  7.   R: TRect;
  8.   s: String;
  9. begin
  10.   R := AGrid.CellRect(ACol, ARow);
  11.   R.Bottom := R.Top;
  12.   s := AGrid.Cells[ACol, ARow];
  13.   DrawText(AGrid.Canvas.Handle, PChar(s), Length(s), R, DT_CALCRECT or DT_WORDBREAK or DT_NOPREFIX);
  14.   Result := R.Bottom - R.Top;
  15. end;
  16.  
  17. // This calculates the maximum required cell height of a specific row
  18. function CalcRowHeight(AGrid: TStringGrid; ARow: Integer): Integer;
  19. var
  20.   col: Integer;
  21.   h, hmax: Integer;
  22. begin
  23.   hmax := AGrid.DefaultRowHeight;
  24.   for col := 0 to AGrid.ColCount-1 do
  25.   begin
  26.     h := CalcCellHeight(AGrid, col, ARow);
  27.     if h > hmax then hmax := h;
  28.   end;
  29.   Result := hmax;
  30. end;
  31.  
  32. // This is the final procedure. It calls CalcRowHeight for all rows of the grid.
  33. procedure AutoRowHeights(AGrid: TStringGrid);
  34. var
  35.   row: Integer;
  36. begin
  37.   for row := 0 to AGrid.RowCount-1 do
  38.     AGrid.RowHeights[row] := CalcRowHeight(AGrid, row) + 2*varCellPadding;
  39. end;

This is essentially the same as Lacak's code, just written as simple procedures.

A class helper provides a mechanism to add new methods to a class without changing its source code. Simply invoke the methods of the class helper as if they were methods of the class. Lacak implements a method AutoSizeRow(ARow: Integer). So, when your stringgrid is named StringGrid1 call StringGrid1.AutoSizeRow(ARow) - essentially the same as my code which puts the grid into the parameter list.
Title: Re: TStringGrid with stretched rows heights according to content of cells
Post by: PasCoder on October 22, 2022, 07:29:55 pm
Dear wp;
Thanks for the quick help. It seems it will help me a lot but suppose I port my Project to another OS like Linux or Mac, will it still work?

Thank you
Title: Re: TStringGrid with stretched rows heights according to content of cells
Post by: wp on October 22, 2022, 07:36:59 pm
Yes, definitely. Because of "uses LCLIntf, LCLType" instead of "uses Windows". These units provide most of what is contained in the Windows unit in a cross-platform way.
Title: Re: TStringGrid with stretched rows heights according to content of cells
Post by: PasCoder on October 23, 2022, 02:41:41 pm
Dear wp,
Thank you very much. I appreciate your support.
Title: Re: TStringGrid with stretched rows heights according to content of cells
Post by: Vodnik on December 05, 2023, 11:02:21 am
I gratefully used this code in my application. It works fine with normal grid. When I enabled columns, I noticed some issues:

1. The title row is no more autosized.

2. Following string causes arithmetic overflow error in case of hidden column:
Code: Pascal  [Select][+][-]
  1. RowHeights[aRow] := maxRowHeight+2;

For hidden column (e.g. StringGrid1.Columns[2].Visible:=False;) column width is 0 (ColWidths[2]=0).
In that case maxRowHeight stays equal to MaxInt.
I've add check for ColWidths[aCol]>0 as a workaround, while not sure this will work for all systems.

(Windows 10, Lazarus 2.2.4)
Title: Re: TStringGrid with stretched rows heights according to content of cells
Post by: wp on December 05, 2023, 11:42:30 am
1. The title row is no more autosized.
Yes, the title row requires extra handling when Columns are enabled:
Code: Pascal  [Select][+][-]
  1. function CalcCellHeight(AGrid: TStringGrid; ACol, ARow: Integer): Integer;
  2. var
  3.   R: TRect;
  4.   s: String;
  5. begin
  6.   R := AGrid.CellRect(ACol, ARow);
  7.   R.Bottom := R.Top;
  8.   if (ARow = 0) and (ACol < AGrid.Columns.Count) then
  9.   begin
  10.     s := AGrid.Columns[ACol].Title.Caption;
  11.     AGrid.Canvas.Font.Assign(AGrid.columns[ACol].Title.Font);
  12.   end else
  13.   begin
  14.     s := AGrid.Cells[ACol, ARow];
  15.     AGrid.Canvas.Font.Assign(AGrid.Font);
  16.   end;
  17.   DrawText(AGrid.Canvas.Handle, PChar(s), Length(s), R, DT_CALCRECT or DT_WORDBREAK or DT_NOPREFIX);
  18.   Result := R.Bottom - R.Top;
  19. end;

2. Following string causes arithmetic overflow error in case of hidden column:
Code: Pascal  [Select][+][-]
  1. RowHeights[aRow] := maxRowHeight+2;
Please post a demo project on this one.
Title: Re: TStringGrid with stretched rows heights according to content of cells
Post by: Vodnik on December 06, 2023, 04:22:51 pm
WP, your code works fine for StringGrids with and without Columns.
Second issue I've noticed in LacaK's code, which is publiced in WiKi, so I have discovered it first.
Title: Re: TStringGrid with stretched rows heights according to content of cells
Post by: wp on December 06, 2023, 05:54:54 pm
Second issue I've noticed in LacaK's code, which is publiced in WiKi, so I have discovered it first.
Tried to construct a full project around LacaK's code above, but could not reproduce any failure. Still: Please submit a project that can be compiled.
TinyPortal © 2005-2018