эффективное копирование коллекции delphi/freepascal в сетку

Я пытаюсь найти наиболее эффективный способ обновления сетки (StringGrid или KGrid) из коллекции Delphi/Lazarus FreePascal.

Одна из моих коллекций приведена ниже:

{ 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;

У меня есть следующий фрагмент кода, который я использую для обновления одной из своих сеток:

// 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;

Это хорошо для коллекций с небольшим количеством полей/свойств, но у меня также есть коллекции до 40 полей, поэтому метод, который я использую выше, слишком громоздкий.

Есть ли более эффективный способ сделать это? Кто-то предложил RTTI, но я не знаю, как его использовать.

Большое спасибо,

JDaniel


person JDaniel    schedule 24.05.2012    source источник
comment
Думали ли вы об использовании чертежа владельца? Таким образом, вам нужно получить доступ только к той части коллекции, которая действительно видна в сетке. бессовестный плагин: TdzVirtualStringGrid в dzlib   -  person dummzeuch    schedule 25.05.2012


Ответы (1)


Вот реализация Delphi Enhanced RTTI. Это действительно начиная с Delphi-2010.

Он будет искать все доступные для чтения опубликованные свойства (в объявленном порядке) и заполнять сетку значениями.

Если в качестве свойств у вас есть не только целые числа и строки, добавьте больше в оператор case.

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;

Как упоминалось в комментариях Кена, Enhanced RTTI не реализован в Lazarus/FreePascal.

Таким образом, общим решением для всех платформ будет добавление базового класса элемента коллекции с возможностью получения значений свойств.

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;

Тогда ваше объявление и реализация будут выглядеть так:

{ 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;

Просто заполните часть реализации GetPropertyCount и GetPropertyString в вашем классе TEntretien.

Этот способ может показаться таким же неэффективным, как и ваш пример, поскольку вам нужно кодировать все значения свойств вручную. Но мой пример соответствует двум основным принципам программирования:

  • Не повторяйтесь. Если вам нужно заполнить много сеток, код придется повторять во многих местах. Теперь он объявляется раз и навсегда в элементе Collection. Если вы измените дизайн элемента, просто обновите две функции.
  • Держите область ограниченной. Часть вашей программы с графическим интерфейсом должна знать как можно меньше о классе элемента коллекции.
person Community    schedule 24.05.2012
comment
+1. Хорошая реализация Delphi, но будет ли она работать с Lazarus/FreePascal? Я не думаю, что они поддерживают расширенный RTTI. - person Ken White; 25.05.2012
comment
@KenWhite, спасибо. Полностью упустил из виду вариант FreePascal. Отредактировал ответ. Вот ссылка на delphi_language_features_what_fpc_does_not_have. - person LU RD; 25.05.2012
comment
:-) Я знал, что это не работает на FP - я просто хотел вежливо обратить ваше внимание на эту половину вопроса. <грамм> - person Ken White; 25.05.2012
comment
@KenWhite добавил решение, которое подходит для всех платформ, не используя RTTI. - person LU RD; 25.05.2012
comment
Спасибо всем за ваши комментарии и предложения. @LU RD: Как бы выглядели GetPropertyCount и GetPropertyString? Кроме того, поскольку у меня много коллекций, не было бы лучше, если бы они были должным образом реализованы в TBaseItemClass, а не как виртуальные, чтобы все мои коллекции будет иметь возможность. Я думаю о правильном наследовании здесь, чтобы уменьшить повторение. - person JDaniel; 25.05.2012
comment
Хорошо, JDaniel, добавлены реализации GetPropertyCount и GetPropertyString. - person LU RD; 25.05.2012
comment
JDaniel, о реализации двух функций непосредственно в базовом классе. Это просто невыполнимо, поскольку они зависят от того, как выглядят ваши производные классы. - person LU RD; 25.05.2012