Verständnisfrage zu "beginRemoveRows"/"endRem

Alles rund um die Programmierung mit Qt
Antworten
Chris81T
Beiträge: 82
Registriert: 4. Mai 2008 00:06
Wohnort: Urbar

Verständnisfrage zu "beginRemoveRows"/"endRem

Beitrag von Chris81T »

Hallo,

Ich habe mir mein eigenes Model geschrieben (abgeleitet von QAbstractItemModel), welches mit einem TreeView verwendet wird.

Die Datenhaltung erreiche ich durch eine eigene Item-Klasse ( wie es das "Simple DOM Model" Qt Example auch macht ). Also bei einem "createIndex" wird als void* ein Item übergeben, sowie bspw. beim data() / setData() Aufruf der internalPointer des QModelIndex abgerufen und über das zugehörige Item Daten abgefragt / editiert.

Das Anzeigen der "Item"-Daten im View, sowie das Editieren von Daten funktioniert soweit problemlos. Auch das Einfügen von weiteren Zeilen klappt wunderbar, indem bei meiner implementierten insertRows Funktion:
1. beginInsertRows mit Angabe des Parents und der Angabe, an welcher Position wie viele Zeilen hinzugefügt werden sollen.
2. Die neuen Item Objekte passend der Vater/Kind Beziehung erzeugt und eingefügt werden.
3. mit endInsertRows die Behandlung abgeschlossen wird.

>> Ich hab das dann so verstanden, dass durch die Funktionen begin/endInsertRows dem Model bzw. dem View mitgeteilt wird, dass es dort neue Zeilen gibt und dann der View das Model anhand der vorhandenen Funktionen wie index() sich das zugehörige QModelIndex zu dem neuen Item verschafft und mittels data() dann die Daten des Items abfragt.

Die Probleme entstehen nun beim Entfernen von Zeilen:

Also ich habe in meiner Model-Klasse die removeRows() implementiert. Auch hier wird erst ein beginRemoveRows(..) durchgeführt, dann die zugehörigen Items der betroffenen Zeilen gelöscht und abschließend ein endRemoveRows aufgerufen.

Ein Beispiel (im Anhang ein Screenshot):

Es gibt 3 Zeilen mit je 3 Spalten. Alle 3 QModelIndex einer Zeile zeigen mit ihrem internalPointer auf das gleiche Item. Also für jede Zeile existiert ein zugehöriges Item.
In diesem Bsp sind Zeile 2 und 3 "Kinder" von Zeile 1 (siehe Screenshot).

Ich möchte nun Zeile 3 löschen, so rufe ich meine FUnktion removeRows auf und übergebe den Parent-QModelIndex (erhalte ich durch den View => currentIndex() (Zeile 3) Aufruf und davon frage ich das parent() an) und übergebe "1" für param "row" (Zeile 2 ist "0" und Zeile 3 ist "1" vom Parent aus gezählt) und "1" für param "count". Innerhalb wird dann die beginRemoveRows aufgerufen und Folgendes übergeben: der Parent-QModelIndex , der param "row" für "first" und "row+count-1" für "last".
Anschließend wird das zugehörige Item gelöscht und endRemoveRows aufgerufen.

Damit ist die Behandlung abgeschlossen, aber sporadisch kommt es zur Access-Violation, weil beispielsweise vom Model die Funktion parent aufgerufen wird und ein QModelIndex übergeben wird, der auf das gelöschte Item zeigt???!! In den wenigsten Fällen funktioniert es und die Zeile ist weg.

Ich hätte damit gerechnet, so wie bei BeginInsertRows(), dass mit BeginRemoveRows dem Model / View mitgeteilt wird, dass die Zeile 3 nicht mehr existiert und darauf keine Abfragen mehr gemacht werden (also das ein QModelIndex auf die Zeile3 mit Spaltenangabe verweist).

Leider kann ich meinen Code gerad nicht hier anzeigen, da ich momentan nicht an dem Rechner sitze, wo ich entwickle (könnt ich noch nachholen, falls erwünscht).

Kann nur nochmal sagen, dass mein Model/Item dem Qt-Example sehr nahe kommt.

Von daher gibt es bestimmt jemanden, der dies auch gemacht hat und eventuell auf ähnliche Probleme gestoßen ist und einen :idea: Tipp :idea: hat, was eventuell der Fehler ist.

Danke + gute Nacht

PS: Sorry für den etwas zu lang geratenen Text.
Dateianhänge
Beispiel
Beispiel
TreeExample.png (4.82 KiB) 3481 mal betrachtet
arj
Beiträge: 45
Registriert: 1. Juni 2009 13:51
Wohnort: Ihringen

Beitrag von arj »

Sehr interessantes Problem. Ich bin zwar aktuell nicht in der Thematik drin, würde es mir aber gerne mal anschauen. Kannst Du den Code mal anhängen?
Chris81T
Beiträge: 82
Registriert: 4. Mai 2008 00:06
Wohnort: Urbar

Beitrag von Chris81T »

Interessant ist es auf jeden Fall, aber nervt grad total ... :twisted:

Also noch vorweg:

In den Qt Labs hab ich den "ModelTester" gefunden. Den habe ich in mein Projekt eingefügt und mal geschaut, was passiert.

