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

Gra*_*erB 7 delphi delphi-7

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

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

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

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

Tri*_*dad 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.
Run Code Online (Sandbox Code Playgroud)

顺便说一句,我使用了一个带有列表框,编辑按钮的简单表格.列表框显示已排序的列表(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.
Run Code Online (Sandbox Code Playgroud)

我标记{ << }了主要的变化.

  • 谢谢特立尼达.这可能是我最终会使用的方法(上面的代码).我认为唯一的另一个选择是创建一个后代TList并实现我自己的快速排序,它接受一个属性名参数,或者至少接受一个方法(对象的过程)作为sort方法的参数,而不仅仅是一个过程. (2认同)