Autor |
Beitrag |
GuaAck
      
Beiträge: 378
Erhaltene Danke: 32
Windows 8.1
Delphi 10.4 Comm. Edition
|
Verfasst: Mi 26.02.14 23:49
Hallo,
TList.sort braucht ja eine Callback-Function, in der das Vergleichskriterium untergebracht wird, Aufruf also: TList.sort(@callback(...));
Die Callback-Function darf keine Methode einer Klasse sein, da gibt es die Compiler-Fehlermeldung, dass es eine einfache Funktion sein muss. (Logisch, sonst müsste ja auch der Kontext des jeweiligen Objekts an TList.Sort übergeben werden.)
Die Callback-Function als lokale Funktion in der Methode zu definieren, in der das TList.Sort aufgerufen wird, überlistet zwar den Compiler, aber der Zugriff in der Callback-Function auf Variablen, die in der Klasse definiert sind, führt zu einer Exception, auch logisch wie oben.
Einzige bisher gefundene (laufende) Lösung: Die Callback-Function außerhalb jeder Klasse in der Unit definieren und die erforderlichen Parameter auch als global in der Unit definieren (siehe unten). Das kann ja aber im Sinn des Klassen-Konzeptes nicht richtig sein. Hat da jemand eine korrekte Lösung?
Viele Grüße
GuaAck
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
| Unit Unit1; ...
TMyForm = class(TForm) ... MyList: TList; PROCEDURE sortiere_es; .... END;
Implementation
VAR sortierkriterium: integer;
FUNCTION callback(...):integer; BEGIN IF sortierkriterium= ... ... END;
PROCEDURE TMyForm.sortiere_es; BEGIN .... sortierkriterium:=...; MyList.sort(@callback); ... END; END; |
|
|
Tranx
      
Beiträge: 648
Erhaltene Danke: 85
WIN 2000, WIN XP
D5 Prof
|
Verfasst: Do 27.02.14 05:49
Eine Möglichkeit wäre, eine abgeleitete TList, z.B. TSortList zu definieren und dann das Quicksort selber mit all den Kriterien als Methode dieser Komponente zu implementieren. Der Vorteil wäre a) die Kapselung und b) die Möglichkeit, alternative Sortiermethoden, die vielleicht schneller sind, dort einzubinden. Nachteil ist natürlich, dass die Sortiererei selber verwaltet werden muss. Und mit Override kann man soweit ich weiß auch keine Prozedure mit einer komplett anderen Parameterstruktur, denn man benötigt ja dann die Vergleichsprozedur nicht mehr, nicht überschreiben. Außer man übernimmt die gleiche Prozedurstruktur der Sortfunktion von TList, übergibt aber NIL als Parameter, dann aber logischerweise nie ein Zugriff auf die überordnete Sortfunktion der Elternklasse (TList).
Was meinst Du zu diesem Vorschlag?
_________________ Toleranz ist eine Grundvoraussetzung für das Leben.
Zuletzt bearbeitet von Tranx am Do 27.02.14 17:52, insgesamt 1-mal bearbeitet
|
|
Nersgatt
      
Beiträge: 1581
Erhaltene Danke: 279
Delphi 10 Seattle Prof.
|
Verfasst: Do 27.02.14 08:31
Du könntest natürlich auch verschiedene Callbacks für die verschiedenen Kriterien anlegen. Wenn es nur um ein Sortierkriterium geht, wie in Deinem Beispiel, würde ich das für legitim halten:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
| PROCEDURE TMyForm.sortiere_es; BEGIN .... sortierkriterium:=...;
if (Sortierkriterium = skAufsteigend) then MyList.Sort(@callback_Aufsteigend) else MyList.sort(@callback_Absteigend); ... END; END; |
So in der Art.
Wenn Du aber noch mehr Sachen im Callback abfragen müsstest, dann würde ich auch TList ableiten, und .Sort mit etwas eigenem überschreiben. Wenn Du in die Quellen von TList schaust, wirst Du feststellen, dass da gar nicht so viel dahinter steckt. Das kannst Du leicht kopieren und für Dich anpassen.
_________________ Gruß, Jens
Zuerst ignorieren sie dich, dann lachen sie über dich, dann bekämpfen sie dich und dann gewinnst du. (Mahatma Gandhi)
|
|
jaenicke
      
