2

I want to display a long string in a string grid cell in a wordwrap format and found the following code to do so:

procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
//enable wordwrap in cells
var
  S: String;
  drawrect :trect;
begin
  stringgrid1.Canvas.FillRect (Rect);
  S:= (Sender As TStringgrid).Cells [ACol, ARow ];
  If Length(S) > 0 Then Begin
    drawrect := rect;
    DrawText((Sender As TStringgrid).canvas.handle,
              Pchar(S), Length(S), drawrect,
              dt_calcrect or dt_wordbreak or dt_left );
    If (drawrect.bottom - drawrect.top) >
       (Sender As TStringgrid).RowHeights[Arow]
    Then
      (Sender As TStringgrid).RowHeights[Arow] :=
         (drawrect.bottom - drawrect.top)
      // changing the row height fires the event again!
    Else Begin
      drawrect.Right := rect.right;

      (Sender As TStringgrid).canvas.fillrect( drawrect );
      DrawText((Sender As TStringgrid).canvas.handle,
                Pchar(S), Length(S), drawrect,
                dt_wordbreak or dt_left);
    End;
  End;
end;

The word wrap works but the cell displays both the original text and the wrapped text. I assume that that since this is an onDrawCell event that the original text is already drawn and I would erase it with the first line of code (stringgrid1.canvas.fillrect(rect), but this has no effect on the display. What am I missing?

Ashlar
  • 636
  • 1
  • 10
  • 25
  • 1
    Do not setup row height from a drawing event. That in turn triggers drawing event again. – Victoria Oct 09 '17 at 16:42
  • 3
    It sounds like you still have `DefaultDrawing` enabled. The appropriate thing would be to disable it, but you also are then responsible for *all* drawing. – Jerry Dodge Oct 09 '17 at 16:48
  • @Victoria Since stringgrid does not perform a wordwrap, I have to use the onDrawCell event to modify the cell size and display the text. – Ashlar Oct 09 '17 at 22:19
  • No, you don't have to. You're not supposed to do that, actually. It's actually you who set the cell text, so do the measurement and set the row height there (`TStringGrid` is old, not flexible control). And, also when the column is resized if you allow it (but that requires the grid class interception). – Victoria Oct 09 '17 at 22:25
  • @JerryDodge I want to avoid drawing the entire stringgrid myself. Since this code executes after the initial drawing, I expected the fillrect. to overwrite the text original display and then repainting the text wrapped. – Ashlar Oct 09 '17 at 22:32
  • @victoria. I followed your suggestion, but changing the row height when I assign the text string to the cell does not result in a wordwrap of the string even though the cell is now high enough to accommodate the text on multiple lines. – Ashlar Oct 10 '17 at 02:45

1 Answers1

5

...would erase it with the first line of code (stringgrid1.canvas.fillrect(rect), but this has no effect on the display

That is because you did not select the Brush.Style (and possibly also Brush.Color) just before calling FillRect() f.ex.

  stringgrid1.canvas.Brush.Style := bsSolid; // add this line
  stringgrid1.canvas.Brush.Color := clWhite; // add this line
  stringgrid1.Canvas.FillRect (Rect);

You will notice that there are some remnants from the default drawing at the left edge of the cells (not visible in the image below, I already fixed them). That is because the TStringGrid internally offsets the cell drawing with 4 pixels. To change that you need to counter offset the Rect parameter with -4 and grow the width with +4, also before calling FillRect().

After above changes the grid looks very flat and dull (in the image I already added colors to the second grid). To reinstate some color differances for the header column and header row, you need to treat cells with gdFixed in State with a different Brush.Color. The same for cells with gdSelected in State.

enter image description here

The above is what you also need to do if you untick DefaultDrawing. The second grid and accompanying code demonstrates this. Note that the code also includes a call to DrawFocusRect when gdFocused in State just before the end.

Note that I replaced all those Sender as TStringGrid with a local grid variable.

procedure TForm4.StringGrid2DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect;
  State: TGridDrawState);
var
  grid: TStringGrid;
  S: String;
  drawrect: TRect;
  bgFill: TColor;
begin
  grid := Sender as TStringGrid;

  if gdFixed in State then
    bgFill := $FFF8F8
  else
  if gdSelected in State then
    bgFill := $FFF0D0
  else
    bgFill := clWhite;

  grid.Canvas.Brush.Color := bgFill;
  grid.canvas.Brush.Style := bsSolid;
  grid.canvas.fillrect(Rect);

  S := grid.Cells[ACol, ARow];
  if Length(S) > 0 then
  begin
    drawrect := Rect;
    drawrect.Inflate(-4 , 0);
    DrawText(grid.canvas.handle, Pchar(S), Length(S), drawrect,
      dt_calcrect or dt_wordbreak or dt_left);
    If (drawrect.bottom - drawrect.top) > grid.RowHeights[ARow] then
      grid.RowHeights[ARow] := (drawrect.bottom - drawrect.top+2)
      // changing the row height fires the event again!
    else
    begin
      drawrect.Right := Rect.Right;

//      grid.canvas.fillrect(drawrect);
      DrawText(grid.canvas.handle, Pchar(S), Length(S), drawrect,
        dt_wordbreak or dt_left);
    end;
  end;

  if gdFocused in State then
    grid.Canvas.DrawFocusRect(Rect);
end;

The third grid demonstrates the easiest and IMO the best solution, wich is to skip the TStringGrid altogether and use TDrawGrid instead. You must keep the data you want to show in the grid, separately somewhere. I defined an array: s_arr: array of array of string;.

In this case you can leave DefaultDrawing on because the TDrawGrid doesn't draw any text during the default drawing, the content drawing takes place only in the OnDrawCell event.

procedure TForm4.DrawGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect;
  State: TGridDrawState);
var
  S: string;
  grid: TDrawGrid;
  drawrect: TRect;
begin
  grid := Sender as TDrawGrid;
  S := s_arr[ACol, ARow];
  If Length(S) > 0 Then
  Begin
    drawrect := Rect;
    drawrect.Inflate(-4 , 0);
    DrawText(grid.canvas.handle, Pchar(S), Length(S), drawrect,
      dt_calcrect or dt_wordbreak or dt_left);
    If (drawrect.bottom - drawrect.top) > grid.RowHeights[ARow] Then
      grid.RowHeights[ARow] := (drawrect.bottom - drawrect.top + 2)
      // changing the row height fires the event again!
    Else
    Begin
      drawrect.Right := Rect.Right;

      grid.canvas.fillrect(drawrect);
      DrawText(grid.canvas.handle, Pchar(S), Length(S), drawrect, dt_wordbreak or dt_left);
    End;
  End;
end;

Leaving DefaultDrawing on, all those theme related features are drawn by the grid itself and we only draw the text on top.

Tom Brunberg
  • 20,312
  • 8
  • 37
  • 54