2010-09-16 4 views
7

Ich habe eine TList. Es enthält eine Sammlung von Objekten des gleichen Typs. Diese Objekte stammen von einem TPersistent ab und haben ungefähr 50 verschiedene veröffentlichte Eigenschaften.Wie kann ich eine TList in Delphi auf einer beliebigen Eigenschaft der enthaltenen Objekte sortieren?

In meiner Anwendung kann der Benutzer eine Suche nach diesen Objekten durchführen, und die Ergebnisse der Suche werden in einem TDrawGrid angezeigt, wobei die angezeigten spezifischen Spalten auf den gesuchten Eigenschaften basieren. Wenn der Benutzer beispielsweise nach "Rechnung" sucht, wird eine Spalte "Rechnung" im Ergebnisraster angezeigt. Ich möchte in der Lage sein, den Benutzer dieses Gitter sortieren zu lassen. Der Kicker ist natürlich, dass ich vorne nicht weiß, welche Spalten im Raster sind.

Normalerweise um eine TList zu sortieren, würde ich einfach eine Funktion wie SortOnName(p1, p2) machen und die sort() Methode der TList aufrufen. Ich möchte noch einen Schritt weiter gehen und einen Weg finden, einen Eigenschaftsnamen an die Sortiermethode zu übergeben und RTTI für den Vergleich zu verwenden.

Ich könnte natürlich 50 verschiedene Sortiermethoden machen und einfach das verwenden. Oder setzen Sie eine Variable global oder als Teil der Klasse, die all diese Arbeiten ausführt, um der Sortiermethode anzuzeigen, wonach sortiert werden soll. Aber ich war neugierig, ob irgendwelche der Delphi-Profis da draußen andere Ideen hatten, wie das zu implementieren ist.

Antwort

6

Delphi 7 Version Hier ist ein Beispiel, wie man das erreicht. Ich habe Delphi2010 verwendet, um es zu implementieren, aber es sollte zumindest in Delphi7 funktionieren, da ich die Typinfo-Einheit direkt verwendete.

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, habe ich ein einfaches Formular mit einem listbox, ein bearbeiten und Taste. Die Listbox zeigt den Inhalt der Liste (FList) sortiert an. Die Schaltfläche wird verwendet, um die Liste nach dem zu sortieren, was der Benutzer in der Bearbeitungsbox eingegeben hat.

Delphi 2010 Version (Verweise auf Methoden verwendet)

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. 

ich die mit { << } die wichtigsten Änderungen.

+0

Oh, wenn Sie Delphi2010 haben, dann können Sie ein Lambda verwenden, um die globale Variable loszuwerden, indem Sie ComparePersonByPropertyName in eine Methode verwandeln und statt Sort() müssen Sie SortList() verwenden – Trinidad

+2

Danke Trinidad. Das ist wahrscheinlich der Ansatz, den ich am Ende verwenden werde (der Code oben). Ich denke, die einzige andere Option ist, eine abgeleitete TList zu erstellen und meine eigene schnelle Sortierung zu implementieren, die einen Eigenschaftsnamenparameter akzeptiert oder zumindest eine Methode (Prozedur des Objekts) als einen Parameter für die Sortiermethode und nicht nur eine Prozedur akzeptiert . – GrandmasterB

3

Upgrade auf Delphi> = 2009, und dann können Sie anonyme Methoden verwenden, um eine Funktionsdeklaration direkt in TList.Sort zu übergeben.

Ein Beispiel kann bei http://delphi.about.com/od/delphitips2009/qt/sort-generic.htm

Ich weiß nicht von irgendeiner anderen Art und Weise, andere als die Methoden, die Sie in Ihrer Frage beschreiben finden.

+0

Danke, aber ich bin nicht sicher, dass das den Trick machen würde, da die Sortierfunktion immer noch eine Möglichkeit braucht zu wissen, welche Eigenschaft zu sortieren ist, die nur zur Laufzeit bekannt ist. (Ich habe D2010, btw) – GrandmasterB

+0

Aber Sie würden diese bekannte Laufzeit-Eigenschaft als Parameter übergeben. Schreibe Code, der nach etwas sortiert, und erzähle ihm dann, welcher zur Laufzeit etwas ist. –

+1

Ich sehe das Problem ja, Sie müssen möglicherweise eine case-Anweisung zu wissen, was in der anonymen Methode, z. MyList.Sort (TComparer .Construct ( Funktion (const L, R: TMyObj): integer beginnen, wenn Edit1 = 'Alter' ergeben sich dann: = CompareValue (L.Age, R.Age) Ende )); –