2010-09-16 19 views
7

Ho un TList. Contiene una raccolta di oggetti dello stesso tipo. Questi oggetti discendono da un TPersistent e hanno circa 50 diverse proprietà pubblicate.Come posso ordinare un TList in Delphi su una proprietà arbitraria degli oggetti che contiene?

Nella mia applicazione, l'utente può eseguire una ricerca di questi oggetti e i risultati della ricerca vengono visualizzati in un TDrawGrid, con le colonne specifiche visualizzate in base alle proprietà ricercate. Ad esempio, se l'utente cerca su "fattura", una colonna "fattura" viene visualizzata nella griglia dei risultati. Vorrei poter consentire all'utente di ordinare questa griglia. Il kicker, ovviamente, è che non saprò in anticipo quali colonne ci sono nella griglia.

Normalmente per ordinare un TList, vorrei semplicemente fare una funzione, come ad esempio SortOnName(p1, p2), e chiamare il metodo sort() del TList. Mi piacerebbe fare un ulteriore passo avanti e trovare un modo per passare un nome di proprietà al metodo di ordinamento e utilizzare RTTI per effettuare il confronto.

Potrei, ovviamente, realizzare 50 diversi metodi di ordinamento e usarlo. Oppure, impostare una variabile globalmente o come parte della classe facendo tutto questo lavoro per indicare al metodo di ordinamento su cosa ordinare. Ma ero curioso di sapere se qualcuno dei professionisti di Delphi avesse altre idee su come implementarlo.

risposta

6

Delphi 7 versione Ecco un esempio di come ottenere ciò. Ho usato Delphi2010 per implementarlo ma dovrebbe funzionare in Delphi7 almeno come ho usato direttamente l'unità 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. 

A proposito, ho usato un semplice modulo con una casella di riepilogo , un modifica e un pulsante . La lista mostra i contenuti della lista (FList) ordinati. Il pulsante viene utilizzato per ordinare l'elenco in base a ciò che l'utente ha digitato nella casella di modifica.

Delphi 2010 versione (usa i riferimenti ai metodi)

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. 

ho segnato con { << } i principali cambiamenti.

+0

Oh, se hai Delphi2010 allora puoi usare un lambda per sbarazzarti della variabile globale girando ComparePersonByPropertyName in un metodo e invece di Sort() devi usare SortList() – Trinidad

+2

Grazie Trinidad. Questo è probabilmente l'approccio che userò (il codice sopra). Penso che l'unica altra opzione sia creare un TList discendente e implementare il mio ordinamento rapido che accetti un parametro nome proprietà, o almeno accetti un metodo (procedura dell'oggetto) come parametro per il metodo di ordinamento, piuttosto che una semplice procedura . – GrandmasterB

3

Aggiornamento a Delphi> = 2009, quindi è possibile utilizzare metodi anonimi per passare una dichiarazione di funzione direttamente in TList.Sort.

Un esempio può essere trovato alla http://delphi.about.com/od/delphitips2009/qt/sort-generic.htm

Io non conosco nessun altro modo, diverso dai metodi che descrivi nella tua domanda.

+0

Grazie, ma non sono sicuro che farebbe il trucco dato che la funzione di ordinamento ha ancora bisogno di un modo per sapere quale proprietà ordinare, che è nota solo al runtime. (Ho D2010, btw) – GrandmasterB

+0

Ma si passerebbe la proprietà known-at-runtime come parametro. Scrivi il codice che ordina in base a qualcosa, quindi indica a quale oggetto è in esecuzione. –

+1

Vedo il problema Sì, potrebbe essere necessario eseguire un'istruzione case per sapere cosa confrontare nel metodo anonimo, ad es. MyList.Sort (TComparer .Construct ( funzione (const L, R: TMyObj): integer iniziare se Edit1 = 'età' quindi risultati: = CompareValue (L.Age, R.Age) fine )); –

Problemi correlati