Autor Beitrag
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8475
Erhaltene Danke: 447

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Do 16.04.20 18:04 
Ich bin wieder am basteln aka "Altlasten entfernen". Der Punkt stört mich schon länger, weil ich da die Anzeige von Daten und die eigentliche Verwaltung der Daten stark vermische, was immer wieder zu Problemen führt. Und der Code ist stellenweise echt grauselig und nicht wirklich wartbar.

Etwas abstrakt Formuliert habe ich eine Klasse, die eine Liste von Objekten verwaltet. Aktuell eine einfach TObjectlist, noch keine Generics, ist aber egal (könnte man mit etwas Aufwand sicher ändern). In dieser Liste ist die Reihenfolge der Objekte relevant.

Diese Liste wird in einem VirtualStringTree angezeigt. Noch mit der "alten Methode" mit Records, könnte man aber sicherlich auch ändern, ist aber eigentlich auch egal.

Mein Problem fängt vor allem bei Drag&Drop an.

Fall 1.
Der Anwender droppt "von außen" weitere Objekte (=Dateien vom Explorer) in den TreeView, die (entsprechend aufbereitet) in die Liste aufgenommen werden sollen.
Bisher erledige ich innerhalb der Klasse das Einfügen in die ObjectList, und auch das Einfügen in den VST. Dazu hat die Klasse immerhin ein Property "ViewVST", das beim Erzeugen passend gesetzt wird. Ich greife also nicht in der Klasse direkt auf die Form zu. ;-) - Allerdings durchaus auf eine visuelle Komponente. :?
So ganz sauber finde ich das nicht.

Wie geht das besser? Über den "DropNode" den Einfüge-Index bestimmen, und dann (Pseudocode)
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
// Form
MyClass.AddItems(aIndex, someItems);

// Klasse
procedure TMyClass.AddItems(aIndex, someItems);
begin
  for i := 0 to SomeItems.Count - 1 do
  begin
     newDataObject := Verarbeite(SomeItems[i])
     MyObjectList.Insert(aIndex, newDataObject)
  end;

  if assigned(OnListChange) then
    OnListChange(self)
end;

// Form
procedure TMyForm.OnListChange(Sender: TObject);
begin
  // VST komplett leeren und neu aufbauen?
  // Beim Drop-Event sind die "Daten" ja noch unbekannt ...
end;


Fall 2
Der Anwender löscht Knoten im Treeview. Bisher löse ich auch das innerhalb der Klasse. Dort werden die Datenobjekte gelöscht, und auch die Knoten aus dem "ViewVST". Die Knoten bzw. Objekte bekomme ich im Klassen-Code aus ViewVST.GetSortedSelection. Wichtig dabei ist, dass das Löschen der Objekte einen gewissen Rattenschwanz hinter sich her zieht, es ändert sich halt nicht nur "Count".

Wie geht das besser? Im Form-Code die Objekte (SelectedObjects) hinter den Nodes ermitteln, Nodes löschen, und dann MyClass.DeleteObjects(SelectedObjects)?

Fall 3
Elemente verschieben innerhalb des VST per Drag&Drop.
Das ist aktuell kompletter Murks. Besonders, weil das irgendwie mit Fall 1 auch durcheinanderkommt und alles andere als übersichtlich ist.
Wie macht man das "anständig"? Nach dem Drop eine sortierte Liste alle KnotenObjekte in der neuen Sortierung erstellen, die Referenzen in der bisherigen Daten-Objektliste löschen und durch die KnotenObjekte neu einfügen?

Oder sollte man doch sämtliche Operationen irgendwie parallel auf "Anzeige" und "Daten" ausführen? Meine Vorschläge hier basieren ja darauf, dass auf "einer Seite" zu machen, und dann gewissermaßen per "brute Force" die andere Seite damit zu synchronisieren.

Oder sind solche Operationen dann Fälle, wo man schonmal fünfe grade sein lässt, und auf die strenge Trennung verzichtet?

_________________
We are, we were and will not be.

Für diesen Beitrag haben gedankt: FinnO
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4279
Erhaltene Danke: 917

Win10
C#, C++ (VS 2015/17)
BeitragVerfasst: Do 16.04.20 20:30 
Warum benötigt die Listenklasse denn überhaupt Zugriff auf den VST (d.h. warum verwaltest du nicht alle Daten in dieser Klasse)?

Gerade ein "Virtual"-Control sollte doch keine eigenen Daten besitzen, sondern diese auf Anforderung aus einer darunterliegenden Datenstruktur holen (und nur evtl. für Caching temporär speichern).

Und bei einem Drag&Drop (so wie jede andere Anwenderaktion) sollte doch immer zuerst die Logik-Funktionalität aufgerufen werden und danach dann nur die Anzeige entsprechend aktualisiert werden (also für alle deine 3 Fälle).

Dein Code sieht m.E. so schon mal gut aus. Evtl. kannst du ja auch für jedes zu ändernde Objekt ein Ereignis aufrufen (mit dem geänderten Objekt als Parameter) - so kann die UI dann evtl. nur diese Daten aktualisieren.

