2

I want to know on an overriding of DrawColumnCell if the grid is drawing its active row.

I thought of keeping an ActiveRecno private variable to check if the DrawColumnCell is drawing that row. I tried intercepting the DataChange of the Datasource to keep track of that ActiveRecno.

TMyDBGrid = class(TDBGrid)
  protected
    OnDataChange_Original: TDataChangeEvent;
    procedure TrackPosition(Sender: TObject; Field: TField);
    procedure DrawColumnCell(const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); override;
  public
    ActiveRecno: integer;
    constructor Create(AOwner: TComponent): override;
  ...
  ...

implementation

constructor TMyDBGrid.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  OnDataChange_Original := nil;
  if Assigned(DataSource) then begin
    OnDataChange_Original := Datasource.OnDataChange;
    Datasource.OnDataChange := TrackPosition;
  end;
end;

procedure TMyDBGrid.TrackPosition(Sender: TObject; Field: TField);
begin
  ActiveRecno := Datasource.DataSet.RecNo;
  if Assigned(OnDataChange_Original) then OnDataChange_Original(Sender, Field);
end;

procedure TMyDBGrid.DrawColumnCell(const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); 
var ActiveRow: boolean;
begin
  ActiveRow := (Self.ActiveRecno = Self.DataSource.Dataset.Recno); 
  ...
  ...

  inherited DrawColumnCell(Rect, DataCol, Column, State);
end;

But ActiveRecno remains always 0, making ActiveRow always False. That's because in the constructor Datasource is still nil, so I never set TrackPosition to keep the ActiveRecno.

Where can I set my handler for that event ?, the SetDataSource procedure is private, so I can't override it.

Do you recommend me another way to keep track of the active row, or detect in DrawColumnCell if the row to draw is the active row ?.

Thank you.

Marc Guillot
  • 6,090
  • 1
  • 15
  • 42
  • That's what the `State` parameter that you receive can be used for. It indicates the current draw state of the column, and the active cell is in `gdSelected` state. When you receive it, that's the active row. (If you've got row select on, you'll get the `gdRowSelected` state instead.) – Ken White Jul 28 '21 at 00:38
  • Thank you @KenWhite I've gone with Martyn solution because gdSelected only works for the active cell, and to use gdRowSelected you need to enable Row Select, which prevents to edit the grid. – Marc Guillot Jul 28 '21 at 10:46
  • When you get `gdSelected`, you know you're on the active row, because the active cell is in the active row. So whenever you get `gdSelected` you've got the active row, which is all you need according to your question. – Ken White Jul 28 '21 at 14:50

1 Answers1

3

I think that what you want would be straightforward except for the fact that neither the current row of the DBGrid nor the row being drawn in the OnDrawrDataCell event is readily accessible inside the event.

Fortunately, it is fairly straightforward to overcome these problems using an interposer TDBGrid class as shown below.

THe interposer TDBGrid class simply exposes the Row property of TCustomGrid as the ActiveRow

The OnDrawDataCell event is called from TCustomDBGrid.DrawCell, which is virtual and so can be overridden in the interposer class. As you can see below, the overridden version first copies the row number (the ARow argument) used in TCustomDBGrid.DrawCell into the FRowBeingDrawn field and then calls the inherited DrawDataCell, which in turn calls the OnDrawDataCell handler. Since this handler sees the interposer class, both the grid's ActiveRow and RowBeingDrawn are accessible inside the OnDrawDataCell event.

type
  TDBGrid = class(DBGrids.TDBGrid)
  private                                   ,
    FRowBeingDrawn : Integer;
    function GetActiveRow: Integer;
  protected
    procedure DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState); override;
    property RowBeingDrawn : Integer read FRowBeingDrawn write FRowBeingDrawn;
    property ActiveRow : Integer read GetActiveRow;
  end;

  TForm1 = class(TForm)
    DBGrid1: TDBGrid;
    ClientDataSet1: TClientDataSet;
    DataSource1: TDataSource;
    ComboBox1: TComboBox;
    DBNavigator1: TDBNavigator;
    procedure FormCreate(Sender: TObject);
    procedure DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
      DataCol: Integer; Column: TColumn; State: TGridDrawState);
  end;

[...]

procedure TForm1.FormCreate(Sender: TObject);
var
  AField : TField;
begin
  AField := TIntegerField.Create(Self);
  AField.FieldKind := fkData;
  AField.FieldName := 'ID';
  AField.DataSet := ClientDataSet1;

  AField := TStringField.Create(Self);
  AField.FieldKind := fkData;
  AField.FieldName := 'AValue';
  AField.DataSet := ClientDataSet1;

  ClientDataSet1.CreateDataSet;
  ClientDataSet1.InsertRecord([1, 'One']);
  ClientDataSet1.InsertRecord([2, 'Two']);
  ClientDataSet1.InsertRecord([3, 'Three']);
  ClientDataSet1.InsertRecord([4, 'Four']);
  ClientDataSet1.InsertRecord([5, 'Five']);

  DBGrid1.DefaultDrawing := True;
end;

procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
  DataCol: Integer; Column: TColumn; State: TGridDrawState);
begin
   if (Sender as TDBGrid).RowBeingDrawn = (Sender as TDBGrid).Row then
     Caption := IntToStr((Sender as TDBGrid).RowBeingDrawn);

  DBGrid1.DefaultDrawDataCell(Rect, Column.Field, State);

end;

procedure TDBGrid.DrawCell(ACol, ARow: Integer; ARect: TRect;
  AState: TGridDrawState);
begin
  RowBeingDrawn := ARow;
  try
    inherited;
  finally
    RowBeingDrawn := -1;
  end;
end;

function TDBGrid.GetActiveRow: Integer;
begin
  Result := Row;
end;

end.

The interposer class can, of course, be contained in a separate unit provided, of course, that it appears in the using unit's Uses list after DBGrids.

One minor point to beware of is that the code above does not take account of whether the title row of the grid is visble or not, and make require minor tweaking if it is not.

MartynA
  • 30,454
  • 4
  • 32
  • 73