Beiträge: 19312
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Do 27.02.14 11:16
GuaAck hat folgendes geschrieben : | Einzige bisher gefundene (laufende) Lösung: Die Callback-Function außerhalb jeder Klasse in der Unit definieren und die erforderlichen Parameter auch als global in der Unit definieren (siehe unten). Das kann ja aber im Sinn des Klassen-Konzeptes nicht richtig sein. |
Weshalb man das seit Delphi 2009 ja auch z.B. mit anonymen Methoden lösen kann. Aber das geht bei deiner Delphiversion ja noch nicht.
|
|
Blup
      
Beiträge: 174
Erhaltene Danke: 43
|
Verfasst: Do 27.02.14 18:47
Abhängig von dem was eigentlich erreicht werden soll, könnte ich mir diese Varianten vorstellen:
- Ableitung von TList
- Class-Helper
- eigenständige Sortierklasse der eine Liste übergeben wird
In jedem Fall muss zumindest Sort und Quicksort(oder vergleichbar) neu implementiert werden.
z.B.:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22:
| IComperator = interface(IInterface) function Compare(Item1, Item2: Pointer): Integer end;
TMyList = class(TList) private procedure QuickSort(L, R: Integer; const AComperator: IComperator); public procedure Sort(const AComperator: IComperator); reintroduce; end;
TMyComperator = class(TInterfacedObject, IComperator) constructor Create(AParam1: Boolean; AParam2: Integer); private FParam1: Boolean; FParam2: Integer; public function Compare(Item1, Item2: Pointer): Integer; end;
MyList.Sort(TMyComperator.Create(True, 5)); |
|
|
Tranx
      
Beiträge: 648
Erhaltene Danke: 85
WIN 2000, WIN XP
D5 Prof
|
Verfasst: Do 27.02.14 19:17
Wie man (bei meinem Delphi 5.0) in der unit Classes sehen kann, ist die grundlegende Sortierungsprozedur eh außerhalb der Klasse TList:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35:
| implementation
: :
procedure QuickSort(SortList: PPointerList; L, R: Integer; SCompare: TListSortCompare);
var I, J: Integer; P, T: Pointer; begin repeat I := L; J := R; P := SortList^[(L + R) shr 1]; repeat while SCompare(SortList^[I], P) < 0 do Inc(I); while SCompare(SortList^[J], P) > 0 do Dec(J); if I <= J then begin T := SortList^[I]; SortList^[I] := SortList^[J]; SortList^[J] := T; Inc(I); Dec(J); end; until I > J; if L < J then QuickSort(SortList, L, J, SCompare); L := I; until I >= R; end; |
Also ist es doch eigentlich egal, dass dann die Funktion Compare auch außerhalb der Klasse definiert ist, oder? Wenn man das alles in einer Klasse behalten will, ist doch eigentlich nur die Überladung mit reintroduce und die Einfügung der Methoden Quicksort und SCompare in die Klasse sinnvoll. Bei Quicksort bräuchte man dann ja den Parameter Sortlist nicht mehr, da man ja auf die Liste selber zugreift, also über die Property Items.
in etwa so:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58:
| implementation
TMyList = Class(TList) public function SCompare(Item1, Item2 : Pointer) : integer; procedure Quicksort(L, R : Integer); end;
implementation
procedure TMyList.QuickSort(L, R: Integer);
var I, J: Integer; P, T: Pointer; begin repeat I := L; J := R; P := Items[(L + R) shr 1]; repeat while SCompare(Items[I], P) < 0 do Inc(I); while SCompare(Items[J], P) > 0 do Dec(J); if I <= J then begin T := Items[I]; Items[I] := Items[J]; Items[J] := T; Inc(I); Dec(J); end; until I > J; if L < J then QuickSort(L, J); L := I; until I >= R; end;
function TMyList.SCompare(Item1, Item2 : Pointer) : integer; var s1, s2 : ^string; begin s1 := Item1; s2 := Item2; if (s1^>s2^) then Result := 1 else if (s1^<s2^) then Result := -1 else Result := 0; end; : : |
_________________ Toleranz ist eine Grundvoraussetzung für das Leben.
|
|
GuaAck 
      