Für diesen Beitrag haben gedankt: Gausi
Gausi Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8475
Erhaltene Danke: 447

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Do 16.04.20 21:49 
user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Warum benötigt die Listenklasse denn überhaupt Zugriff auf den VST (d.h. warum verwaltest du nicht alle Daten in dieser Klasse)?

Weil sich die Listenklasse (teilweise) auch um die Anzeige kümmert :?. Ich habe da also nicht den Delphi-Klassiker genommen und Datenverwaltung in die Anzeige gepackt, sondern gewissermaßen Anzeige in die Datenverwaltung. Manchmal auch wieder andersrum, wild gemsicht. Ist halt alter Code. :oops:

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Und bei einem Drag&Drop (so wie jede andere Anwenderaktion) sollte doch immer zuerst die Logik-Funktionalität aufgerufen werden und danach dann nur die Anzeige entsprechend aktualisiert werden (also für alle deine 3 Fälle).

Schon klar, aber grade bei Drag&Drop fängt bei mir das Verständnisproblem an. Wie bekomme ich halbwegs sauber aus den Drag&Drop-Events die dahinterliegenden Daten bzw. die Einfüge- und Verschiebe-Logik hin?

Als Beispiel: Ich markiere in der Anzeige Knoten 1,2,10,12, und möchte diese 4 Knoten verschieben, so dass sie an neuer Stelle nacheinander liegen, z.B. zwischen Objekt 4 und 5. In den Drag&Drop-Beispielen zum VST werden dann die Knoten per MoveTo umsortiert. Als Parameter bekommt diese MoveTo-Methode des VST zwei Knoten (zu verschiebender Knoten und Zielknoten) und einen Parameter davor/dahinter.
In der ObjectList arbeitet man bei Move mit Indizes, was hier extrem fehleranfällig ist, weil sich nach jedem Verschiebevorgang eines Items die Indizes der anderen (die auch noch verschoben werden sollen) ändern können, und auch der Zielindex ändert sich (oder auch nicht), wenn man nach oben oder unten verschiebt. (Aktuell umgehe ich das, weil ich schon im DragOver verschiebe, sobald ich eine Knotenhöhe weiter bin, d.h. ich muss immer nur benachbarte Knoten vertauschen. Das ist aber auch nicht wirklich schön. Nicht benachbarte Knoten bleiben dabei auch "nicht benachbart". Kann man als Feature ansehen, gewollt ist das aber in den meisten Fällen nicht.)
Da wäre es also sinnvoller/einfacher, das Verschieben in der Anzeige durchzuführen, und das Ergebnis in die Daten zu übertragen, d.h. meine Datenklasse müsste eine Methode bekommen "Übernehme Sortierung aus ..." Ob nun direkt aus den Knoten, oder ob ich in der Form eine Liste mit den Objektreferenzen erstelle, und diese der Klasse zum Übernehmen gebe, ändert dann ja nur wenig. Aber das erscheint mir auch irgendwie unelegant ... :?

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Dein Code sieht m.E. so schon mal gut aus. Evtl. kannst du ja auch für jedes zu ändernde Objekt ein Ereignis aufrufen (mit dem geänderten Objekt als Parameter) - so kann die UI dann evtl. nur diese Daten aktualisieren.
Wenn sich die Eigenschaften eines einzelnen Objekts in der Listenklasse ändern, geht das gewissermaßen automatisch, bzw. schon mit einem Repaint des VST. Der holt sich dann im OnGetText-Event die nötigen Daten zum Anzeigen aus dem Objekt, das beim Erstellen des Knotens als Referenz übergeben wurde.

_________________
We are, we were and will not be.
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4279
Erhaltene Danke: 917

Win10
C#, C++ (VS 2015/17)
BeitragVerfasst: Fr 17.04.20 09:20 
Versteh ich das richtig, daß du zwar ein hierarchisches UI-Element verwendest (eben VirtualStringTree), aber intern eine lineare Objektliste, wo die einzelnen Einträge dann per Index verknüpft (referenziert) werden?
Dann wirst du wohl eine beidseitige Synchronisierung durchführen müssen (zumindestens, wenn die Reihenfolge in beiden Containern gleich bleiben soll).

Wenn du, wie du beschreibst, erst per VST.MoveTo die Elemente verschiebst, dann wirst du m.E. nicht darum herumkommen, diese dann nachträglich mit der Objektliste zu synchronisieren.

PS: Um die Abhängigkeit der Objektliste zum VST zu lösen, könntest du doch ein Ereignis definieren (anstatt der VST-Eigenschaft), so daß dann die UI-Komponente das Einfügen (und andere Änderungen) durchführt.

Bei der Internet-Suche nach VirtualStringTree habe ich ein "Schmankerl" in VirtualStringTree as Grid - Delphi tutorial (Tip for 10 ) gefunden:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
for i := 0 to ColNumb-1 do begin

  if ( Column=i) then CellText:=MyRecord.ArrOfStr[i];

end;

LOL

