Reusable Grid View - part 3
Autor: Dantas | Publicado: 2008-08-15T13:18:00-07:00

body {margin:0; padding:0; background:#fff; color:#000}td,body,.form {font-family:Tahoma; letter-spacing:1.1lm;font-size: 9pt}A {color:#336699; text-decoration: none;}Let's take a closer look at how to hijack a pristine TStringGrid and make it our playground without inheriting the grid directly.  The VCL class is well designed for reuse and expose properties and event handlers that we easily can grab hold of.  The OnDrawCell event is of particular interest to us.  That's a good spot to redefine how the grid retrieve and render it's content.You will be assimilatedThis is pretty self explanatory, but what we basically do is hook the draw routine and set up the appropriate number of rows and columns, and their respective default widths. procedure TGridViewController.Refresh;var  ix : Integer;begin  if not Assigned(Grid)   then Exit;  if not IsEmpty  then begin // Set Visitor's drawing handler, row and column count    Grid.Enabled := True;    Grid.OnDrawCell := DrawCell;    Grid.RowCount := RowCount + FixedRows;    Grid.ColCount := Count;    for ix := 0 to Count - 1  // Set column widths     do begin       Grid.ColWidths[ix] := (Objects[ix] as TGridViewColumn).Width;     end;  end  else begin // disable drawing handler, set rows/cols to 1 and fill in 1 "blank"    Grid.Enabled := False;    Grid.OnDrawCell := nil;    Grid.RowCount := 1;    Grid.ColCount := 1;    Grid.ColWidths[0] := 0;    Grid.Cells[0,0] := defaultEmptyCell;  end;  if Grid.RowCount > 1  then begin // Reset header and Row positions in case empty grid overrode them.    Grid.FixedRows := FixedRows;    Grid.Row := FixedRows;  end;  Grid.Invalidate; // ensure that the grid is refreshedend;OnDrawCellSo this is where all the action is... err... actually, not much happens here.  Instead we delegate the actual rendering to the column instance.procedure TGridViewController.DrawCell(Sender: TObject; ACol, ARow: Integer;  Rect: TRect; State: TGridDrawState);begin  (Objects[aCol] as TGridViewColumn).DrawCell(Grid.Canvas, aRow, Rect, State);end;The column instance then again divide the draw into two sections.  The outer DrawCell does basic housekeeping of canvas color settings and a little color trickery (a feature that snuck in while I was having fun - my bad). procedure TGridViewColumn.DrawCell(Canvas: TCanvas; aRow: Integer; Rect: TRect;  State: TGridDrawState);var  FG, ofg,  BG, obg : TColor;begin  with Canvas  do begin    ofg:=Font.Color;    obg:=Brush.Color;    if (State = []) // Not focused/selected/etc    then begin      FG := ofg;      BG := obg;      if aRow > 0 // Not a title line      then begin        ForeColor(aRow, FG);        BackColor(aRow, BG);      end;      if ofg <> FG then Font.Color:=FG;      if obg <> BG then Brush.Color:=BG;    end;    DrawCellInner(Canvas, aRow - Controller.FixedRows, Rect, State); // The business happens here    if (State = [])    then begin      if ofg <> FG then Font.Color:=ofg;      if obg <> BG then Brush.Color:=obg;    end;  end;end;The actual content rendering happens in DrawCellInner. Note that we do an adjustment related to grid layout here.  If you look at the line where DrawCellInner is called, we subtract FixedRows from aRow, ensuring that our data is zero offset based.procedure TGridViewColumn.DrawCellInner(Canvas: TCanvas; aRow: Integer;  Rect: TRect; State: TGridDrawState);var  CellText : String;  w      : Integer; // String width  txtAdj : Integer; // old adjust modebegin  if aRow < 0  then CellText := Title  else CellText := FormattedText(aRow);  with Canvas  do begin    case FAlign of      taCenter       : begin                         w:=TextWidth(CellText);                         if (w>(Rect.Right-Rect.Left))                         then w := 2                         else w := Round(((Rect.Right - Rect.Left) - w) / 2);                         TextRect(Rect, Rect.Left + w,Rect.Top + 1, CellText);                       end;      taRightJustify : begin                         txtAdj:=SetTextAlign(Handle, ta_Top or ta_Right);                         TextRect(Rect, Rect.Right - 3,Rect.Top + 1, CellText);                         SetTextAlign(Handle, txtAdj);                       end;               else // taLeftJustify                       TextRect(Rect, Rect.Left+2,Rect.Top+1, CellText);    end;  end;end;In the inner draw routine we first figure out if we are drawing the title row or a data row.  If it is a data row, we call the TGridViewColumn.FormattedText method.function TGridViewTextColumn.FormattedText(aRow: Integer): String;begin  Result := GetTextMethod(aRow);end;Does GetTextMethod look familiar?  It is the procedure variable which holds the method we implement in our descendant viewcontroller class and pass on when we set up the column.  That is the method which actually retrieve the data from the underlying data structure as described in part 2 (GetNumber, GetNumberSquared). The rest of DrawCellInner measures the retrieved text and ensure it is adjusted as desired and rendered within the cell region.  Magic ColorsBack to ForeColor and BackColor in the outer draw.  This is the closest we come to bells and whistles.  To make the grid look more sophisticated, I added support for overriding the colors as well as a very simple default rowshading algorithm. procedure TGridViewColumn.ForeColor(aRow: Integer; var Color: TColor);begin  if Assigned(FGetForeColorMethod)   then GetForeColorMethod(aRow, Color)  else if DefaultForeColor <> clNone   then Color := DefaultForeColor;end;procedure TGridViewColumn.BackColor(aRow: Integer; var Color: TColor);begin  if Assigned(FGetBackColorMethod)   then GetBackColorMethod(aRow, Color)  else if DefaultBackColor <> clNone   then Color := DefaultBackColor;  if Assigned(FGetShadeColorMethod)  then GetShadeColorMethod(aRow, Color);end;Both these methods start in the same way.  The default color have been retrieved from the grid itself in the outer draw.  First we check if a color method have been plugged in, if not - we check if a column color override has been set.For the background color routine, there is another feature - a pluggable shading method.  The default row shader looks like this...procedure TGridViewController.DefaultRowShader(const aRow:Integer; var Color:TColor);begin  if ShadeRows and (not Odd(aRow))  then begin    Color := Graphics.ColorToRGB(Color); // Translate theme/system colors to RGB    if  ((Color and $FFFFFF) > $888888)  // Lazy man's luminosity adjustment     then Color := Color - $080808     else Color := Color + $080808;  end;end;Too much flexibility?  Well, maybe I broke the KISS rule, but no rule without exceptions :) Now I can have static or procedural color adjustments per row as well as per column.Next: Reusable Grid goes code complete and come with examples.Stay tuned.&nbsp; Veja artigo completo aqui Parceiros: |BlogTips| Musas| Hacker| News| EnglishTips| TipsDelphi| FutOnline|