Virtual Listview Sortieren

Rund um die LCL und andere Komponenten
Antworten
king558
Beiträge: 25
Registriert: So 27. Aug 2023, 16:44

Virtual Listview Sortieren

Beitrag von king558 »

Hallo,

ich verwende Lazarus 3.0RC1 und FPC 3.2.2 OwnerData ist auf True gesetzt, nur der Breakpoint bei der Funktion TForm1.ListView1Compare und die Zeile iter := 0; wird gar nicht aufgerufen.

Code: Alles auswählen

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ComCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    ListView1: TListView;
    procedure FormCreate(Sender: TObject);
    procedure ListView1ColumnClick(Sender: TObject; Column: TListColumn);
    procedure ListView1Compare(Sender: TObject; Item1, Item2: TListItem;
      Data: Integer; var Compare: Integer);
    procedure ListView1Data(Sender: TObject; Item: TListItem);
  private
    cnt : Integer;
  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.ListView1Compare(Sender: TObject; Item1, Item2: TListItem;
  Data: Integer; var Compare: Integer);
var
  iter : Integer;
begin
  iter := 0;
end;

procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
begin
  Inc( Self.cnt );
  Item.Caption := 'Test' + IntToStr( Self.cnt );
  Item.SubItems.Add( 'Test' + IntToStr( Self.cnt ) );
  Item.SubItems.Add( 'Test' + IntToStr( Self.cnt ) );
  Item.SubItems.Add( 'Test' + IntToStr( Self.cnt ) );
end;

procedure TForm1.ListView1ColumnClick(Sender: TObject; Column: TListColumn);
begin
  Self.ListView1.ColumnClick := True;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Self.cnt := 0;
  Self.ListView1.Items.Count := 10;
end;

end.

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1354
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Virtual Listview Sortieren

Beitrag von fliegermichl »

Ich vermute mal der wird wegoptimiert.
Iter ist als lokale Variable deklariert.
Du bekommst garantiert auch den Hinweis, Iter is assigned but not used.

king558
Beiträge: 25
Registriert: So 27. Aug 2023, 16:44

Re: Virtual Listview Sortieren

Beitrag von king558 »

fliegermichl hat geschrieben:
Fr 29. Sep 2023, 08:23
Ich vermute mal der wird wegoptimiert.
Iter ist als lokale Variable deklariert.
Du bekommst garantiert auch den Hinweis, Iter is assigned but not used.
Ich habe nun die Compare Funktion etwas erweitert, trotz funktioniert das Sortieren nicht, weil die Funktion ListView1Compare nicht aufgerufen wird, da habe ich einen Breakpoint in der Zeile gesetzt "if Self._sortedColumn = 0 then", Breakpoint wird nicht getriggert.

Code: Alles auswählen

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ComCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    ListView1: TListView;
    procedure FormCreate(Sender: TObject);
    procedure ListView1ColumnClick(Sender: TObject; Column: TListColumn);
    procedure ListView1Compare(Sender: TObject; Item1, Item2: TListItem;
      Data: Integer; var Compare: Integer);
    procedure ListView1Data(Sender: TObject; Item: TListItem);
  private
    cnt : Integer;
    _sortedColumn : Integer;
    _descending : Boolean;
  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.ListView1Compare(Sender: TObject; Item1, Item2: TListItem;
  Data: Integer; var Compare: Integer);
var
  iter : Integer;
begin
  if Self._sortedColumn = 0 then
    Compare := CompareText( Item1.Caption, Item2.Caption )
  else
    Compare := CompareText( Item1.SubItems[ Self._sortedColumn - 1 ], Item2.SubItems[ Self._sortedColumn - 1 ] );
  if Self._descending then Compare := -Compare;
end;

procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
begin
  Inc( Self.cnt );
  Item.Caption := 'Test' + IntToStr( Self.cnt );
  Item.SubItems.Add( 'Test' + IntToStr( Self.cnt ) );
  Item.SubItems.Add( 'Test' + IntToStr( Self.cnt ) );
  Item.SubItems.Add( 'Test' + IntToStr( Self.cnt ) );
end;

procedure TForm1.ListView1ColumnClick(Sender: TObject; Column: TListColumn);
begin
  Self.ListView1.SortType := stNone;
  Self.ListView1.SortType := stText;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Self.cnt := 0;
  Self.ListView1.Items.Count := 10;
end;

end.

wp_xyz
Beiträge: 4781
Registriert: Fr 8. Apr 2011, 09:01

Re: Virtual Listview Sortieren

Beitrag von wp_xyz »

