代码之家  ›  专栏  ›  技术社区  ›  GrandmasterB

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

  •  7
  • GrandmasterB  · 技术社区  · 14 年前

    在我的应用程序中,用户可以对这些对象进行搜索,搜索结果显示在TDrawGrid中,特定列的显示基于所搜索的属性。例如,如果用户搜索“invoice”,结果网格中将显示“invoice”列。我想能够让用户排序这个网格。当然,关键是我不知道网格中有哪些列。

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

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

    2 回复  |  直到 14 年前
        1
  •  7
  •   Trinidad    11 年前

    Delphi 7版本

    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.
    

    顺便说一句,我用了一个简单的表格和 ,安 编辑 按钮

    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.
    

    我打了个记号 { << }

        2
  •  3
  •   Alan Clark    14 年前

    升级到Delphi>=2009,然后可以使用匿名方法将函数声明直接传递到列表排序.

    http://delphi.about.com/od/delphitips2009/qt/sort-generic.htm