Как я могу отсортировать TList в Delphi по произвольному свойству объектов, которые он содержит? - PullRequest
7 голосов
/ 16 сентября 2010

У меня есть TList.Он содержит коллекцию объектов одного типа.Эти объекты происходят от TPersistent и имеют около 50 различных опубликованных свойств.

В моем приложении пользователь может выполнить поиск по этим объектам, и результаты поиска отображаются в TDrawGrid, причем конкретные отображаемые столбцы основаны на поиске свойств.Например, если пользователь выполняет поиск по «накладной», в таблице результатов отображается столбец «накладная».Я хотел бы позволить пользователю сортировать эту сетку.Главное, конечно, то, что я не знаю заранее, какие столбцы находятся в сетке.

Обычно, чтобы отсортировать TList, я бы просто создал функцию, такую ​​как SortOnName( p1, p2), и вызвал TListsort() метод.Я хотел бы пойти еще дальше и найти способ передать имя свойства в метод сортировки и использовать RTTI для сравнения.

Я мог бы, конечно, сделать 50 различных методов сортировки и просто использовать это.Или, установите переменную глобально или как часть класса, выполняющего всю эту работу, чтобы указать методу сортировки, на что производить сортировку.Но мне было любопытно, есть ли у кого-нибудь из профессионалов Delphi другие идеи о том, как это реализовать.

Ответы [ 2 ]

7 голосов
/ 16 сентября 2010

Delphi 7 версия Вот пример того, как этого добиться.Я использовал Delphi2010 для его реализации, но он должен работать в Delphi7, по крайней мере, так как я использовал модуль TypInfo напрямую.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Edit1: TEdit;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    FList: TList;
    procedure DoSort(PropName: String);
    procedure DoDisplay(PropName: String);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  TypInfo;

var
  PropertyName: String;

type
  TPerson = class
  private
    FName: String;
    FAge: Integer;
  published
  public
    constructor Create(Name: String; Age: Integer);
  published
    property Name: String read FName;
    property Age: Integer read FAge;
  end;

{ TPerson }

constructor TPerson.Create(Name: String; Age: Integer);
begin
  FName := Name;
  FAge := Age;
end;

function ComparePersonByPropertyName(P1, P2: Pointer): Integer;
var
  propValueP1, propValueP2: Variant;
begin
  propValueP1 := GetPropValue(P1, PropertyName, False);
  propValueP2 := GetPropValue(P2, PropertyName, False);

  if VarCompareValue(propValueP1, propValueP2) = vrEqual then begin
    Result :=  0;
  end else if VarCompareValue(propValueP1, propValueP2) = vrGreaterThan then begin
    Result :=  1;
  end else begin
    Result := -1;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FList := TList.Create;
  FList.Add(TPerson.Create('Zed', 10));
  FList.Add(TPerson.Create('John', 20));
  FList.Add(TPerson.Create('Mike', 30));
  FList.Add(TPerson.Create('Paul', 40));
  FList.Add(TPerson.Create('Albert', 50));
  FList.Add(TPerson.Create('Barbara', 60));
  FList.Add(TPerson.Create('Christian', 70));

  Edit1.Text := 'Age';

  DoSort('Age'); // Sort by age
  DoDisplay('Age');
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  DoSort(Edit1.Text);
  DoDisplay(Edit1.Text);
end;

procedure TForm1.DoSort(PropName: String);
begin
  PropertyName := PropName;
  FList.Sort(ComparePersonByPropertyName);
end;

procedure TForm1.DoDisplay(PropName: String);
var
  i: Integer;
  strPropValue: String;
begin
  ListBox1.Items.Clear;

  for i := 0 to FList.Count - 1 do begin
    strPropValue := GetPropValue(FList[i], PropName, False);
    ListBox1.Items.Add(strPropValue);
  end;
end;

end.

Кстати, я использовал простую форму с списком , edit и button .В списке отображается содержимое отсортированного списка (FList).Кнопка используется для сортировки списка в соответствии с тем, что пользователь ввел в поле ввода.

Delphi 2010 версия (используются ссылки на методы)

unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm2 = class(TForm)
    ListBox1: TListBox;
    Edit1: TEdit;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    FList: TList;
    FPropertyName: String; { << }
    procedure DoSort(PropName: String);
    procedure DoDisplay(PropName: String);
    function CompareObjectByPropertyName(P1, P2: Pointer): Integer; { << }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

uses
  TypInfo;

type
  TPerson = class
  private
    FName: String;
    FAge: Integer;
  published
  public
    constructor Create(Name: String; Age: Integer);
  published
    property Name: String read FName;
    property Age: Integer read FAge;
  end;

{ TPerson }

constructor TPerson.Create(Name: String; Age: Integer);
begin
  FName := Name;
  FAge := Age;
end;

/// This version uses a method to do the sorting and therefore can use a field of the form,
/// no more ugly global variable.
/// See below (DoSort) if you want to get rid of the field also ;)
function TForm2.CompareObjectByPropertyName(P1, P2: Pointer): Integer; { << }
var
  propValueP1, propValueP2: Variant;
begin
  propValueP1 := GetPropValue(P1, FPropertyName, False);
  propValueP2 := GetPropValue(P2, FPropertyName, False);

  if VarCompareValue(propValueP1, propValueP2) = vrEqual then begin
    Result :=  0;
  end else if VarCompareValue(propValueP1, propValueP2) = vrGreaterThan then begin
    Result :=  1;
  end else begin
    Result := -1;
  end;
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  FList := TList.Create;
  FList.Add(TPerson.Create('Zed', 10));
  FList.Add(TPerson.Create('John', 20));
  FList.Add(TPerson.Create('Mike', 30));
  FList.Add(TPerson.Create('Paul', 40));
  FList.Add(TPerson.Create('Albert', 50));
  FList.Add(TPerson.Create('Barbara', 60));
  FList.Add(TPerson.Create('Christian', 70));

  Edit1.Text := 'Age';

  DoSort('Age'); // Sort by age
  DoDisplay('Age');
end;

procedure TForm2.Button1Click(Sender: TObject);
begin
  DoSort(Edit1.Text);
  DoDisplay(Edit1.Text);
end;

procedure TForm2.DoSort(PropName: String);
begin
  FPropertyName := PropName; { << }
  FList.SortList(CompareObjectByPropertyName); { << }

  /// The code above could be written with a lambda, and without CompareObjectByPropertyName
  /// using FPropertyName, and by using a closure thus referring to PropName directly.

  /// Below is the equivalent code that doesn't make use of FPropertyName. The code below
  /// could be commented out completely and just is there to show an alternative approach.
  FList.SortList(
    function (P1, P2: Pointer): Integer
    var
      propValueP1, propValueP2: Variant;
    begin
      propValueP1 := GetPropValue(P1, PropName, False);
      propValueP2 := GetPropValue(P2, PropName, False);

      if VarCompareValue(propValueP1, propValueP2) = vrEqual then begin
        Result :=  0;
      end else if VarCompareValue(propValueP1, propValueP2) = vrGreaterThan then begin
        Result :=  1;
      end else begin
        Result := -1; /// This is a catch anything else, even if the values cannot be compared
      end;
    end);
  /// Inline anonymous functions (lambdas) make the code less readable but
  /// have the advantage of "capturing" local variables (creating a closure)
end;

procedure TForm2.DoDisplay(PropName: String);
var
  i: Integer;
  strPropValue: String;
begin
  ListBox1.Items.Clear;

  for i := 0 to FList.Count - 1 do begin
    strPropValue := GetPropValue(FList[i], PropName, False);
    ListBox1.Items.Add(strPropValue);
  end;
end;

end.

Iотмечены { << } основные изменения.

3 голосов
/ 16 сентября 2010

Обновите до Delphi> = 2009, и затем вы можете использовать анонимные методы для передачи объявления функции непосредственно в TList.Sort.

Пример можно найти по адресу http://delphi.about.com/od/delphitips2009/qt/sort-generic.htm

Я не знаю другого способа, кроме методов, которые вы описываете в своем вопросе.

...