Ganz allgemein: Eine ListView im virtuellen Modus über das OnCompare-Ereignis sortieren? Das wird nicht funktionieren. Virtueller Modus, also OwnerData=true, bedeutet, dass die anzuzeigenden Informationen in der ListView selbst gar nicht enthalten sind, sondern in einer externen Datenstruktur; das Ereignis OnData ordnet dem gerade benötigten ListItem über den ListItem.Index die Daten aus dieser externen Struktur zu. Wenn du jetzt in OnCompare zwei ListItems miteinander vergleichen würdest, werden die jeweiligen Daten abgerufen, und du kannst vielleicht feststellen, Item1 ist kleiner als Item2, aber der von TListIems verwendete QuickSort ist der von TFPList, der nur die Reihenfolge vertauscht, aber nicht den veränderten Index in die Items schreibt - denn das wäre nötig, damit die ListView beim Auslesen der Daten auf den richtigen Index in der externen Datenstruktur zugreift. Somit behalten beide Items dieselbe Position, und die Reihenfolge ändert sich nicht. Aber so weit kommt es schon gar nicht, denn die interne Items-Liste ist im virtuellen Modus ja leer, d.h. die Sort-Methode wird gar nicht ausgeführt.

Die Lösung ist aber ganz einfach: Du musst im virtuellen Modus die externe Datenstruktur selbst sortieren und dann die ListView neu anzeigen.

Weiterhin meine ich, dass deine OnData-Routine nicht in Ordnung ist: Bei jedem Zugriff erhöhst du einen internen Zähler, cnt. Das ist deine "externe Datenstruktur", die ich oben erwähnte. Das bedeutet, dass jeder ListItem bei jedem Zugriff einen anderen Wert hat. Kann mir nicht vorstellen, dass das so beabsichtigt ist.

In der Anlage findest du ein funktionierendes Beispiel für das Sortieren einer virtuellen ListView. Die "externe Datenquelle" ist hier zur Vereinfachung eine StringList, mit fiktiven Dokumenten, bei denen das Datum dummerweise hinten angehängt ist (statt vorne), und die nach Datum sortiert werden sollen. Wenn du das Programm startest, ist die Listview zum Vergleich im normalen, nicht-virtuellen Modus, und das Sortieren (getriggert mit den "Sort ListView"-Button) funktioniert einwandfrei. Wenn aber die "virtual mode" Checkbox selektiert ist, ist der Button wirkungslos. Sortiert man aber mit dem Button "Sort Data" die externe StringList, ist alles in Ordnung.
Dateianhänge
virtual_listview_sort.zip
(3.13 KiB) 6-mal heruntergeladen

king558
Beiträge: 25
Registriert: So 27. Aug 2023, 16:44

Re: Virtual Listview Sortieren

Beitrag von king558 »

wp_xyz hat geschrieben:
Fr 29. Sep 2023, 15:14
Ganz allgemein: Eine ListView im virtuellen Modus über das OnCompare-Ereignis sortieren? Das wird nicht funktionieren. Virtueller Modus, also OwnerData=true, bedeutet, dass die anzuzeigenden Informationen in der ListView selbst gar nicht enthalten sind, sondern in einer externen Datenstruktur; das Ereignis OnData ordnet dem gerade benötigten ListItem über den ListItem.Index die Daten aus dieser externen Struktur zu. Wenn du jetzt in OnCompare zwei ListItems miteinander vergleichen würdest, werden die jeweiligen Daten abgerufen, und du kannst vielleicht feststellen, Item1 ist kleiner als Item2, aber der von TListIems verwendete QuickSort ist der von TFPList, der nur die Reihenfolge vertauscht, aber nicht den veränderten Index in die Items schreibt - denn das wäre nötig, damit die ListView beim Auslesen der Daten auf den richtigen Index in der externen Datenstruktur zugreift. Somit behalten beide Items dieselbe Position, und die Reihenfolge ändert sich nicht. Aber so weit kommt es schon gar nicht, denn die interne Items-Liste ist im virtuellen Modus ja leer, d.h. die Sort-Methode wird gar nicht ausgeführt.

Die Lösung ist aber ganz einfach: Du musst im virtuellen Modus die externe Datenstruktur selbst sortieren und dann die ListView neu anzeigen.

Weiterhin meine ich, dass deine OnData-Routine nicht in Ordnung ist: Bei jedem Zugriff erhöhst du einen internen Zähler, cnt. Das ist deine "externe Datenstruktur", die ich oben erwähnte. Das bedeutet, dass jeder ListItem bei jedem Zugriff einen anderen Wert hat. Kann mir nicht vorstellen, dass das so beabsichtigt ist.

