如何在Delphi中对其包含的对象的任意属性进行排序?

时间:2010-09-16 06:56:15

标签: delphi delphi-7

我有一个TList。它包含相同类型的对象的集合。这些对象来自TPersistent,并且具有大约50种不同的已发布属性。

在我的应用程序中,用户可以搜索这些对象,搜索结果显示在TDrawGrid中,显示的特定列基于要搜索的属性。例如,如果用户搜索“发票”,则“结果”网格中会显示“发票”列。我希望能够让用户对此网格进行排序。当然,踢球者是我不知道网格中的哪些列。

通常要对TList进行排序,我只需创建一个函数,例如SortOnName( p1, p2),然后调用TList的sort()方法。我想更进一步,找到一种方法将属性名称传递给sort方法,并使用RTTI进行比较。

当然,我可以制作50种不同的排序方法并使用它。或者,全局设置一个变量,或者作为完成所有这些工作的类的一部分,以向排序方法指示要排序的内容。但我很好奇Delphi专业人员是否有其他想法如何实现这一点。

2 个答案:

答案 0 :(得分:7)

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.

BTW,我使用了一个带有列表框的简单表单,一个编辑和一个按钮。列表框显示已排序的列表(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.

我用{ << }标记了主要更改。

答案 1 :(得分:3)

升级到Delphi&gt; = 2009,然后您可以使用匿名方法将函数声明直接传递给TList.Sort。

可以找到一个例子 http://delphi.about.com/od/delphitips2009/qt/sort-generic.htm

除了您在问题中描述的方法之外,我不知道任何其他方式。