1

I am trying to find the most efficient way of updating a grid (StringGrid or KGrid) from a Delphi/Lazarus FreePascal collection.

One of my collections is as listed below:

{ TEntretien }
TEntretien = class(TCollectionItem)
private
  { private declarations }
  FPrenom: string;
  FSexe: string;
  FSigneDistinctif: string;
  FPays: string;
  FTotale: integer;
  FColumns: integer;
public
  { public declarations }
published
  { published declarations }
  property Prenom: string read FPrenom write FPrenom;
  property Sexe: string read FSexe write FSexe;
  property SigneDistinctif: string read FSigneDistinctif write FSigneDistinctif;
  property Pays: string read FPays write FPays;
property Totale: integer read FTotale write FTotale;
end;

{ TEntretiens }
TEntretiens = class(TCollection)
private
  { private declarations }
  function GetItem(AIndex: integer): TEntretien;
public
  { public declarations }
  constructor Create;
  function Add: TEntretien;
  property Items[AIndex: integer]: TEntretien read GetItem; default;
end;

I have the following code snippet that I use for updating one of my grids:

// Fill the grid with the results of the query
for intGridRow := 0 to intNumberOfRows - 1 do
begin
  for intGridCol := 0 to intNumberOfColumns - 1 do
  begin
    // Write the rest of the retrieved data into the grid proper   USE RTTI HERE??
    if intGridCol = 0 then
      kgGridName.Cells[intGridCol + kgGridName.FixedCols, intGridRow + kgGridName.FixedRows] := 
         AEntretiens[intGridRow].Prenom
    else if intGridCol = 1 then
       kgGridName.Cells[intGridCol + kgGridName.FixedCols, intGridRow + kgGridName.FixedRows] :=
         AEntretiens[intGridRow].Sexe
    else if intGridCol = 2 then
       kgGridName.Cells[intGridCol + kgGridName.FixedCols, intGridRow + kgGridName.FixedRows] :=
         AEntretiens[intGridRow].SigneDistinctif
    else if intGridCol = 3 then
       kgGridName.Cells[intGridCol + kgGridName.FixedCols, intGridRow + kgGridName.FixedRows] :=
         AEntretiens[intGridRow].Pays
    else if intGridCol = 4 then
       kgGridName.Cells[intGridCol + kgGridName.FixedCols, intGridRow + kgGridName.FixedRows] := IntToStr(AEntretiens[intGridRow].Totale)
    end;
end;

This is fine for collections with a small number of fields/properties but I also have collections with up to 40 fields and so the method I use above is too cumbersome.

Is there a more efficient way of doing this? Someone suggested RTTI but I don't know how to use it.

Thanks a lot,

JDaniel

4

1 回答 1

3

Here is a Delphi Enhanced RTTI implementation. This is valid from Delphi-2010.

It will search for all readable published properties (in the declared order) and fill the grid with values.

If you have more than integer and strings as properties, add more in the case statement.

uses
  System.Classes,System.RTTI,System.TypInfo, System.SysUtils;

procedure Test;
// Fill the grid with the results of the query
Var
  AnItem     : TEntretien;
  fixedCols  : integer;
  fixedRows  : integer;
  ARow,ACol  : integer;
  intGridRow : integer;
  context    : TRttiContext;
  rType      : TRttiType;
  prop       : TRttiProperty;
  value      : TValue;
  s          : String;
begin
  context := TRttiContext.Create;
  rType := context.GetType(TEntretien);
  fixedCols := kgGridName.FixedCols;
  fixedRows := kgGridName.FixedRows;
  for intGridRow := 0 to intNumberOfRows - 1 do
  begin
    AnItem := AEntretiens[intGridRow];
    ARow := intGridRow + fixedRows;
    ACol := fixedCols;
    for prop in rType.GetProperties do
    begin
      if prop.IsReadable then
      begin
        s := '';
        value := prop.GetValue(AnItem);
        case prop.PropertyType.TypeKind of
          tkInteger : s := IntToStr(value.AsInteger);
          tkString : s := value.AsString;
        end;
        kgGridName.Cells[ACol, ARow] := s;
        Inc(ACol);
      end;
    end;
  end;
end;

As mentioned in the comments by Ken, Enhanced RTTI is not implemented in Lazarus/FreePascal.

A generic solution for all platforms would then be to add a collection item base class with the possibilities to get the property values.

Type
  TBaseItemClass = class(TCollectionItem)
    private
      function GetPropertyCount : integer; virtual; abstract;  
      function GetPropertyString( index : integer) : string; virtual; abstract;
    public
      property PropertyCount : integer read GetPropertyCount;
      property PropertyString[index : integer] : string read GetPropertyString;
  end;

Then your declaration and implementation would look like this :

{ TEntretien }
TEntretien = class(TBaseItemClass)
private
  { private declarations }
  FPrenom: string;
  FSexe: string;
  FSigneDistinctif: string;
  FPays: string;
  FTotale: integer;
  FColumns: integer;
  function GetPropertyCount : integer; override;
  function GetPropertyString( index : integer) : string; override;
public
  { public declarations }
published
  { published declarations }
  property Prenom: string read FPrenom write FPrenom;
  property Sexe: string read FSexe write FSexe;
  property SigneDistinctif: string read FSigneDistinctif write FSigneDistinctif;
  property Pays: string read FPays write FPays;
  property Totale: integer read FTotale write FTotale;
end;

function TEntretien.GetPropertyCount : integer;
begin
  Result := 5;
end;

function TEntretien.GetPropertyString(index : integer) : string;
begin
  Result := '';
  case index of
    0 : Result := Prenom;
    1 : Result := Sexe;
    2 : Result := SigneDistinctif;
    3 : Result := Pays;
    4 : Result := IntToStr(Totale);
  end;
end;

procedure Test1;
// Fill the grid with the results of the query
Var
  AnItem    : TEntretien;
  intGridRow,intNumberOfRows : Integer;
  fixedCols : integer;
  fixedRows : integer;
  ARow      : integer;
  i         : integer;
begin
  fixedCols := kgGridName.FixedCols;
  fixedRows := kgGridName.FixedRows;
  for intGridRow := 0 to intNumberOfRows - 1 do
  begin
    AnItem := AEntretiens[intGridRow];
    ARow := intGridRow + FixedRows;
    for i := 0 to AnItem.PropertyCount - 1 do
    begin
      kgGridName.Cells[i + FixedCols, ARow] := AnItem.PropertyString[i];
    end;
  end;
end;

Just fill in the GetPropertyCount and GetPropertyString implementation part in your TEntretien class.

This way of doing it may seem as inefficient as your example by having to code all property values by hand. But doing as my example is conforming to two basic principles in programming :

  • Don't repeat yourself. If you have many grids to fill, the code would have to be repeated in many places. Now it's declared once and for all within the Collection item. Should you redesign the item, just update the two functions.
  • Keep the scope limited. The GUI part of your program should know as little as possible about the collection item class.
于 2012-05-24T22:17:58.190 回答