2010-09-16 50 views
7

Tengo un TList. Contiene una colección de objetos del mismo tipo. Estos objetos descienden de un TPersistent y tienen aproximadamente 50 propiedades publicadas diferentes.¿Cómo puedo ordenar un TList en Delphi en una propiedad arbitraria de los objetos que contiene?

En mi aplicación, el usuario puede emitir una búsqueda de estos objetos, y los resultados de la búsqueda se muestran en un TDrawGrid, con las columnas específicas que se muestran basadas en las propiedades que se buscan. Por ejemplo, si el usuario busca en 'factura', se muestra una columna 'factura' en la cuadrícula de resultados. Me gustaría poder dejar que el usuario clasifique esta cuadrícula. El pateador, por supuesto, es que no sabré por adelantado qué columnas están en la grilla.

Normalmente, para ordenar un TList, solo hago una función, como SortOnName(p1, p2), y llamo al método sort() de TList. Me gustaría dar un paso más y encontrar la manera de pasar el nombre de una propiedad al método de clasificación y usar RTTI para hacer la comparación.

Pude, por supuesto, hacer 50 métodos de clasificación diferentes y simplemente usar eso. O bien, establezca una variable globalmente o como parte de la clase haciendo todo este trabajo para indicar al método de clasificación en qué ordenar. Pero tenía curiosidad si alguno de los profesionales de Delphi tenía otras ideas sobre cómo implementar esto.

Respuesta

6

Delphi 7 versión Aquí hay un ejemplo de cómo lograr eso. Utilicé Delphi2010 para implementarlo, pero debería funcionar en Delphi7 al menos porque usé la unidad TypInfo directamente.

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. 

Por cierto, he usado un sencillo formulario con un cuadro de lista , una edición y un botón . El cuadro de lista muestra los contenidos de la lista (FList) ordenados. El botón se usa para ordenar la lista de acuerdo con lo que el usuario ha escrito en el cuadro de edición.

Delphi versión 2010 (utiliza referencias de los métodos)

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. 

I marcados con { << } los principales cambios.

+0

Ah, si tiene Delphi2010, entonces puede usar una lambda para deshacerse de la variable global al convertir ComparePersonByPropertyName en un método y en lugar de Sort() debe usar SortList() – Trinidad

+2

Gracias Trinidad. Ese es probablemente el enfoque que terminaré usando (el código anterior). Creo que la única otra opción es crear un TList descendente e implementar mi propia clasificación rápida que acepte un parámetro de nombre de propiedad, o al menos aceptar un método (procedimiento de objeto) como un parámetro para el método de ordenación, en lugar de solo un procedimiento . – GrandmasterB

3

Actualice a Delphi> = 2009, y luego puede usar métodos anónimos para pasar una declaración de función directamente a TList.Sort.

Un ejemplo se puede encontrar en http://delphi.about.com/od/delphitips2009/qt/sort-generic.htm

No sé de ninguna otra manera, aparte de los métodos que usted describe en su pregunta.

+0

Gracias, pero no estoy seguro de que sirviera de algo, ya que la función de clasificación aún necesita una forma de saber qué propiedad ordenar, que solo se conoce en tiempo de ejecución. (Tengo D2010, por cierto) – GrandmasterB

+0

Pero pasaría esa propiedad conocida en tiempo de ejecución como parámetro. Escriba el código que ordena por algo, y luego dígale cuál es ese algo en tiempo de ejecución. –

+1

Veo el problema, sí, es posible que tenga que hacer una declaración de caso para saber qué comparar en el método anónimo, p. MyList.Sort (TComparer .Construct ( función (const L, R: TMyObj): número entero comienzan si Edit1 = 'edad' entonces el resultado: = CompareValue (L.Age, R.Age) extremo )); –

Cuestiones relacionadas