QT: MoveAction bei QTreeView



  • Ja, also QDrag wird automatisch erstellt, das stimmt, ich muss dem nur die mimeData geben, die packt der automatisch da rein.

    Kann gut sein, dass der dann einfach selbst removeLine(s) aufruft und ich das somit noch überschreiben muss, denn das habe ich noch nicht getan! Klingt gut, werde ich probieren, danke 🙂



  • Kurze Rückmeldung für Googler: Mechanics hat völlig Recht. removeRows kann und sollte (für obigen Zweck) überschrieben werden. Das wird automatisch aufgerufen (wenn dropMimeData mit dropAction == Qt::MoveAction true zurückgibt).

    Ach und noch eine Frage: wenn ich insertRows mit setData kombinieren will, habe ich ja das Problem, dass setData als zweiten Parameter nur QVariant unterstützt. Ich habe aber meine eigene Itemklasse, die kriege ich in QVariant ja nicht rein. Soll ich jetzt auf setData verzichten oder irgendwas anderes machen?

    Edit: Ich glaube, ich sollte wohl einfach nicht setData in dropMimeData verwenden... aber fürs Editieren scheint setData notwendigerweise überschrieben werden zu müssen, also mach ich das Mal brav.

    Edit: Wenn ich jetzt einen ganzen Unterbaum mit Unterelementen verschiebe, ist es ja extrem heftig, wenn ich alle Elemente des Unterbaums über den Stream in dies ByteArray schieben muss?! Kann ich nicht irgendwie den Ausgangszeiger übermitteln und darüber kopieren, das wäre deutlich einfacher. 😮



  • Eisflamme schrieb:

    Edit: Wenn ich jetzt einen ganzen Unterbaum mit Unterelementen verschiebe, ist es ja extrem heftig, wenn ich alle Elemente des Unterbaums über den Stream in dies ByteArray schieben muss?! Kann ich nicht irgendwie den Ausgangszeiger übermitteln und darüber kopieren, das wäre deutlich einfacher. 😮

    Wenn diese drag'n'drop aktion nur innerhalb deines Programms (und auch nur innerhalb des selben prozesses) läuft kannst du auch mit "Zeigern" oder QModelIndexen auf Model Elemente arbeiten.

    Du erstellst dir einfach eine eigene Klasse, welche von QMimeData abgeleitet ist.
    In diese Klasse packst du dann z.b. den Modelindex des root-elements des Unterbaums rein. Denn zum verschieben eines unterbaums reicht es ja wenn nur das root-element an die neue stelle gepackt wird.



  • Oh, stimmt! Sehr gut, dann weiß ich Bescheid!

    Jetzt habe ich halt über nen (De)kodier-Algo tatsächlich alles über nen Stream in die Mimedata geschoben... na ja, klappt auch, so ähnlich hätte ich es auch mit Zeiger machen müssen. 🙂



  • Okay, nächstes Problem...

    jetzt möchte ich gerne über einen QPushButton im Tree ein Element anlegen. Ich erhalte jedoch permanent Access Violations und ich verstehe überhaupt nicht, wieso.

    Also was ich im QPushButton mache ist jetzt nur noch:

    QModelIndex idx = ui.tree->selectionModel()->currentIndex();
    // idx dürfte i.O. sein; IsValid() ist true, internalPointer zeigt nach Casten genau das an, was zum markierten Element gehört
    ui.tree->insertRow(0, idx);
    

    insertRow sagt dann "access violation" und ich kann auch nicht näher reindebuggen, weil ich dann im Assemblercode lande.

    Wenn ich im Model eine neue Methode bastle, die etwas in den Tree einfügen soll (indem sie einfach in den zu Grunde liegenden Container etwas einfügt), erhalte ich nach Aufruf dieser (über den Pushbutton wieder) bei "emit layoutAboutToBeChanged" bzw. "emit layoutChanged" eine Access Violation.

    Wenn ich die Zeilen auskommentiere, fügt er etwas ein, aktualisiert zwar nicht, aber durch collapsen/expanden des Trees sehe ich es dann. Doch wenn ich den Dialog (in dem sich der Tree befindet) schließe, dann erhalte ich eine Nullpointer-Exception in den Tiefen von QT.

    Ich habe mir auch Mal das editableTreeModel-Beispiel angeschaut, aber da machen die das eben auch so:

    void MainWindow::insertRow() // das wird durch einen Menübutton getriggert
     {
         QModelIndex index = view->selectionModel()->currentIndex();
         QAbstractItemModel *model = view->model();
    
         if (!model->insertRow(index.row()+1, index.parent()))
             return;
    

    index.parent() würde in meinem Fall jetzt keinen Sinn machen, weil das ausgewählte Element bereits der parent() ist. Habe es jedoch auch so probiert, mit gleicher Access Violation.

    Ja, ich weiß nicht mehr weiter. Die Access Violation macht für mich überhaupt keinen Sinn und ich kann von außerhalb des Modells quasi überhaupt nichts vom Modell anfassen, ohne dass ich wieder eine Access Violation kriege. Daher kann ich auch nicht weiter den Fehler eingrenzen. Jemand eine Idee?



  • Also, zum einen kannst du in ein QVariant alles reinstecken, was du willst. Du musst lediglich irgendwo (am besten im Header nach der Klassendeklaration) ein

    Q_DECLARE_METATYPE(MyClass);
    

    haben.

    Wegen den access violations... Dass du im Assemblercode landest ist kein Problem, du kannst ja im Callstack einbisschen hochgehen und schauen, wo du noch Infos bekommst, dann kannst du sicher die Fehlerursache eingrenzen.
    Rufst du resetModel auf, nachdem du was eingefügt/gelöscht hast?



  • Erstmal vielen Dank, auch für den Hinweis für QVariant 🙂

    Wenn ich die Aufrufkette hochwandere, lande ich leider trotzdem nur an Stellen, die ich nicht einschätzen kann. Jedoch habe ich herausgefunden, dass man QModelIndex nicht einfach als Klassenattribut halten darf. Das hatte ich temporär nämlich zum Speichern der aktuellen Position genutzt. Aus irgendeinem Grund verursacht das nach Destrukturaufruf einen Dump... habe nicht wenig auf QT geflucht, weil ich das nicht verstehe.

    resetModel rufe ich nicht auf, jedoch ist ja selbst die Einfügeoperation selbst schon fehlgeschlagen und dumpt wegen Access Violation, d.h. er käme ohnehin nicht in die Zeile danach. Es ist halt so seltsam, weil ich z.B. ohne Probleme mit derselben Methode etwas einfügen kann, wenn ich es in dropMimeData schiebe. Derselbe Code dumpt jedoch, wenn ich aus einem Pushbutton heraus den Aufruf mache.

    Edit: Wenn ich jetzt über ein Signal etwas hinzufüge, also beim Klick auf Button ein Signal losschieße, dass im Model einen Slot hat, der einfügt, was ich will, dann dumpt er nicht?! Da war meine Intuition richtig, aber wieso ist so was so?

    Edit2: Okay, also wenn ich einfach etwas ins Modell einfüge und eben keine Signale emittiere, dann dumpt er auch nicht. Und auch im Beispiel von QT werden ja keine Signale gesendet. Doch wie erreiche ich den Refresh dann? Mache ich das über resetModel? (bzw. habe nur reset gefunden) Aber dann klappt er es ja auch wieder zu, ich hätte es schon gerne so, dass er alles so lässt, wie es ist.



  • Wenn es crasht, dann machst du sicher was falsch. Das muss dir klar sein und den Fehler solltest du finden und nicht versuchen, ihn zu umgehen.
    Ein Model hat mehrere Signale, die dem View mitteilen, dass sich was geändert hat. Die kann man in der Ableitung nicht direkt auslösen, sondern muss protected Methoden aufrufen, sowas wie beginInsertRows, endInsertRows usw.



  • Wenn es crasht, dann machst du sicher was falsch. Das muss dir klar sein und den Fehler solltest du finden und nicht versuchen, ihn zu umgehen.

    Puh, du scheinst wirklich so überhaupt nichts von mir zu halten, wenn Du so was schreibst. Ich muss hier einen extrem schlechten Eindruck hinterlassen. 🙂

    Also der Dump lag daran, dass ich QModelIndex in einem Dialog als Attribut hatte. Nach dem Löschen (ceteris paribus) dumpt er nicht mehr... außer, wenn ich eben Signale abfeuern möchte.

    Na ja, ich will eben dem Tree/Model sagen, dass sich am Modell etwas geändert hat. Es wäre in meinen Augen nur sinnvoll das über ein Signal zu tun. Eigentlich dachte ich, der Vorteil am Model ist, dass man etwas an den Daten ändern kann und damit automatisch auch der View aktualisiert wird und ich eben nicht ständig darauf achten muss, dass ich an jeder Stelle zig Methoden aufrufe... aber okay.

    begin/endInsertRows führen ebenfalls zu Access Violation, was auch klar ist, da die ja ihrerseits Signale aussenden. Ich neige so langsam dazu einfach alles in Slots des Modells auszulagern und über Signal/Slot-Mechanismus die Aufrufe zu tätigen. Wenn ich etwas einfüge und kein Signal sende, dumpt er ja auch nicht mehr und alles klappt, bis auf das Aktualisieren. Reset klappt, aber dadurch wird eben der Baum wieder collapsed, was je nach Tiefe für den Benutzer sehr ärgerlich ist.



  • Eisflamme schrieb:

    Puh, du scheinst wirklich so überhaupt nichts von mir zu halten, wenn Du so was schreibst. Ich muss hier einen extrem schlechten Eindruck hinterlassen. 🙂

    Durchaus nicht. Aber was ich geschrieben habe, mein ich auch so und deine letzte Antwort bestätigt den Eindruck auch 😉 Also, nicht den "schlechten Eindruck" von dir, sondern den Eindruck, dass du irgendwo einen Fehler hast.
    Das ist genauso gedacht. Wenn du Zeilen in ein Model einfügst, dann rufst du in der Ableitung beginInsertRows und endInsertRows auf. Das löst entsprechende Signale aus. Das View connected Slots auf diese Signale beim Setzen des Models und aktualisiert die Anzeige, wenn das nötig ist. Wenn das nicht funktioniert, ist definitiv etwas falsch. Das muss so funktionieren.



  • Okay, dann bin ich ja beruhigt... jedoch...

    Das Problem war, dass ich im QDialog das TreeModel mit new erstellt hab. Dann habe ich tree->setModel(model); aufgerufen und danach ist model genullt... d.h. ich habe über einen nullptr die Methode aufgerufen. :headbang: Wieso das Element nach Aktualisieren dann trotzdem eingefügt war, weiß niemand. Na ja, undefiniertes Verhalten halt...

    Manchmal ist QT so unberechenbar, wieso invalidiert der denn nach setModel einfach meinen Zeiger. 😞 (Edit: moment Mal, das ergibt so keinen Sinn)

    Vielen Dank nochmal!!

    Mit static_cast<...*>(tree->model())->InsertTest(idx); funktioniert alles. Und dafür saß ich jetzt Stunden dran. Ich lass mich jetzt mit ner Schrottflinte vom Balkon runter an einen Galgen schießen, in dem ich dann einen Schierlingsbecher trinke, während die Dynamitladung zündet. So ein Scheißfehler...

    Edit:
    Ich verstehe das immer noch nicht. Ich habe mein model jetzt Mal als konstanten Zeiger angelegt, sodass den niemand mehr verändern kann. setModel ändert den Zeiger auch nicht, aber wenn ich in die Slot-Methode komme, die vom Pushbutton-Signal aufgerufen wird, dann zeigt mir 1. der Debugger nicht mehr den Wert des Model-Zeigers an (muss ich also in einen anderen Pointer schieben, um zu debuggen) und dann ist dieser andere Zeiger eben nullptr?! Und das obwohl der this-Zeiger die gleiche Adresse hat und der Zeiger konstant ist, also nicht geändert werden kann. Was geschieht hier??

    Edit X:
    Übrigens klappen layoutAboutToBeChanged() und layoutChanged() jetzt auch. Ich denke jedoch, das ist auch in Ordnung, da ich schon einige Beispiele sah, die nicht über beginInsertRows() solche Einfügungen gemacht haben. Was wäre überhaupt der Vorteil davon?

    Edit Y:
    Okay, ich habe bereits einen Bug gefunden, weil ich es nicht brav mit beginInsertRows gemacht habe, dann mache ich das jetzt Mal lieber damit *duck und wegrenn*



  • Wie ist jetzt der Status nach den vielen Edits?

    setModel kann deinen Zeiger nicht ändern. Er kann aber das Objekt löschen (macht er aber sicher nicht). Du könntest einen Breakpoint in den Destruktor von deinem Model setzen und schauen, ob das gelöscht wird. Aber ich schätz mal, du hast einfach irgendwo eine Kleinigkeit übersehen.



  • Also es funktioniert jetzt alles, weil ich nicht über diesen Zeiger zugreife, sondern mir einfach vom Tree das Model über ->model() hole, caste und dann damit arbeite. Diverse Bugs konnte ich dann beseitigen, indem ich jetzt nirgendwo mehr selbst layoutChanged() emitte, sondern das über beginInsertRows() und Konsorten nutze. Die Bugs sind damit weg. Verwirrt hatte mich halt, dass die Doku sagt

    If the structure of the underlying data changes, the model can emit layoutChanged() to indicate to any attached views that they should redisplay any items shown, taking the new structure into account.

    Aber gut, jetzt läuft es.

    Gelöscht wird mein Model wohl nicht, aber trotzdem hat das Zeigerattribut nachher keinen durch Debugger identifizierbaren Wert mehr, obwohl this sich nicht ändert und der Zeiger eben konstant ist. Drum versteh ich absolut nicht, wie das sein kann... ist jetzt halt auch egal, da es wie gesagt, funktioniert, seltsam ist es dennoch...



  • Wird "this" vielleicht gelöscht? Sich von der View das Model zu holen ist an sich auch nicht verkehrt, nur das casten nicht so schön. Musst du das überhaupt machen? Wenn du aus dem Model immer noch keine Signale auslösen kannst, würde es immer noch auf einen Fehler hindeuten.



  • Signale kann ich aussenden, aber ich habe jetzt halt eigene Methoden im Model geschrieben, bei denen man nur Index und neues Objekt überhaupt und darüber automatisch einfügen kann. Das Interface vom Model hat ja nicht wirklich eine Methode, die angenehm fürs Einfügen ist. (insertRow wäre okay, wenn ich inzwischen den Q-Befehl gemacht hätte, womit setData auch eine passende QVariant erhalten könnte, aber das habe ich noch nicht gemacht; trotzdem ist das alles länger als mein Einzeiler, den ich dank meiner Methoden machen kann)


Anmelden zum Antworten