Beiträge: 378
Erhaltene Danke: 32
Windows 8.1
Delphi 10.4 Comm. Edition
|
Verfasst: Fr 28.02.14 01:31
Danke an alle,
anscheinend ist es ja wirklich eine Unvollkommenheit in Delphi 7, die später mit den "anonymen..." behoben wurde.
Quicksort nutze ich als Procedure seit langer Zeit. Tlist ist sehr praktisch und ich komme ja klar. Nur von der Klassenidee konnte ich mir nicht vorstellen, dass es so richtig ist, wie ich es mache.
Beste Grüße
GuaAck
|
|
Tranx
      
Beiträge: 648
Erhaltene Danke: 85
WIN 2000, WIN XP
D5 Prof
|
Verfasst: Fr 28.02.14 05:57
Die Lösung, die Vergleichsprozedur auszulagern, ich habe mal drüber nachgedacht, ist wohl deswegen so passend, weil sie flexibel ist. Ich kann verschiedenste Datentypen vergleichen. Schließlich ist der Vergleich ja unterschiedlich, ob ich Zahlen, Datumsangaben, Strings ... vergleiche. Daher ist jede eingebettete Lösung auch etwas Einschränkender und damit nicht mehr so flexibel.
Als Lösung habe ich mir gedacht, man könnte ja in der neu erzeugten TMyList auch eine Integer-Eigenschaft Typus einbetten. Die könnte dann in der Vergleichsprozedur dafür gedacht sein, die richtigen Variablentypen zur Verzweigung zu verwenden z.B.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67:
| const isInteger = 1; isReal = 2; isString = 3; : isUnknown = 255;
type TMyList = class(TList); fTypus : integer;
function SCompare(p1, p2 : pointer) : integer;
: property Typus : integer Read GetTypus write SetTypus;
end;
function TMyList.SCompare(p1, p2 : pointer) : integer;
var s1, s2 : ^string; i1, i2 : ^longint; r1, r2 : ^extended;
begin if Typus = isInteger then begin s1 := p1; s2 := p2; if (s1^>s2^) then Result := 1 else if (s1^<s2^) then Result := -1 else Result := 0; end else if Typus = isReal then begin r1 := p1; r2 := p2; if (r1^>r2^) then Result := 1 else if (r1^<r2^) then Result := -1 else Result := 0; end else if Typus = isInteger then begin i1 := p1; i2 := p2; if (i1^>i2^) then Result := 1 else if (i1^<i2^) then Result := -1 else Result := 0; end : else Result := 0; end; |
Dann wäre eine gewisse Flexibilität gegeben. In der Createprozedur (Konstruktor) müsste dann der Typus mit isUknown vorbelegt werden, das ist dann alles. Und natürlich der Typus manuell gesetzt werden. Denn bei Erstellen der Liste weiß man ja, was dort gespeichert sein soll. Und Strings mit Zahlen zu vergleichen macht echt wenig Sinn, außer ich will die möglichen Zahlen als Zahl nehmen, und die strings als Strings. Doch wie soll das Ganze dann sortiert werden? Man kann m.E. nur gleiche Typen sinnvoll vergleichen.
Das als einen Ansatz.
_________________ Toleranz ist eine Grundvoraussetzung für das Leben.
|
|
WasWeißDennIch
      
Beiträge: 653
Erhaltene Danke: 160
|
Verfasst: Fr 28.02.14 11:03
Wäre da eine Property nicht flexibler? Also etwas in der Art
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| type TListCompareMethod = function(Item1, Item2: Pointer): integer of object;
TMyList = class(TList) private FCompareMethod: TListCompareMethod; ... public property OnCompare: TListCompareMethod read FCompareMethod write FCompareMethod; ... end; |
|
|
Blup
      
