Entwickler-Ecke

Delphi Tutorials - auto-sortierte Bestenliste mit TListView (Highscore)


Narses - Do 22.02.07 11:48
Titel: auto-sortierte Bestenliste mit TListView (Highscore)
Moin!

Es gibt sicher einige Ansätze, wie man eine Bestenliste umsetzen kann, ich persönlich finde dafür aber die VCL-Komponente TListView ausgesprochen gut geeignet. Unter anderem deshalb, weil sie benutzerdefinierte Sortierungen relativ leicht ermöglicht.

Wir erzeugen ein neues Projekt und legen ein ListView (aus dem Reiter "Win32") auf das Formular. Anschließend müssen wir die Komponente noch unseren Bedürfnissen entsprechend anpassen:

Damit ist die Komponente schon fast fertig konfiguriert. Da wir aber noch eine Vergleichsprozedur zur Verfügung stellen müssen (die größte Punktzahl soll ja oben stehen), wechseln wir im ObjectInspector auf die Seite "Ereignisse" und machen einen Doppelklick beim Ereignis OnCompare. Es wird ein Ereignishandler angelegt, den wir mit folgendem Code füllen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
uses
  ..., Math; // für CompareValue()

// Sortiervergleich für die Highscore-Liste - Version _mit_ CompareValue/Math
procedure TForm1.lvHighscoreCompare(Sender: TObject; Item1, Item2: TListItem;
  Data: Integer; var Compare: Integer);
begin
  Compare := CompareValue(Integer(Item2.Data),Integer(Item1.Data));
end;

Man kann jetzt darüber streiten, ob die Unit Math für die Vergleichsoperation unbedingt nötig ist. Sicher ist es für diesen kleinen Anwendungsfall etwas überzogen, aber es gibt eine Menge überladener Varianten von CompareValue für eine Vielzahl von Datentypen, die auch gleich das erwartete Vergleichsergebnis für den Ereignishandler liefern, so dass ein Blick in diese Unit sicher nicht schaden kann. ;)

Wer die Unit Math nicht hat bzw. verwenden möchte oder mit einer Delphi-Version kleiner 6 arbeitet (CompareValue gibt es erst seit D6), kann den Vergleich auch alternativ so durchführen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
// Sortiervergleich für die Highscore-Liste - Version _ohne_ CompareValue
procedure TForm1.lvHighscoreCompare(Sender: TObject; Item1, Item2: TListItem;
  Data: Integer; var Compare: Integer);
begin
  if (Integer(Item2.Data) < Integer(Item1.Data)) then
    Compare := -1
  else if (Item2.Data = Item1.Data) then
    Compare := 0
  else
    Compare := 1;
end;

Der Witz dieses Ereignishandlers ist nun, dass damit die Komponente in der Lage ist (und zwar selbstständig!), die Einträge sortiert zu halten. :D

Wie kommen wir nun zu den Einträgen? Dazu legen wir noch einen Button auf das Formular und erstellen einen OnClick-Handler, den wir mit folgendem Code füllen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
procedure TForm1.Button1Click(Sender: TObject);
begin
  with lvHighscore.Items.Add do begin // neuen Highscore-Eintrag machen (auto-sortiert!)
    Caption := 'Narses'// Name eintragen
    Data := Pointer(Random(100)); // Punktestand zum Sortieren
    SubItems.Add(IntToStr(Integer(Data))); // und für die Ausgabe

    SubItems.Add(DateTimeToStr(Now)); // Datum eintragen
  end;
end;

Was hat es mit dem Pointer() für den Punktestand auf sich? :gruebel: Jeder Listeneintrag (vom Typ TListItem) bietet in der Eigenschaft .Data die Möglichkeit, eine Referenz auf ein beliebiges, anderes Objekt zu speichern. Deshalb ist der Datentyp allgemein Pointer. Wir wollen aber nur einen Integer speichern, was zwar vom Speicherplatz her das gleiche ist, aber für den Compiler nicht. Deshalb müssen wir diesem mit der Typumwandlung ("typecast") klar machen, dass er ruhig den Integer als Pointer betrachten darf, wenn er dann glücklicher ist... :P

Wir starten das Projekt und testen mit dem Button die Funktion. Es sollten jetzt Einträge mit zufälliger Punktzahl angelegt werden - und zwar automatisch sortiert! ;)

In Anhang befindet sich ein Demo-Projekt, in dem auch das Laden und Speichern dieser Bestenliste behandelt ist, um den Beitrag hier nicht unnötig aufzublähen.

Viel Erfolg!

cu
Narses