Search code examples
gridcomponentspascallazarusfreepascal

A custom grid component with number (float or integer) support in Lazarus IDE


In Lazarus (and any others RAD), a TStringGrid visual component is a key component for input/output data from DataSet or self-contained. This data is stored in cells as string data type.

I'm wondering if there is any custom grid component that supports float or integer number manipulation and associated methods.

For example:
A grid component which captures input from the user in cells (as usual) and if the data captured is a String, internally and automatically convert this data input in corresponding numeric value as Single or Integer data type for indeed manipulation and display data as usual.

Additionally, if the numeric value for the cell is modified, then also change string representation.

I think a component like this is very useful in case of we need a grid that stored numeric values only (without including a header row, maybe).

Can any of you know if exist a component like that or provide examples or source code about this component?

Thanks a lot and best regards


Solution

  • The answer depends on what you mean with "float or integer number manipulation".

    If you want to execute calculations within the grid, like in a spreadsheet, you should try the fpspreadsheet package (https://sourceforge.net/projects/lazarus-ccr/files/FPSpreadsheet/fpspreadsheet-1.6.2.zip/download, or https://sourceforge.net/p/lazarus-ccr/svn/HEAD/tree/components/fpspreadsheet/). It contains a dedicated grid in which you can enter formulas like in Excel. Each cell stores data essentially as string or number,depending on the data type. See http://wiki.lazarus.freepascal.org/TsWorksheetGrid for an introduction.

    If, on the other hand, you have a "matrix" of numbers (2D-array) and want to display them in a grid without storing the converted strings permanently like in a StringGrid, you should have a look at the standard TDrawGrid which comes with Lazarus (and Delphi). This grid provides everything that the TStringGrid has except for the strings themselves. Data are converted to strings "on the fly" whenever they are needed and then discarded again. In order to display and edit the numbers you must implement these event handlers:

    • OnDrawCell: converts the number to be displayed in a particular cell to a string, and paints the string to the canvas of the grid
    • OnGetEditText, again, converts the number to a string, but now for editing since this string will be passed to the cell editor.
    • OnSetEditText gets the string from the cell editor and must convert it to a number which must be inserted into the data matrix.

    Here is the basic code for the DrawGrid approach. Just add a TDrawGrid to a form and assign the mentioned event handlers. FData is a dummy matrix of random numbers which is created by the CreateData method.

    unit Unit1;
    
    {$mode objfpc}{$H+}
    
    interface
    
    uses
      Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, Grids;
    
    type
    
      { TForm1 }
    
      TMatrix = array of array of double;
    
      TForm1 = class(TForm)
        DrawGrid1: TDrawGrid;
        procedure DrawGrid1DrawCell(Sender: TObject; aCol, aRow: Integer;
          aRect: TRect; aState: TGridDrawState);
        procedure DrawGrid1GetEditText(Sender: TObject; ACol, ARow: Integer;
          var Value: string);
        procedure DrawGrid1SetEditText(Sender: TObject; ACol, ARow: Integer;
          const Value: string);
        procedure FormCreate(Sender: TObject);
      private
        FData: TMatrix;
        procedure CreateData;
        procedure PrepareGrid;
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.lfm}
    
    { TForm1 }
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      CreateData;
      PrepareGrid;
    end;
    
    procedure TForm1.CreateData;
    var
      r, c: Integer;
    begin
      SetLength(FData, 5, 10);  // 1st index = col = x, 2nd index = row = y
      for r := 0 to High(FData[0]) do
        for c := 0 to High(FData) do
          FData[c, r] := random*100;
    end;
    
    procedure TForm1.DrawGrid1DrawCell(Sender: TObject; aCol, aRow: Integer;
      aRect: TRect; aState: TGridDrawState);
    var
      s: String;
      xpos, ypos: Integer;
    begin
      if (aRow = 0) and (aCol = 0) and 
         (DrawGrid1.fixedRows > 0) and (DrawGrid1.Fixedcols > 0) then
         exit;
    
      // Fixed row
      if (aRow = 0) and (DrawGrid1.FixedRows > 0) then begin
        s := Format('Col %d', [aCol - DrawGrid1.FixedCols]);
        xpos := (aRect.Left + aRect.Right - DrawGrid1.Canvas.TextWidth(s)) div 2;
      end else
      // Fixed col
      if (aCol = 0) and (DrawGrid1.FixedCols > 0) then begin
        s := Format('Row %d', [aRow - DrawGrid1.FixedRows]);
        xpos := (aRect.Left + aRect.Right - DrawGrid1.Canvas.TextWidth(s)) div 2;
      end else begin
        // Normal cells
        s := FormatFloat('0.000', FData[aCol-DrawGrid1.FixedCols, aRow-DrawGrid1.FixedRows]);
        xpos := aRect.Right - constCellPadding - DrawGrid1.Canvas.TextWidth(s);
      end;
      ypos := (aRect.Top + aRect.Bottom - DrawGrid1.Canvas.TextHeight('Tg')) div 2;
      // Draw cell text
      DrawGrid1.Canvas.TextOut(xpos, ypos, s);
    end;
    
    procedure TForm1.DrawGrid1GetEditText(Sender: TObject; ACol, ARow: Integer;
      var Value: string);
    begin
      Value := FormatFloat('0.000', FData[ACol - DrawGrid1.FixedCols, ARow - DrawGrid1.FixedRows]);
    end;
    
    procedure TForm1.DrawGrid1SetEditText(Sender: TObject; ACol, ARow: Integer;
      const Value: string);
    var
      number: Double;
    begin
      if TryStrToFloat(Value, number) then
        FData[ACol - DrawGrid1.FixedCols, ARow - DrawGrid1.FixedRows] := number;
    end;
    
    procedure TForm1.PrepareGrid;
    begin
      DrawGrid1.RowCount := Length(FData[0]) + DrawGrid1.FixedRows;
      DrawGrid1.ColCount := Length(FData) + DrawGrid1.FixedCols;
      Invalidate;
    end;
    
    end.