Beiträge: 174
Erhaltene Danke: 43
|
Verfasst: Fr 28.02.14 18:51
Die Vergleichsfunktion als Methode der Liste anzulegen, erscheint mir nicht sinnvoll.
Diese ist vom Anwendungsfall abhängig, ein neuer Anwendungsfall kann auch eine neue Sortierreihenfolge erfordern.
Da möchte ich nicht jedesmal die Datenklasse bzw. Liste erweitern.
Ob die Vergleichsfunktion als Property oder als Parameter übergeben wird, ist eher nebensächlich.
Ich würde den Vergleich (wie in meinem Beispiel) sogar in eine Klasse auslagern.
Damit wird optimale Flexibilität(z.B. beliebige Parameter) mit Wiederverwendbarkeit kombiniert.
Für diesen Beitrag haben gedankt: Tranx
|
|
Tranx
      
Beiträge: 648
Erhaltene Danke: 85
WIN 2000, WIN XP
D5 Prof
|
Verfasst: Fr 28.02.14 19:24
Blup hat folgendes geschrieben : | Die Vergleichsfunktion als Methode der Liste anzulegen, erscheint mir nicht sinnvoll.
Diese ist vom Anwendungsfall abhängig, ein neuer Anwendungsfall kann auch eine neue Sortierreihenfolge erfordern.
Da möchte ich nicht jedesmal die Datenklasse bzw. Liste erweitern.
Ob die Vergleichsfunktion als Property oder als Parameter übergeben wird, ist eher nebensächlich.
Ich würde den Vergleich (wie in meinem Beispiel) sogar in eine Klasse auslagern.
Damit wird optimale Flexibilität(z.B. beliebige Parameter) mit Wiederverwendbarkeit kombiniert. |
Dem kann ich eigentlich nur zustimmen. Denn alle Fälle wird man nie abdecken, weil in TList ja alles gespeichert werden kann, was man sich vorstellen kann, oder auch nicht vorstellen kann. Ich bin mir auch 100%ig sicher, das genau aus diesem Grund auch beides: sowohl die Sortierprozedur als auch Vergleichsfunbktion, ausgelagert wurde. Den auch die Sortierprozedur selber kann sich ja auch ändern.
Wenn man partout das in die Klasse integrieren will, dann muss man selber beides programmieren, darum kommt man nicht drumrum. Und um es dann flexibel zu gestalten fiel mir - jetzt spinne ich mal, denn ich weiß nicht recht, wei man es umsetzen könnte - eine Ereignisprozedur "OnSort" ein. Aber ganz erhlich, ich sehe selbst darin keinen Vorteil. Aber vielleicht hat ja der Threadstarter neben der prinzipiellen Frage einer Kapselung aller Prozeduren eine konkrete Anwendung im Kopf. Dann kann man darüber dan sich Gedanken machen. So, wie das jetzt implementiert ist, ist es wohl am flexibelsten.
_________________ Toleranz ist eine Grundvoraussetzung für das Leben.
|
|
GuaAck 
      
Beiträge: 378
Erhaltene Danke: 32
Windows 8.1
Delphi 10.4 Comm. Edition
|
Verfasst: Fr 28.02.14 21:07
Hallo Alle,
da scheine ich ja ein aufregendes Thema angeschnitten zu haben, vielen Dank für die vielen guten Ideen.
Der "Threadstarter" hat als konkretes Problem eine TStringlist, die er mit Click auf den jeweiligen Spaltenkopf nach der Spalte sortieren möchte.
Meine Lösung: Vor dem Sortieren trage ich in jeden Tlist-Eintrag in einem Feld das Sortierkriterium ein. Dann kann ich das Compare in einer lokalen Funktion in der
Methode aufrufen, die auch das TList.Sort aufruft. Somit ist alle in der Klasse gekapselt.
Gruß
GuaAck
|
|
Tranx
      
