program demo_virtualmode_write;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}
  {$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}
  {$ENDIF}
  Classes, SysUtils, lazutf8, variants,
  fpstypes, fpspreadsheet, fpsallformats;

type
  TDataProvider = class
    procedure WriteCellDataHandler(Sender: TsWorksheet; ARow,ACol: Cardinal;
      var AData: variant; var AStyleCell: PCell);
  end;

var
  workbook: TsWorkbook;
  worksheet: TsWorksheet;
  dataprovider: TDataProvider;
  headerTemplate: PCell;
  t: TTime;

  procedure TDataProvider.WriteCellDataHandler(Sender: TsWorksheet;
    ARow, ACol: Cardinal; var AData: variant; var AStyleCell: PCell);
  { This is just a sample using random data. Normally, in case of a database,
    you would read a record and return its field values, such as:

    Dataset.Fields[ACol].AsVariant := AData;
    if ACol = Dataset.FieldCount then Dataset.Next;
    // NOTE: you have to take care of advancing the database cursor!
  }
  var
    s: String;
  begin
    if ARow = 0 then begin
      AData := Format('Column %d', [ACol + 1]);
      AStyleCell := headerTemplate;
      // This makes the style of the "headerTemplate" cell available to
      // formatting of all virtual cells in row 0.
      // Important: The template cell must be an existing cell in the worksheet.
    end else
    if (ACol = 0) then
      AData := NULL     // Let's skip the column #0
    else
    if odd(random(10)) then begin
      AData := Format('R=%d-C=%d', [ARow, ACol]);
    end else
      AData := 10000*ARow + ACol;

    // you can use the event handler also to provide feedback on how the process
    // progresses:
    if (ACol = 0) and (ARow mod 1000 = 0) then
      WriteLn('Writing row ', ARow, '...');
  end;

begin
  dataprovider := TDataProvider.Create;
  try
    workbook := TsWorkbook.Create;
    try
      worksheet := workbook.AddWorksheet('Sheet1');
      worksheet.WriteFontStyle(0, 1, [fssBold]);

      { These are the essential commands to activate virtual mode: }

//      workbook.Options := [boVirtualMode, boBufStream];
      workbook.Options := [boVirtualMode];
      { boBufStream can be omitted, but is important for large files: it causes
        writing temporary data to a buffered file stream instead of a pure
        memory stream which can overflow memory. In cases, the option can slow
        down the writing process a bit. }

      { Next two numbers define the size of virtual spreadsheet.
        In case of a database, VirtualRowCount is the RecordCount, VirtualColCount
        the number of fields to be written to the spreadsheet file }
      worksheet.VirtualRowCount := 20000;
      worksheet.VirtualColCount := 100;

      { The event handler for OnWriteCellData links the workbook to the method
        from which it gets the data to be written. }
      worksheet.OnWriteCellData := @dataprovider.WriteCellDataHandler;

      { If we want to change the format of some cells we have to provide this
        format in template cells of the worksheet. In the example, the first
        row whould be in bold letters and have a gray background.
        Therefore, we define a "header template cell" and pass this in the
        NeedCellData event handler.}
      worksheet.WriteFontStyle(0, 0, [fssBold]);
      worksheet.WriteBackgroundColor(0, 0, scSilver);
      headerTemplate := worksheet.FindCell(0, 0);

      worksheet.WriteRowHeight(0, 3);
      worksheet.WriteColWidth(0, 30);
      { In case of a database, you would open the dataset before calling this: }

      t := Now;
      //workbook.WriteToFile('test_virtual.ods', sfOpenDocument, true);
      //workbook.WriteToFile('test_virtual.xlsx', sfOOXML, true);
      workbook.WriteToFile('test_virtual.xls', sfExcel8, true);
      //workbook.WriteToFile('test_virtual.xls', sfExcel5, true);
      //workbook.WriteToFile('test_virtual.xls', sfExcel2, true);
      t := Now - t;

    finally
      workbook.Free;
    end;

  finally
    dataprovider.Free;
  end;

  WriteLn(Format('Execution time: %.3f sec', [t*24*60*60]));
  WriteLn('Press [ENTER] to quit...');
  ReadLn;
end.

