2010-09-16 15 views
7

TList sahibiyim. Aynı türdeki nesnelerin bir koleksiyonunu içerir. Bu nesneler bir TPersistent'ten türemiştir ve yaklaşık 50 farklı yayınlanmış özelliğe sahiptir.Delphi'de bir TList'i içerdiği nesnelerin keyfi özelliğinde nasıl sıralayabilirim?

Uygulamamda, kullanıcı bu nesnelerin bir aramasını yayınlayabilir ve aramanın sonuçları, görüntülenen belirli sütunlara göre görüntülenen bir TDrawGrid'de görüntülenir. Örneğin, kullanıcı 'fatura' üzerinden arama yapıyorsa, sonuçların kılavuzunda bir 'fatura' sütunu görüntülenir. Kullanıcının bu ızgarayı sıralamasına izin vermek istiyorum. Kicker, tabii ki, hangi sütunların ızgarada olduğunu bilmeyeceğim.

Normalde TList'i sıralamak için, SortOnName(p1, p2) gibi bir işlev yapabilir ve TList'in sort() yöntemini çağırırdım. Bir adım daha ileri gitmek ve sıralama yöntemine bir özellik ismini iletmenin bir yolunu bulmak ve karşılaştırmayı yapmak için RTTI'yi kullanmak istiyorum.

Elbette 50 farklı sıralama yöntemi yapabilirim ve sadece bunu kullanabilirim. Veya, genel olarak ya da sınıfın bir parçası olarak tüm bu işi yaptığınız sıralama yöntemine göre sıralamak için bir değişken belirleyin. Ama Delphi profesyonellerinin herhangi birinin bunun nasıl uygulanacağına dair başka fikirleri varsa merak ettim.

cevap

6

Delphi 7 sürüm İşte bunun nasıl elde edileceğine dair bir örnek. Delphi2010'u uygulamak için kullandım ama en azından TypInfo ünitesini doğrudan kullandığım gibi Delphi7'de çalışmalı.

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, bir liste, bir düzenlemek ve düğmeye ile basit bir formu kullanılır. Liste kutusu, listenin (FList) içeriğini sırayla gösterir. Düğme, listeyi kullanıcının düzenleme kutusuna yazdıklarına göre sıralamak için kullanılır.

Delphi (yöntemlerine referanslar kullanır) 2010 sürümü

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. 

Ben { << } ana değişikliklerle işaretlenmiş.

+0

Eğer Delphi2010'unuz varsa, o zaman bir lambda'yı ComparePersonByPropertyName'i bir yönteme çevirerek global değişkenden kurtulmak için kullanabilirsiniz ve Sort() yerine SortList() – Trinidad

+2

Teşekkürler Trinidad'yı kullanmalısınız. Muhtemelen ben kullanacağım yaklaşım (yukarıdaki kod). Bence diğer bir seçenek, bir descendent TList oluşturmak ve bir özellik adı parametresini kabul edecek veya en azından bir yöntemden ziyade sıralama yöntemine bir parametre olarak en azından bir yöntemi (nesne prosedürünü) kabul edecek kendi hızlı sıralamaımı uygulamaktır. . – GrandmasterB

3

Delphi> = 2009'a yükseltin ve sonra bir işlev bildirimini doğrudan TList.Sort'a geçirmek için anonim yöntemler kullanabilirsiniz.

bir örnek

Eğer söz konusu açıklayan yöntemlere dışındaki http://delphi.about.com/od/delphitips2009/qt/sort-generic.htm

ben başka bir yolunu bilmiyorum bulunabilir.

+0

Teşekkürler, ancak sıralama işlevinin, yalnızca çalışma zamanında bilindiği, hangi özelliklerin sıralanacağını bilmenin bir yoluna ihtiyaç duymasından bu yana hile yapacağından emin değilim. (D2010, btw'm var) – GrandmasterB

+0

Ama bu bilinen çalışma zamanı özelliğini bir parametre olarak iletecektiniz. Bir şeye göre sıralayan kod yazın ve sonra bir şeyin çalışma zamanında olduğunu söyleyin. –

+1

Sorunu evet görüyorum, anonim yöntemde neyi karşılaştırmanız gerektiğini öğrenmek için bir vaka bildirimi yapmanız gerekebilir, ör. MyList.Sort (TComparer .Construct ( fonksiyonu (const L, R: TMyObj): Edit1 = 'Yaş' sonra sonuçlanıyorsa tamsayıdır başlar: = CompareValue (L.Age, R.Age) ucu )); –

İlgili konular