In der Anlage findest du ein funktionierendes Beispiel für das Sortieren einer virtuellen ListView. Die "externe Datenquelle" ist hier zur Vereinfachung eine StringList, mit fiktiven Dokumenten, bei denen das Datum dummerweise hinten angehängt ist (statt vorne), und die nach Datum sortiert werden sollen. Wenn du das Programm startest, ist die Listview zum Vergleich im normalen, nicht-virtuellen Modus, und das Sortieren (getriggert mit den "Sort ListView"-Button) funktioniert einwandfrei. Wenn aber die "virtual mode" Checkbox selektiert ist, ist der Button wirkungslos. Sortiert man aber mit dem Button "Sort Data" die externe StringList, ist alles in Ordnung.
Das ist wirklich ausführlich erklärt, vielen lieben Danke. Ich werd mir deine Beispielcode anschauen. Du hast Recht, der Code welche ich gepostet habe, ist natürlich schnell gecodet, als ein Beispiel. Der Zähler dort ist schlecht.

king558
Beiträge: 25
Registriert: So 27. Aug 2023, 16:44

Re: Virtual Listview Sortieren

Beitrag von king558 »

wp_xyz hat geschrieben:
Fr 29. Sep 2023, 15:14
Ganz allgemein: Eine ListView im virtuellen Modus über das OnCompare-Ereignis sortieren? Das wird nicht funktionieren. Virtueller Modus, also OwnerData=true, bedeutet, dass die anzuzeigenden Informationen in der ListView selbst gar nicht enthalten sind, sondern in einer externen Datenstruktur; das Ereignis OnData ordnet dem gerade benötigten ListItem über den ListItem.Index die Daten aus dieser externen Struktur zu. Wenn du jetzt in OnCompare zwei ListItems miteinander vergleichen würdest, werden die jeweiligen Daten abgerufen, und du kannst vielleicht feststellen, Item1 ist kleiner als Item2, aber der von TListIems verwendete QuickSort ist der von TFPList, der nur die Reihenfolge vertauscht, aber nicht den veränderten Index in die Items schreibt - denn das wäre nötig, damit die ListView beim Auslesen der Daten auf den richtigen Index in der externen Datenstruktur zugreift. Somit behalten beide Items dieselbe Position, und die Reihenfolge ändert sich nicht. Aber so weit kommt es schon gar nicht, denn die interne Items-Liste ist im virtuellen Modus ja leer, d.h. die Sort-Methode wird gar nicht ausgeführt.

Die Lösung ist aber ganz einfach: Du musst im virtuellen Modus die externe Datenstruktur selbst sortieren und dann die ListView neu anzeigen.

Weiterhin meine ich, dass deine OnData-Routine nicht in Ordnung ist: Bei jedem Zugriff erhöhst du einen internen Zähler, cnt. Das ist deine "externe Datenstruktur", die ich oben erwähnte. Das bedeutet, dass jeder ListItem bei jedem Zugriff einen anderen Wert hat. Kann mir nicht vorstellen, dass das so beabsichtigt ist.

In der Anlage findest du ein funktionierendes Beispiel für das Sortieren einer virtuellen ListView. Die "externe Datenquelle" ist hier zur Vereinfachung eine StringList, mit fiktiven Dokumenten, bei denen das Datum dummerweise hinten angehängt ist (statt vorne), und die nach Datum sortiert werden sollen. Wenn du das Programm startest, ist die Listview zum Vergleich im normalen, nicht-virtuellen Modus, und das Sortieren (getriggert mit den "Sort ListView"-Button) funktioniert einwandfrei. Wenn aber die "virtual mode" Checkbox selektiert ist, ist der Button wirkungslos. Sortiert man aber mit dem Button "Sort Data" die externe StringList, ist alles in Ordnung.
Ich habe da eine neue Frage. Ich habe ein neuer Test gestartet, dieses Mal ohne Virtual Listview, also OwnerData := False. Die Items habe ich in Design hinzugefügt und kein OnCompare-Ereignis eingebaut. Das Sortieren funktioniert hier ohne OnCompare-Ereignis. Wozu ist dann das OnCompare-Ereignis da, wenn in virtual Mode gar nicht funktioniert und in Non-Virtual Mode das OnCompare-Ereignis gar nicht gebraucht wird.

wp_xyz
Beiträge: 4781
Registriert: Fr 8. Apr 2011, 09:01

Re: Virtual Listview Sortieren

Beitrag von wp_xyz »

Ohne OnCompare wird die Standard-Sortierung verwendet: alphabetisch bei SortType=stText, numerisch bei SortType=stData (wobei die ListItem.Data als Integer interpretiert werden). Wenn das aber nicht erwünscht ist, weil man, z.B. wie in meinem Beispiel, nur einen Teilstring der Item.Captions braucht und diesen auch noch auf eine besondere Art auswerten will (Datum), dann muss man einen Handler für das OnCompare-Event implementieren, um der ListView zu sagen, wie zwei Items zu vergleichen sind.

Antworten