Search code examples
sqldatabasesqlitedelphidelphi-xe8

The error 'Grid index out of range' when switching between the databases


This is the procedure to display the Customer Database:

    procedure TfrmMain.mnuCustomerClick(Sender: TObject);
    var
      j: integer;
    begin

      con := TFDConnection.Create(nil);
      query := TFDQuery.Create(con);
      con.LoginPrompt := False;
      con.Open('DriverID=SQLite;Database=C:\Users\katiee\Documents\Embarcadero\Studio\Projects\ProgramDatabase;');
      query.Connection := con;
      query.sql.Text := 'SELECT * FROM CustDatabase ORDER BY ID';
      query.Open();
      query.First;

      sgdDatabases.colCount := 9;
      sgdDatabases.FixedCols := 0;
      for j := 0 to sgdDatabases.rowCount do
      sgdDatabases.ColWidths[j] := 100;

      sgdDatabases.Cells[0, 0] := 'ID';
      sgdDatabases.Cells[1, 0] := 'First Name';
      sgdDatabases.Cells[2, 0] := 'Last Name';
      sgdDatabases.Cells[3, 0] := 'Address';
      sgdDatabases.Cells[4, 0] := 'Town';
      sgdDatabases.Cells[5, 0] := 'County';
      sgdDatabases.Cells[6, 0] := 'Postcode';
      sgdDatabases.Cells[7, 0] := 'Telephone No.';
      sgdDatabases.Cells[8, 0] := 'E-Mail';

      row := 1;
      while not query.EOF do
      begin
        ID := query.FieldByName('ID').AsString;
        firstname := query.FieldByName('First Name').AsString;
        lastname := query.FieldByName('Last Name').AsString;
        address := query.FieldByName('Address').AsString;
        town := query.FieldByName('Town').AsString;
        county := query.FieldByName('County').AsString;
        postcode := query.FieldByName('Postcode').AsString;
        telno := query.FieldByName('TelNo').AsString;
        email := query.FieldByName('Email').AsString;

        sgdDatabases.Cells[0, row] := ID;
        sgdDatabases.Cells[1, row] := firstname;
        sgdDatabases.Cells[2, row] := lastname;
        sgdDatabases.Cells[3, row] := address;
        sgdDatabases.Cells[4, row] := town;
        sgdDatabases.Cells[5, row] := county;
        sgdDatabases.Cells[6, row] := postcode;
        sgdDatabases.Cells[7, row] := telno;
        sgdDatabases.Cells[8, row] := email;

        sgdDatabases.RowCount := sgdDatabases.RowCount + 1;
        row := row + 1;
        query.Next;

         end;
       end;

This is the procedure to display the Employee Database, which is basically identical except "SELECT * FROM EmplDatabase":

    procedure TfrmMain.mnuEmployeeClick(Sender: TObject);
    var
      i: integer;
    begin

      con := TFDConnection.Create(nil);
      query := TFDQuery.Create(con);
      con.LoginPrompt := False;
      con.Open('DriverID=SQLite;Database=C:\Users\kasio\Documents\Embarcadero\Studio\Projects\ProgramDatabase;');
      query.Connection := con;
      query.sql.Text := 'SELECT * FROM EmplDatabase ORDER BY ID';
      query.Open();
      query.First;

      sgdDatabases.colCount := 9;
      sgdDatabases.FixedCols := 0;
      for i := 0 to sgdDatabases.RowCount do
      sgdDatabases.ColWidths[i] := 100;

      sgdDatabases.Cells[0, 0] := 'ID';
      sgdDatabases.Cells[1, 0] := 'First Name';
      sgdDatabases.Cells[2, 0] := 'Last Name';
      sgdDatabases.Cells[3, 0] := 'Address';
      sgdDatabases.Cells[4, 0] := 'Town';
      sgdDatabases.Cells[5, 0] := 'County';
      sgdDatabases.Cells[6, 0] := 'Postcode';
      sgdDatabases.Cells[7, 0] := 'Telephone No.';
      sgdDatabases.Cells[8, 0] := 'E-Mail';

      row := 1;
      while not query.EOF do
      begin
        ID := query.FieldByName('ID').AsString;
        firstname := query.FieldByName('First Name').AsString;
        lastname := query.FieldByName('Last Name').AsString;
        address := query.FieldByName('Address').AsString;
        town := query.FieldByName('Town').AsString;
        county := query.FieldByName('County').AsString;
        postcode := query.FieldByName('Postcode').AsString;
        telno := query.FieldByName('TelNo').AsString;
        email := query.FieldByName('Email').AsString;

        sgdDatabases.Cells[0, row] := ID;
        sgdDatabases.Cells[1, row] := firstname;
        sgdDatabases.Cells[2, row] := lastname;
        sgdDatabases.Cells[3, row] := address;
        sgdDatabases.Cells[4, row] := town;
        sgdDatabases.Cells[5, row] := county;
        sgdDatabases.Cells[6, row] := postcode;
        sgdDatabases.Cells[7, row] := telno;
        sgdDatabases.Cells[8, row] := email;

        sgdDatabases.RowCount := sgdDatabases.RowCount + 1;
        row := row + 1;
        query.Next;

        end;
      end;

