A TDBGrid descendant with an "easy-read" coloring feature

By: Federico Albesano

Abstract: How to put alternate colored rows in a TDBGrid, so that it looks like old-fashioned print-out sheets. By Federico Albesano. Anche in italiano.

A TDBGrid descendant with an "easy-read" coloring feature

by Federico Albesano

Click here for italian version.
Cliccare qui per la versione in italiano.

Some time ago, I read an article on the Borland Community about how to color single cells of a TDBGrid using OnDrawDataCell and DefaultDrawDataCell.
Given that I've always found it difficult to follow a screen row from left to right (I'm astigmatic), I asked myself:
why don't we make a TDBGrid descendant with rows of alternate colors?
In such a way I think it's easier to read a line from left to right without losing the correct row, so I called this feature "easy-read"!

Look at the images below and tell me what you think!


TDBGrid






TDBGridEasy

I called this new component TDBGridEasy (you can download its full source code from Code Central, with also its icon), and tree new properties define its behaviour:

  • AltColor:TColor: the alternate color of a row.
  • EasyRead:Boolean: if the component "easy-read" feature is enabled.
  • FixedBackground:Boolean: selects two different kinds of scrolling the colored background (see below).

First I redefined the DrawColumnCell method and set:

inherited DefaultDrawing:=False;

in the Create constructor to turn off default drawing so that my DrawColumnCell can get called. Then I created another DefaultDrawing property to let users of my component use OnDrawColumnCell and do their own drawing.

Now the pretty thing begins: How can I decide which color to use in a row?
So I started browsing the VCL source code looking for some useful and accessible properties.
I tried two different methods.

The first (which you can achieve setting FixedBackground:=False) was to look at record numbering mantained by the associated DataSet (the RecNo property, that for TTable and TQuery datasets works fine!).
I found that one metod for accessing that number was through the Column parameter of DrawCoumnCell, so if Column.Field.DataSet.RecNo is odd (or if it's even, if you like) and the cell hasn't to be highlited (to test this, the HighlightCell is a useful protected metod of TCustomDBGrid, as I saw while browsing its code), I set a different value for Canvas.Brush.Color and here's an entire row colored differently!


// Scrolling background

procedure TDBGridEasy.DrawColumnCell(const Rect: TRect; DataCol: Integer;
  Column: TColumn; State: TGridDrawState);
begin
  // Set cell background color the right way before calling user event
  if FEasyRead then
    if (Column.Field.DataSet.RecNo mod 2)=1 then
      if HighlightCell(DataCol,DataLink.ActiveRecord,Column.Field.DisplayText,State) then
        Canvas.Brush.Color:=clHighlight
      else
        Canvas.Brush.Color:=FAltColor;
  // If assigned and enabled, call the user drawing, otherwise do default drawing of TDBGrid
  if (not FDefDrw) and Assigned(OnDrawColumnCell) then
    OnDrawColumnCell(Self, Rect, DataCol, Column, State)
  else
    DefaultDrawColumnCell(Rect,DataCol,Column,State);
end;

The coloring works fine, but when scrolling the effect isn't so appealing, so I tried to look for a row numbering that remains the same even when scrolling (the second method of scrolling, with FixedBackground:=True).

I thought about some numbering in the original TCustomGrid: there is an interesting ARow parameter in TCustomGrid.DrawCell but unfortunately it's hidden in TCustomDBGrid.DrawCell. In this method, however, Datalink.ActiveRecord is temporarily set to ARow just before calling DrawColumnCell (ActiveRecord is the index of the current record in the internal cache of records mantained by TDataSet, one record for each row in the grid). What a luck!
So, yes, testing Datalink.ActiveRecord for evenness gives the required result.


// Fixed background

procedure TDBGridEasy.DrawColumnCell(const Rect: TRect; DataCol: Integer;
  Column: TColumn; State: TGridDrawState);
begin
  // Set cell background color the right way before calling user event
  if FEasyRead then
    if (DataLink.ActiveRecord mod 2)=0) then
      if HighlightCell(DataCol,DataLink.ActiveRecord,Column.Field.DisplayText,State) then
        Canvas.Brush.Color:=clHighlight
      else
        Canvas.Brush.Color:=FAltColor;
  // If assigned and enabled, call the user drawing, otherwise do default drawing of TDBGrid
  if (not FDefDrw) and Assigned(OnDrawColumnCell) then
    OnDrawColumnCell(Self, Rect, DataCol, Column, State)
  else
    DefaultDrawColumnCell(Rect,DataCol,Column,State);
end;

Well...almost. The surprise came when I tried to scroll the grid, and looking at TCustomDBGrid.Scroll method gives the explanation: the window's content is scrolled with Windows API call ScrollWindowEx and only the area of the added row is invalidated.
With my kind of coloring I have to force a full repaint of the grid's area even when scrolling only a line up or down.
In the Scroll method, if the Distance parameter is equal to 0, the control's window is not scrolled, so I simply redefined Scroll (fortunately declared as virtual) calling inherited Scroll(0) and then forcing Invalidate to request a repaint of the entire control client area.

procedure TDBGridEasy.Scroll(Distance: Integer);
begin
  if FEasyRead and FixedBackground then
  begin
    inherited Scroll(0);     // In this case we have to repaint all the grid !!
    Invalidate;
  end
  else
    inherited Scroll(Distance);
end;

And now, finally, I can browse through my address book database without calling the wrong person because I've lost...the right row!


Server Response from: ETNASC03