Nochmal zur Problemstellung, wann das genau passiert:
Ich füge in meine bestehende Struktur zwei neue Zeilen ein (Bsp. Wie Screenshot)
Dann lösche ich die letzte Zeile wieder und es kracht ( eventuell )

Das ModelTest hat mir schon mal en ASSERT geliefert, als ich die zwei Zeilen eingefügt habe (ha, ohne diesen Testor ist das so nicht aufgefallen, da im View die zwei Zeilen brav angezeigt wurden und ich auch darin editieren konnte, was zu keinem Fehler führte)
Dieses Problemchen hab ich erkannt und behoben, so dass auch der Testor nicht mehr meckert.

Trotzdem bleibt es bestehen, dass das Model über Eine der folgenden Funktionen eventuell abgefragt wird, wenn die Zeile entfernt wurde:
- rowCount(const QModelIndex & parent)
- parent(const QModelIndex & index)

STATISTIK:
- selten, dass es sauber weiterläuft und eine Zeile weniger angezeigt wird
- selten, dass die rowCount() aufgerufen wird
- vorrangig wird die parent() aufgerufen

Und dann der übergebene ModelIndex (ich denke mal vom View aus) einer ist, der auf das entfernte Item zeigt, so dass es natürlich zur AccesViolation kommt. :shock:

Hier nun der gewünschte Code:

Mit dieser Funktion erzeuge ich 2 neue Zeilen (sieht dann aus wie im Screenshot):

Code: Alles auswählen

bool PropTreeModel::insertRows(int row, int count, const QModelIndex &parent)
{
    // check, that count is bigger than zero
    if (count > 0)
    {
        beginInsertRows(parent, row, row+count-1);

        // the appropriate Item of model index
        CategoryItem *parentItem = static_cast<CategoryItem*>(parent.internalPointer());

        for (int i=0; i<count; i++)
        {
            // create new CategoryItem
            new CategoryItem(CategoryItem::PropertyChildItem,
                             parentItem->getDOMItemParent(),
                             parentItem);
        }

        endInsertRows();

        return true;
    }
    return false;
}
Mit dieser Funktion lösche ich die letzte Zeile (wie im ersten Post beschrieben)

Code: Alles auswählen

bool PropTreeModel::removeRows(int row, int count, const QModelIndex &parent)
{
    // check, that count is bigger than zero
    if (count > 0)
    {
        beginRemoveRows(parent, row, row+count-1);

        CategoryItem *parentItem = static_cast<CategoryItem*>(parent.internalPointer());

        // remove all child item objects
        for (int i=0; i<count; i++)
        {
            CategoryItem *childItem = static_cast<CategoryItem*>(parent.child(row+i, 1).internalPointer());
            parentItem->removeChild(childItem);
        }

        endRemoveRows();
        return true;
    }
    return false;
}
Hier die rowCount Funktion:

Code: Alles auswählen

int PropTreeModel::rowCount(const QModelIndex &parent) const
{
    // check, if the modelindex object refers to another column as column zero.
    if (parent.column() > 0)
    {
        /* this object doesn't know about the count of rows.
           Only item objects of col 0 does know it
         */
        return 0;
    }

    CategoryItem *parentItem;

    // check, if this modelindex object is the parent/root
    if(parent.isValid())
    {
        // it is a child
        parentItem = static_cast<CategoryItem*>(parent.internalPointer());
    }
    else
    {
        // it is the parent/root
        parentItem = mRootItem;
    }

    // now the count of rows can be determined
    return parentItem->countChildItems();
}
und die parent() Funktion des Models:

Code: Alles auswählen

QModelIndex PropTreeModel::parent(const QModelIndex &child) const
{
    // check, if this is a child or parent object or an invalid object
    if (!child.isValid())
    {
        return QModelIndex();
    }

    // get the child item
    CategoryItem *childItem = static_cast<CategoryItem*>(child.internalPointer());

    // check, that childItem is not null
    if (!childItem)
    {
        // item is invalid!
        return QModelIndex();
    }

    CategoryItem *parentItem = childItem->getParent();

    // check again, if this is maybe the parent/root object
    if (parentItem == 0 || mRootItem == parentItem)
    {
        // this item is the parent
        return QModelIndex();
    }

    QModelIndex newIndex = createIndex(parentItem->getAppropriateRow(), 0, parentItem);
    return newIndex;
}
24dan
Beiträge: 274
Registriert: 26. Juni 2006 14:15
Wohnort: Hamburg

Beitrag von 24dan »

Hi hattest Du das schon mal gesehen?

http://qt.nokia.com/doc/4.5/itemviews-e ... model.html
"Der erste Trunk aus dem Becher der Naturwissenschaften macht atheistisch, aber auf dem Grund des Bechers wartet Gott."
(W. Heisenberg)
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Hab haargenau die selben Probleme gehabt, als ich das mit dem Modelindex noch ned richtig raus hatte ...
Wichtig ist, das deine index, parent ... methoden usw korrekt funktionieren ... Ansonsten knallts da immer ^^
Nur wenn die richtig sind, klappt auch der rest.
siehe link von 24dan !

Aufn ersten blick seh ich aber ned wirklich was verkehrtes, ausser das einiges umstaendlich machst, und paar andere irrelevante kleinigkeiten .-)
Sicher das deine categoryItemstruktur richtig funktioniert ?
Und zeig mal deine index() methode ....

Ciao ...
Antworten