When I run the program, I can open either of the databases on the first click, but then if I click again on either of the Customer or Employee buttons or try to change the database, the following error shows: "Project ProjectQuote.exe raised exception class EInvalidGridOperation with message 'Grid index out of range'".

If I delete the line

sgdDatabases.RowCount := sgdDatabases.RowCount + 1;

from the code, it displays both databases, but only shows the first four rows from the database even if there's more.

(I am aware of the uselessly repeated code and no I can't use anything else other than TStringGrid)


Solution

  • This line of your code looks wrong to me:

    for j := 0 to sgdDatabases.rowCount do
      sgdDatabases.ColWidths[j] := 100;
    

    The [Index] of a StringGrid's ColWidths property is a column number, not a row number, so sgdDatabases.rowCount should have nothing to do with it. If, at the time the above code executes, the number of rows in the grid is greater than the number of columns, you will get an "Index out of range" error when the value of j reaches a value which represents an invalid column number.

    In any case, even if that code were valid in that respect , there is an "off by one" error involving sgdDatabases.rowCount. The row numbers are zero-based, so it should be sgdDatabases.rowCount - 1 (assuming you were attempting to refer to a particular row by index, of course).

    A more general point is that you can single-step through your code using the IDE's debugger; if you do that, you will see the exception occur when one particular line is executed, and that's the place to start looking for the cause. You should always include the location of the exception in your SO question, because readers should not have to guess this.

    Usually, the IDE debugger will find the exception even if you don't single-step, as long as you go to

    Tools | Debugger Options | Embarcadero Debuggers | Language Exceptions

    in the IDE and check the checkbox Notify on Language Exceptions.

    Btw, it would be better if you wrote a general-purpose routine to populate a StringGrid from a Dataset, maybe along the following lines:

    procedure TForm1.DatasetToGrid(Dataset : TDataset; Grid : TStringGrid);
    var
      Col,
      Row : Integer;
    begin
    
      Grid.RowCount := 1;
      Row := 0;
    
      // The following gives the column headers the names of the
      // Dataset fields.  
      for Col := 0 to Dataset.FieldCount - 1 do
        Grid.Cells[Col, Row] := Dataset.Fields[Col].FieldName;
    
      Inc(Row);
      Dataset.First;
      while not Dataset.Eof do begin
        for Col := 0 to Dataset.FieldCount - 1 do begin
          //  Oops! we don't need this Row := Grid.RowCount;
          Grid.Cells[Col, Row] := DataSet.Fields[Col].AsString;;
        end;
        Dataset.Next;
        Grid.RowCount := Grid.RowCount + 1;
        Inc(Row);
      end;
    end;
    

    One of the benefits of doing it that way is that all your mistakes are in one place, not duplicated in duplicated code, so if you fix them once, you're done.