Für diesen Beitrag haben gedankt: Gausi
Gausi Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8475
Erhaltene Danke: 447

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Fr 17.04.20 10:28 
Erstmal danke für die Diskussion. Das hilft ungemein, die Gedanken zu sortieren. :D

Der VST ist ja eine Quasi-Standard-Komponente. Und auch wenn Tree im Namen steckt, wird der sehr oft eingesetzt, um TListView, TListBox, StringGrid u.Ä. zu ersetzen.

Tatsächlich nutze ich auch eine zweite Hierarchie-Stufe in der Anzeige, denn einige der bisher beschriebenen Objekte haben Kind-Objekte, die ich in einer Property vom Typ TObjectList an den Objekten speichere. Ein Verschieben von Objekten von der einen in die andere Ebene ist dabei nicht sinnvoll - eine echte Baumstruktur in den Daten brauche ich also nicht. Die Sub-Liste ist auch komplett starr. Die ist entweder da an einem Objekt (und bleibt so) oder sie ist nicht da (und kommt auch nicht so ohne weiteres dazu). Eine lineare Liste (mit ggf. einer linearen Subliste) bildet das sehr gut ab.

Aber das Synchronhalten von Anzeige und Daten finde ich hier irgendwie knifflig.

Zum Schmankerl: Ja, kann man so machen. Stolperfalle war da möglicherweise der Typ TColumnIndex = type Integer;, den man natürlich nicht als Array-Index nehmen kann und deswegen die Schleife braucht. :lol:
(Keine Sorge, das mache ich bei mir schon besser - mit einer dicken Case Column of Anweisung :mrgreen:)

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
PS: Um die Abhängigkeit der Objektliste zum VST zu lösen, könntest du doch ein Ereignis definieren (anstatt der VST-Eigenschaft), so daß dann die UI-Komponente das Einfügen (und andere Änderungen) durchführt.

Ja, das wird der saubere Weg sein. Muss ich mich mal etwas hinsetzen und austüfteln, wie ich das am besten abbilde, und was ich im Detail für Parameter in diesen Events brauche. Ich muss glaube ich weg von der Idee "Nach dem Drop alles auf einmal machen", und stattdessen besser jede Verschiebung einzeln zu verarbeiten (auf den Daten, und dann per Event in der Anzeige).
Ich glaube, da reift auch schon eine Idee, die dann auch noch an anderer Stelle Code einspart ... :gruebel: :think:

_________________
We are, we were and will not be.
Nersgatt
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 1580
Erhaltene Danke: 278


Delphi 10 Seattle Prof.
BeitragVerfasst: Fr 17.04.20 11:45 
Ein Ereignis mache ich an solchen Stellen auch gern. Damit wird einfach mitgeteilt, dass sich was in der Liste geändert hat. Ob und wie dann darauf reagiert wird, ist nicht mehr Aufgabe der Liste selbst. Allerdings ist es damit auch egal, wer und warum etwas an der Liste geändert hat. Vielleicht gibt es ja mehrere Möglichkeiten, die Reihenfolge der Liste zu ändern. Dann ist es völlig egal, wie die Änderung ausgelöst wurde.

_________________
Gruß, Jens
Zuerst ignorieren sie dich, dann lachen sie über dich, dann bekämpfen sie dich und dann gewinnst du. (Mahatma Gandhi)

Für diesen Beitrag haben gedankt: Gausi
Gausi Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8475
Erhaltene Danke: 447

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: So 19.04.20 10:23 
Zwischenmeldung: Umbau geht gut voran. :D

Im ersten Schritt die TObjectList durch TMyClassList = class(TObjectList<TMyClass>); ersetzt, überall die Typecasts aus dem Code entfernt, die Syntax der Sortiermethoden angepasst, und auch das einfügen der Daten in den VST mit Generics gelöst (statt der überall zu lesenden Rekord-Variante). Das waren alleine mehrere Stunden.

Das Rausfriemeln des VST aus der Datenklasse fühlt sich an wie eine OP am offenen Herzen. Bisher funktioniert aber alles weiter, und der Code fühlt sich schon deutlich schöner an. Ein paar kleine Änderungen im Verhalten wird es am Ende geben, die aber insgesamt für mehr Konsistenz sorgen. Dieser "Frühjahrsputz" war glaube ich mal dringend nötig. :oops:

Edit: Wow. Fall 3 (Drag&Drop im Tree) hat jetzt weniger Code (und der ist auch klarer lesbar), mehr Features (man kann Einträge auch im Tree kopieren, und auch Daten nach draußen kopieren) und es fühlt sich bei der Benutzung besser an. So sollte das immer klappen. :dance2:

Umbau ist damit so ziemlich fertig. Jetzt kann ich endlich das Feature einbauen, das das Gedöns hier losgetreten hat. 8)

_________________
We are, we were and will not be.
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4279
Erhaltene Danke: 917

Win10
C#, C++ (VS 2015/17)
BeitragVerfasst: Di 21.04.20 15:51 
Freut mich für dich, daß du den Umbau erfolgreich durchziehen konntest!

Für diesen Beitrag haben gedankt: Gausi