Beiträge: 648
Erhaltene Danke: 85
WIN 2000, WIN XP
D5 Prof
|
Verfasst: Sa 01.03.14 13:22
Frage, wenn Du eine Stringliste mit Spalten hast, warum nicht eine TStringGrid? Das ist schon in Spalten organisiert. Allerdings habe ich gesehen, dass die Sortierung dort nicht gerade einfach zu organisieren ist, da die Einträge in den Spalten und Zeilen als TStrings ansprechbar sind, und diese leider nicht sortiert werden können. Eine komplett andere Art wäre dann TDBGrid und die Daten in einer Datenbanktabelle. Dann kannst Du die Tabelle sortieren, was dann der entsprechende Datenbanktreiber für Dich übernimmt. Dann benötigst Du keine Sort-Methode, bloß ein Klick auf die Titelzeile und Du schreibst - bei einer TQuery als Datenbankkomponente - zum Beispiel:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7:
| procedure TForm1.dbgListeTitleClick(Column: TColumn);
begin Tabelle.Active := FALSE; Tabelle.SQL.Text := "Select * FROM '#10#13'TabellenDaten'#10#13'ORDER BY '+Column.FieldName; Tabelle.Active := TRUE; end; |
Dann benötigst Du die Tabelle "Tabellendaten" und die Komponente dgbListe.
Dann allerdings müsstest Du die Daten in die Tabelle eintragen. Ich weiß ja nicht, wie groß Deine Liste ist und ob die sich ändert.
_________________ Toleranz ist eine Grundvoraussetzung für das Leben.
|
|
jaenicke
      
Beiträge: 19312
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Sa 01.03.14 15:15
Auch wenn es langweilig wird (weil ich das oft schreibe  ), aber das ist ein typischer Fall für eine TVirtualStringTree. 
|
|
Tranx
      
Beiträge: 648
Erhaltene Danke: 85
WIN 2000, WIN XP
D5 Prof
|
Verfasst: Sa 01.03.14 18:27
_________________ Toleranz ist eine Grundvoraussetzung für das Leben.
|
|
GuaAck 
      
Beiträge: 378
Erhaltene Danke: 32
Windows 8.1
Delphi 10.4 Comm. Edition
|
Verfasst: Sa 01.03.14 23:53
Hallo,
auch wenn ich die Frage als erledigt markiert habe, läuft die Diskussion noch:
Zu TVirtualStringTree: Ist in meinem Delphi 7 Personal nicht enthalten. Mein Delphi 6 Professionel habe ich leider verkauft, der Personal-Umfang (und Lizenz!) reichte mir damals im Grunde. Außerdem: Es ist kein Tree.
TStringList, TStringGrid: Das verwechsele ich immer und habe mich in meinem Beitrag geirrt. Ich nutze TStringGrid, das aber eben kein sort bietet.
Hier noch einmal im Kürze meine Lösung, die gut ist und Potenzial für weitere Verfeinerungen hat:
a) Im TForm ist es ein TStringGrid.
b) Im Hintergrund gibt es eine TList mit den Zeilen, die im TStringrid dargestellt werden sollen. Nach einer Neusortierung wird TList nach TStringlist kopiert, wobei auch z. B. inttostr genutzt wird.
Es handelt sich um etwa 1000 Zeilen und 10 Spalten, ich habe kein Performance Problem (auch nicht auf meiner zu-Hause-Uralt-800MHz-CPU).
c) In TList habe ich eine Spalte zusätzlich definiert, die das Sortierkriterium angibt, das könnte aber auch ein Pointer auf eine andere Struktur sein. Jedenfalls hat man dann über den Zeilen-Pointer in der Compare-Routine Zugriff auf die Variablen in dieser Zusatzstruktur.
Geht bestens, ist übersichtlich, ist als Klasse gekapselt, also genau das was ich brauchte, als Resultat der Diskussion in dieme Forum!
Beste Grüße
GuaAck
|
|
jaenicke
      
Beiträge: 19312
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: So 02.03.14 00:02
GuaAck hat folgendes geschrieben : | Zu TVirtualStringTree: Ist in meinem Delphi 7 Personal nicht enthalten. Mein Delphi 6 Professionel habe ich leider verkauft, der Personal-Umfang (und Lizenz!) reichte mir damals im Grunde. Außerdem: Es ist kein Tree. |
Nein, es muss selbst installiert werden, das stimmt.
TVirtualStringTree heißt nur Tree, das ist genauso gut als Grid nutzbar oder sogar als Kombination von beidem.
Der Thread und das Bild sind uralt, aber auch da habe ich ein solches Grid benutzt, was du in dem Bild siehst ist eine TVirtualStringTree:
www.entwickler-ecke....ar+RC+1_64090,0.html
|
|
|