8

I want to have one fixed row as a header, but the texts are rather long, so I'd like to increase the row height and insert CR/LF into the cell text.

Googling shows this as a solution (and it's the first thing I thought of before googling), but it doesn't seem to work. Any ideas?

Grid.Cells[2,3] := 'This is a sample test' + #13#10 + 'This is the second line';

What happens is that the cell contains This is a sample testThis is the second line

I am using Delphi 7 if it makes any difference.

[Bounty] "My bad. I actually awarded this an answer two years ago without checking and now find that the answer did not work. Apologies to anyone who was misled. This is a frequently asked, often wrongly answered question."

I presume that we are looking to use OnDrawCell, but imagine that we would also have to increase the height of the string grid row which contains the cell.

I will award the answer for either code or a FOSS VCL component.

[Update] must work with Delphi XE2 Starter edition

w5m
  • 2,286
  • 3
  • 34
  • 46
Mawg says reinstate Monica
  • 38,334
  • 103
  • 306
  • 551
  • 3
    RowHeights[#] of TStringGrid wouldn't help? edit: AFAICS in the linked code in the answer, RowHeights is used to that extent. – Sertac Akyuz Oct 24 '12 at 08:23
  • Some alternative idea (might not be what you want): You could show your own editor (a small TMemo) when the user enter edit mode in TStringGrid. – Gabriel Mar 30 '22 at 07:19

2 Answers2

24

TStringGrid uses Canvas.TextRect, which uses ExtTextOut, which in turn does not support drawing of multiline text.

You have to draw this yourself in an OnDrawCell event handler with WinAPI's DrawText routine. See for example this answer on how to use DrawText for multiline text, and this recent answer on how to implement custom drawing in OnDrawCell:

type
  TForm1 = class(TForm)
    StringGrid1: TStringGrid;
    procedure FormCreate(Sender: TObject);
    procedure StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
      Rect: TRect; State: TGridDrawState);
  private
    procedure FillWithRandomText(AGrid: TStringGrid);
    procedure UpdateRowHeights(AGrid: TStringGrid);
  end;

procedure TForm1.FillWithRandomText(AGrid: TStringGrid);
const
  S = 'This is a sample'#13#10'text that contains'#13#10'multiple lines.';
var
  X: Integer;
  Y: Integer;
begin
  for X := AGrid.FixedCols to AGrid.ColCount - 1 do
    for Y := AGrid.FixedRows to AGrid.RowCount - 1 do
      AGrid.Cells[X, Y] := Copy(S, 1, 8 + Random(Length(S) - 8));
  UpdateRowHeights(AGrid);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FillWithRandomText(StringGrid1);
end;

procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
begin
  with TStringGrid(Sender) do
    if Pos(#13#10, Cells[ACol, ARow]) > 0 then
    begin
      Canvas.FillRect(Rect);
      Inc(Rect.Left, 2);
      Inc(Rect.Top, 2);
      DrawText(Canvas.Handle, PChar(Cells[ACol, ARow]), -1, Rect,
        DT_NOPREFIX or DT_WORDBREAK);
    end;
end;

procedure TForm1.UpdateRowHeights(AGrid: TStringGrid);
var
  Y: Integer;
  MaxHeight: Integer;
  X: Integer;
  R: TRect;
  TxtHeight: Integer;
begin
  for Y := AGrid.FixedRows to AGrid.RowCount - 1 do
  begin
    MaxHeight := AGrid.DefaultRowHeight - 4;
    for X := AGrid.FixedCols to AGrid.ColCount - 1 do
    begin
      R := Rect(0, 0, AGrid.ColWidths[X] - 4, 0);
      TxtHeight := DrawText(AGrid.Canvas.Handle, PChar(AGrid.Cells[X, Y]), -1,
        R, DT_WORDBREAK or DT_CALCRECT);
      if TxtHeight > MaxHeight then
        MaxHeight := TxtHeight;
    end;
    AGrid.RowHeights[Y] := MaxHeight + 4;
  end;
end;

Default StringGrid


There are also other StringGrid components able of drawing multiline text. For instance, this one which I wrote myself (download source: NLDStringGrid) with possibly this result:

NLDStringGrid

var
  R: TRect;
begin
  NLDStringGrid1.Columns.Add;
  NLDStringGrid1.Columns.Add;
  NLDStringGrid1.Cells[1, 1] := 'Sample test'#13#10'Second line';
  NLDStringGrid1.Columns[1].MultiLine := True;
  NLDStringGrid1.AutoRowHeights := True;
  SetRect(R, 2, 2, 3, 3);
  NLDStringGrid1.MergeCells(TGridRect(R), True, True);
  NLDStringGrid1.ColWidths[2] := 40;
  NLDStringGrid1.Cells[2, 2] := 'Sample test'#13#10'Second line';
end;
NGLN
  • 43,011
  • 8
  • 105
  • 200
  • +1 @NGLN I will D/L and try it now. Any restrictions on usage? – Mawg says reinstate Monica Oct 24 '12 at 09:59
  • Compile fails - can't find DesignEditors.dcu - probably because I am using XE2 Starter edition ? – Mawg says reinstate Monica Oct 24 '12 at 11:55
  • @Mawg Possibly, but I don't know. Did you convert the package-file to XE2? Note the `requires DesignIde` line! Try it runtime otherwise. – NGLN Oct 24 '12 at 12:05
  • The code above (not your package), works just fine - except ... if all entries in a row are blank the row shows as one pixel high. Can it be the default row hight? Try this - in `FillWithRandomText()` drop the loops and assign a single cell (say `Cells[3,3]`). I think you will get the bounty on this one, but it would be nice if you could tweak that blank row thing. – Mawg says reinstate Monica Oct 27 '12 at 01:23
  • 1
    I changed `UpdateRowHeights()` from `MaxHeight := 0;` to `MaxHeight := AGrid.DefaultRowHeight;` and empty lines now look reasonable. – Mawg says reinstate Monica Oct 31 '12 at 01:13
5

The TStringGrid's default renderer don't support multiple lines. By setting the TStringGrid in OwnerDraw mode (by invoking the OnDrawCell event) you can render each cell by your own liking.

Have a look at this for an example that helped a previous user.

Linked reference code inserted:

procedure DrawSGCell(Sender : TObject; C, R : integer; Rect : TRect;
          Style : TFontStyles; Wrap : boolean; Just : TAlignment;
          CanEdit : boolean);
  { draws formatted contents in string grid cell at col C, row R;
    Style is a set of fsBold, fsItalic, fsUnderline and fsStrikeOut;
    Wrap invokes word wrap for the cell's text; Just is taLeftJustify,
    taRightJustify or taCenter; if CanEdit false, cell will be given 
    the background color of fixed cells; call this routine from 
    grid's DrawCell event }
var
  S        : string;
  DrawRect : TRect;
begin
  with (Sender as tStringGrid), Canvas do begin
    { erase earlier contents from default drawing }
    if (R >= FixedRows) and (C >= FixedCols) and CanEdit then
      Brush.Color:= Color
    else
      Brush.Color:= FixedColor;
    FillRect(Rect);
    { get cell contents }
    S:= Cells[C, R];
    if length(S) > 0 then begin
      case Just of
        taLeftJustify  : S:= ' ' + S;
        taRightJustify : S:= S + ' ';
        end;
      { set font style }
      Font.Style:= Style;
      { copy of cell rectangle for text sizing }
      DrawRect:= Rect;
      if Wrap then begin
        { get size of text rectangle in DrawRect, with word wrap }
        DrawText(Handle, PChar(S), length(S), DrawRect,
          dt_calcrect or dt_wordbreak or dt_center);
        if (DrawRect.Bottom - DrawRect.Top) > RowHeights[R] then begin
          { cell word-wraps; increase row height }
          RowHeights[R]:= DrawRect.Bottom - DrawRect.Top;
          SetGridHeight(Sender as tStringGrid);
          end
        else begin
          { cell doesn't word-wrap }
          DrawRect.Right:= Rect.Right;
          FillRect(DrawRect);
          case Just of
            taLeftJustify  : DrawText(Handle, PChar(S), length(S), DrawRect,
                               dt_wordbreak or dt_left);
            taCenter       : DrawText(Handle, PChar(S), length(S), DrawRect,
                               dt_wordbreak or dt_center);
            taRightJustify : DrawText(Handle, PChar(S), length(S), DrawRect,
                               dt_wordbreak or dt_right);
            end;
          end
        end
      else
        { no word wrap }
        case Just of
          taLeftJustify  : DrawText(Handle, PChar(S), length(S), DrawRect,
                             dt_singleline or dt_vcenter or dt_left);
          taCenter       : DrawText(Handle, PChar(S), length(S), DrawRect,
                             dt_singleline or dt_vcenter or dt_center);
          taRightJustify : DrawText(Handle, PChar(S), length(S), DrawRect,
                             dt_singleline or dt_vcenter or dt_right);
          end;
      { restore no font styles }
      Font.Style:= [];
      end;
    end;
end;

I think this will work fine for you...

LU RD
  • 34,438
  • 5
  • 88
  • 296
Max Kielland
  • 5,627
  • 9
  • 60
  • 95
  • +1 That looks good. I will give it a try & get back to you. Thanks – Mawg says reinstate Monica Dec 12 '10 at 02:24
  • 1
    @Mawg, it's an example (in many points) of how you shouldn't write the code (except that it does what NGLN pointed out). My suggestion, don't use it... – TLama Oct 24 '12 at 08:38
  • Just reading the code, I don't see where it handles multiple lines on encountering #13#10 – Mawg says reinstate Monica Oct 24 '12 at 11:52
  • 3
    @Mawg - The first call to `DrawText` (with DT_CALCRECT flag) calculates the rectangle required to draw the text, which takes into account of multiple lines. – Sertac Akyuz Oct 24 '12 at 12:29
  • +1 for replyimng, but we may be talking at cross porpoises. Is that wrapping when a line gets too long? If there is space for 12 chars and I pass 'a'#13'b'#13'c' - do I get three lines? I want to be able to say exactly where the line breaks come. Thanks for your help @SertacAkyuz – Mawg says reinstate Monica Oct 24 '12 at 13:10
  • 1
    @Mawg - Yes, that will calculate the rectangle for 3 lines. Additionally if any of the lines exceeds the right edge, it will be wrapped (because of the `DT_WORDBREAK` flag) so that you may end up with more lines. – Sertac Akyuz Oct 24 '12 at 13:26
  • @MaxKielland - this looks like good code (and extremely flexible), but it has one compile error - what is `SetGridHeight` ? – Mawg says reinstate Monica Oct 27 '12 at 01:44
  • 1
    @Mawg The code is from a forum in 2003. LU RD has pasted the code here, I only linked to the original post. However, I'm not a Delphi programmer (I use C++ Builder) and the same VCL library as Delphi users use. It might be that the TStringGrid component has been changed since 2003 (not the first time they move around stuff). My guess is that SetGridHeight() was called to force the cell to repaint itself, hence falling into this routine again but this time the cell height will fit the whole text and will be drawn. (continue in next comment) – Max Kielland Nov 01 '12 at 17:02
  • 1
    (...continue from brevious comment) If the code works without the SetGridHeight() it does the job in the underlying VCL. I agree that this isn't a good place to adjust the cell height, it should be done at the same time you assign a new value to the grid. Sometimes you might not want the cell to change in size. However, you could apply a cap value to the height calculation to only allow it to a maximum height. – Max Kielland Nov 01 '12 at 17:02