QT: beginInsertRows bei root?



  • Hi,

    mich stört gerade wieder das QAbstractItemModel. Die Dokumentation sagt, ich soll in insertRows vor dem Einfügen beginInsertRows aufrufen... das macht für mich auch Sinn, ist implementiert, klappt alles wunderbar.

    Aber was soll ich denn bitte machen, wenn ich beim Root was einfüge, also auf der ersten sichtbaren Ebene? Ich muss bei beginInsertRows einen Index übergeben, kann aber über index(...) keinen für den Root erzeugen. Der Versuch einfach QModelIndex() zu übergeben, scheitert natürlich (ist ja auch ein invalider Index). Wie löse ich das Problem? Zurzeit rufe ich beginInsertRows für den Fall einer Root-Einfügung eben nicht auf und muss dann resetten, aber das ist doch hässlich.

    Ideen?



  • Was für eine Struktur hat dein Model?



  • Wie, was für eine Struktur? Ich habe mich nach der Doku gerichtet und es gibt einen Root, der am Anfang erstellt wird, da werden alle Items eingefügt (aber das betrifft alles nur den selbstgemachten Tree, der hinter dem Model liegt). Und wenn man übers UI oder eben über eine Ladefunktion, wenn das Model schon erstellt ist, etwas einfügt, dann nutze ich eben insertRows.

    Ansonsten gibt es nur eine Column, dafür beliebig viele Unterebenen.



  • Eisflamme schrieb:

    Der Versuch einfach QModelIndex() zu übergeben, scheitert natürlich (ist ja auch ein invalider Index).

    Woran sieht du, dass es scheitert? Ist deine index Methode richtig implementiert?
    Wie bekommst du den Index für das fünfte Top Level Element? Indem du

    index(QModelIndex(), 5, 0)

    aufruft. Wenn du die Methode richtig implementiert hast, müsste es funktionieren.



  • Also deine Syntax fügt ja in Reihe 5 etwas ein... ich will aber unter dem RootNode etwas einfügen, sprich: Ich will neue TopRows einfügen.

    Das Scheitern sehe ich darin, dass er bei beginInsertingRows dumpt, wenn ich ihm den ParentIndex QModelIndex() übergebe.

    index habe ich nach dem Beispiel hier implementiert: http://doc.qt.nokia.com/4.7-snapshot/itemviews-simpletreemodel.html

    Konkret:

    QModelIndex SelectionTreeModel::index( int row, int column, const QModelIndex& parent) const
    {
    	if(!hasIndex(row, column, parent))
    		return QModelIndex();
    
    	Item* parentItem;
    
    	if(!parent.isValid())
    		parentItem = rootItem.get();
    	else
    		parentItem = static_cast<Item*>(parent.internalPointer());
    
    	Item* childItem = parentItem->GetChild(row);
    
    	if(!childItem)
    		return QModelIndex();
    	return createIndex(row, column, childItem);
    }
    


  • was ist bei dir rootItem?
    Wird dieses Item im view angezeigt und soll der Benutzer auf ebene des root-items neue elemente erzeugen können?



  • rootItem erstelle ich im constructor selbst, das ist ein normales Item. Es soll nicht angezeigt werden und der Benutzer soll auch keine neuen Unterknoten erstellen können. Ich aber schon 😞



  • Eisflamme schrieb:

    rootItem erstelle ich im constructor selbst, das ist ein normales Item. Es soll nicht angezeigt werden und der Benutzer soll auch keine neuen Unterknoten erstellen können. Ich aber schon 😞

    Ok dann ist der Index doch klar.

    beginInsertRows ( <QModelIndex des root-Items>,  int first, int last )
    

    und für das RootItem solltest du den index so bekommen:

    index(0,0, QModelIndex())
    


  • Also ich habe jetzt (Variableninhalte als Zahlen dargestellt):

    beginInsertRows(QModelIndex(0, 0, parent), 0, 0);
    
    parentItem->InsertChild(item, 0);
    //...
    

    und er dumpt bei beginInsertRows... (Edit: sorry, der blöde gelbe Pfeil ist immer eine Zeile zu spät)

    Der Callstack umfasst 8 Aufrufe oder so, aber so ziemlich am Ende ruft er parent auf:

    QModelIndex TreeModel::parent( const QModelIndex& index ) const
    {
    	if(!index.isValid())
    		return QModelIndex();
    
    	Item* childItem = static_cast<Item*>(index.internalPointer());
    	Item* parentItem = childItem->GetParent();
    
    	if(parentItem == rootItem.get())
    		return QModelIndex();
    
    	return createIndex(parentItem->GetRow(), 0, parentItem);
    }
    

    Er läuft bis zur letzten Zeile createIndex, aber parentItem ist null, sodass GetRow() dumpt. Interessant ist hier aber, dass childItem in diesem Fall das rootItem vom einzufügenden Baum ist. Klar, dass der kein parent hat und gleichzeitig aber eben auch nicht rootItem ist, hm...

    Edit:
    Also das Problem scheint zu sein, dass index(0, 0, QModelIndex()) folgendes macht:

    Wenn der angezeigte Baum leer ist (rootItem also vorhanden), liefert index einfach einen Index ohne internalPointer zurück, d.h. offensichtlich nicht gültig für rootItem. Gibt es einen Eintrag im Baum, so wird ein Index auf den zurückgegeben (z.B. ist bei einem sichtbaren Element eben index(0, 0, QModelIndex()) der Index von eben diesem ersten Eintrag im Baum). Anders gesagt: index(0, 0, QModelIndex()) == QModelIndex() für 0 angezeigte Element im Baum...

    daher glaube ich irgendwie auch, dass es keinen QModelIndex gibt, daher aber auch beginInsertRows/endInsertRows gar nicht erst aufgerufen werden muss?! Oder die haben den Fall nicht bedacht?



  • Eisflamme schrieb:

    Also ich habe jetzt (Variableninhalte als Zahlen dargestellt):

    beginInsertRows(QModelIndex(0, 0, parent), 0, 0);
    

    das ist doch falsch. Das das überhaupt kompiliert ist mir ein rätzel. Denn QModelIndex(0, 0, parent) ist kein gültiger Konstruktor Aufruf (Was ist überhaupt parent...). Denn QModelIndex hat so einen Konstruktor gar nicht....

    probiers mal mit

    beginInsertRows(index(0, 0, QModelIndex()), 0, 0);
    


  • Eisflamme schrieb:

    QModelIndex TreeModel::parent( const QModelIndex& index ) const
    {
    	if(!index.isValid())
    		return QModelIndex();
    
    	Item* childItem = static_cast<Item*>(index.internalPointer());
    	Item* parentItem = childItem->GetParent();
    
    	if(parentItem == rootItem.get())
    		return QModelIndex();
    
    	return createIndex(parentItem->GetRow(), 0, parentItem);
    }
    

    Er läuft bis zur letzten Zeile createIndex, aber parentItem ist null, sodass GetRow() dumpt. Interessant ist hier aber, dass childItem in diesem Fall das rootItem vom einzufügenden Baum ist. Klar, dass der kein parent hat und gleichzeitig aber eben auch nicht rootItem ist, hm...

    Ah jetzt versteh ich, du hast ein Denkfehler. Wenn parent() für dein rootItem (bzw. dessen index)aufgerufen wird, dann musst du ein invaliden ModelIndex zurückliefern, da ja das rootItem selbst keine parent hat.

    Dir fehlt eine abfrage ob childItem == rootItem.get() ist.



  • Was anderes. WO crasht es, wenn du

    beginInsertRows(QModelIndex(), 0, 0);
    

    aufrufst?

    Bzw. wie sieht bei dir die komplette methode aus, in der du ein neues element ins model hinzufügst?

    Edit: eventuell hilft dir auch das Qt beispiel weiter: http://qt-project.org/doc/qt-4.8/itemviews-editabletreemodel.html
    Da wird das erzeugen von neuen zeilen durch den Benutzer gestartet aber das sollte ja nicht das Problem sein, dass auf deinen Anwendungsfall zu adaptieren.



  • tl;dr: siehe Edit 3

    Sorry, das war ein Tippfehler, natürlich nutze ich da index statt QModelIndex mit den drei Parametern...

    hm, okay, aber diese Abfrage ist in dem QT-Beispiel ja gar nicht drin?

    In jedem Fall liefert aber QModelIndex() immer dasselbe wie index(0, 0, QModelIndex()), nämlich einen invaliden QModelIndex(), dessen internalPointer nie auf rootItem zeigt.

    Aber er bricht jetzt trotz dieser Änderung zusammen, da er nämlich wiederum in beginInsertRows(parent, 0, 0) mit parent == QModelIndex() irgendwo...

    So und jetzt hab ich schon das nächste Problem... 🙄

    Bevor ich in das Model ganz viel neues Zeug reinschieße, möchte ich es gerne entleeren. Jetzt muss ich dazu sagen, dass beginRemoveRows(...) und endRemoveRows() neu sind... ja, die sollte man wohl beim Löschen immer angeben. Tu ich im Normalfall auch, nur bei diesem Reset tat ich es bislang nicht. Interessant ist jetzt aber, dass er bei endRemoveRows() meckert, weil er in qEmpty

    if(numberOfChildren != 0)
    	{
    		beginRemoveRows(index(0, 0, QModelIndex()), 0, numberOfChildren - 1);
    
    		for(unsigned int n = 0; n < numberOfChildren; ++n)
    			rootItem->RemoveChildAt(0);
    
    		endRemoveRows();
    	}
    

    So und hier dumpt er gearde, weil endRemoveRows() das Signal clicked() auslöst, das ich fange... und das erhält einen QModelIndex, der zwar gültig ist, dessen internalPointer aber nicht gültig ist... eine Abfrage darauf, ob das Model einfach leer ist, hat dieses Problem jedoch erledigt.

    Jetzt klappt also das remove, trotzdem läuft er über beginInsertRows(...) seltsamerweise in einen parent()-Aufruf rein, in dem childItem gleich dem Root des einzufügenden Nodes ist... ich versteh gar nichts mehr, wieso kennt er den überhaupt? Der sollte eigentlich als gelöscht gelten... ist er auch, zumindest auf Seite des eigentlichen Trees unter rootItem.

    Edit: Danke für den Link, ich schau mir das Mal im Detail an. 🙂

    Edit2: Hm, das Problem könnte auch sein, dass ich einen Unterbaum mit weiteren Unterbaumelementen einfüge. Ich füge halt einen gesamten Baum in meinen Baum ein. Für Drag&Drop klappt das jedoch problemlos...

    Edit 3: Jetzt geht's... das Problem war, dass ich erst das gesamte Modell gelöscht hab, beginRemoveRows und endRemoveRows aber nicht ausreichend waren, um ads interne Modell upzudaten... d.h. obwohl ich das gelöscht habe und das auch mit den zwei begin/end-Methoden kenntlich gemacht habe, hat er beim Einfügen gedacht, dass diese Elemente noch drin waren.

    Ein reset() nach dem Löschen hat das Problem behoben, persistentModel ist dann nämlich korrekterweise leer und das Einfügen funktioniert ebenfalls problemlos. 🙂

    Vielen Dank für die Hilfe, jetzt funktioniert es endlich problemlos (fürs Erste). Falls mir noch jemand erklären kann, wieso begin/endRemoveRows nicht ausreichen, um das interne Modell upzudaten, wäre ich sehr froh. 🙂



  • Eisflamme schrieb:

    Vielen Dank für die Hilfe, jetzt funktioniert es endlich problemlos (fürs Erste). Falls mir noch jemand erklären kann, wieso begin/endRemoveRows nicht ausreichen, um das interne Modell upzudaten, wäre ich sehr froh. 🙂

    Für deinen Fall (komplettes leeren des Models) ist beginResetModel()/endResetModel die bessere Wahl.

    die beginXX/endXX methoden dienen hauptsächlich dafür einem View(oder proxy), welche das Model verwenden, anstehende Änderungen im Model selbst mitzuteilen.

    Was meinst du mit "das interne Modell"? Das TreeModel, welches du implementiert hast, ist doch das "interne Modell".



  • Vll. ist es auch das angezeigte, aber ohne reset läuft er irgendwann bei beginInsertRows in einen Node, der eigentlich bereits gelöscht wurde. Bei dem guckt er auf den parent und der ist dann nullptr und da er von dessen parent wieder irgendwas möchte (die Änderung mit dem ChildItem habe ich in parent() bereits eingefügt), dumpt er dort.

    Keine Ahnung, was das für ein Model ist, intern bezeichnet der das als persistentModel (irgendwo in den Tiefen des Debuggers)...

    Edit: begin/endResetModel funktionieren übrigens prima, danke 🙂


Anmelden zum Antworten