[gelöst]Umsetzung QAbstractProxyModel

Alles rund um die Programmierung mit Qt
Antworten
ziegenbart
Beiträge: 4
Registriert: 28. September 2010 12:58
Wohnort: Bergisch Gladbach

[gelöst]Umsetzung QAbstractProxyModel

Beitrag von ziegenbart »

Hallo zusammen,
ich habe ein Baummodell (abgeleitet von QAbstractItemModel), das meine Daten kapselt. Jedes Objekt in meinem Baum ist eine Eigenschaft und hat einen Namen (Property) und einen Wert (Value). Die Anzahl der Objekte (Eigenschaften) pro Zeile variert, es sind aber immer mindestens 2 Objekte.
SOURCEMODEL:(QAbstractItemModel)
A1 A2 A3
+ B1 B2 B3 B4 B5 B6
+ C1 C2
+ D1 D2 D3 D4


Im ersten Schritt habe ich ein ProxyModel von QSortFilterProxyModel abgeleitet. Damit stelle ich meine Daten in einem TreeView als Übersicht dar, jedoch filter ich lediglich die ersten zwei Eigenschaften heraus.
ANSICHT ALS BAUM:(QSortFilterProxyModel)
A1 A2
+ B1 B2
+ C1 C2
+ D1 D2


Jetzt möchte ich einen zweiten View (Table) haben, der mir die Daten als Tabelle mit 2 Spalten aufbereitet darstellt, wobei aber immer nur eine Reihe der Quelle (SOURCEMODEL) betrachtet wird. Die Tabelle soll aber die gesamte Reihe abbilden.
ANSICHT ALS TABELLE:
A1->property, A1->value
A2->property, A2->value
A3->property, A3->value


Mein TreeView funktioniert prima, da ich von dem QSortFilterProxyModel lediglich columnCount() und data() implementieren musste. Die Umsetzung der ModelIndexe wird mir komplett abgenommen...

Nun zu meiner Frage bzw. zu meinem Verständnisproblem:
Ich habe nicht verstanden, wie ich mapToSource() bzw mapFromSource() zu implementieren habe. Ich weiss, das ich mir irgendwie eine Map oder ähnliches aufbauen muss um die unterschiedlichen ModelIndexe zu konvertieren, aber ich weiss nicht an welcher Stelle ich eine solche Map erstelle bzw. wann und wo sie aktualisiert wird.
Hat jemand vielleicht ein einfaches Beispiel oder kann es mir anders verständlich machen?

Vielen Dank in die Runde.
Zuletzt geändert von ziegenbart am 11. August 2011 11:05, insgesamt 1-mal geändert.
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Re: Umsetzung QAbstractProxyModel

Beitrag von RHBaum »

Ich weiss, das ich mir irgendwie eine Map oder ähnliches aufbauen muss um die unterschiedlichen ModelIndexe zu konvertieren
Wenn Du die ModelIndizies nicht einfach "umrechnen" kannst, sondern die Info aus dem Aufbau des anderen Models kommt, ja.
aber ich weiss nicht an welcher Stelle ich eine solche Map erstelle bzw. wann und wo sie aktualisiert wird.
Mehrere Wege führen nach ROM, und alle haben Vor und Nachteile ....

Frage:
Andert dein BasisModel öfters sein Layout ? oder ist es eher statisch ....


Weil davon wuerd ich es eher abhangig machen.

Ist dein BasisModel eher statisch, brauchst du die Map nur einmal aufbauen
Dazu brauchst du einen einsprungpunkt, der:
- wenns geht nur einmal ausgefuehrt wird
- wo dein Basis Model schon bekannt ist ...

Da springt einem SetSourceModel beinahe an ...
das ist virtual ... das heisst du kannst deine eigene Funktionalitaet da hinzufügen ....
(nicht vergessen das SetSourceModel auch an der Basis aufzurufen)
Vorraussetzung: das BasisModel muss schon befuellt sein, bevor du es dem Proxymodell dann zuweisst.

Ändert sich dein Model recht haeufig ... kommst nicht drumherum etwas komplexer zu programmieren.
DU musst also entweder:
- deine Map sobald den Index mapst, neu aufrufen ... (koennte sehr inperformant sein)
- oder auf einmal die Map aufbauen, vielleicht so wie oben, und dann auf alle aenderungen des Basismodels hoeren und deine Map dann jedesmal anpassen ...

Ciao ...
ziegenbart
Beiträge: 4
Registriert: 28. September 2010 12:58
Wohnort: Bergisch Gladbach

Re: Umsetzung QAbstractProxyModel

Beitrag von ziegenbart »

Zuerst einmal Danke an RHBaum.

Mein SourceModel soll sich immer wieder ändern, so das das Proxy auf alle Änderungen reagieren muss. Ich würde dafür im Proxy einen Slot erstellen, der mit jeder Änderung der Quelle aufgerufen wird.
Muss dieser Slot - jedesmal - das gesamte SourceModel durchlaufen um eine Umrechnung für jeden Index zu erstellen?
Das stelle ich mir in der Tat inperformant vor, schließlich soll das ProxyModel nur einen Zweig darstellen...
ziegenbart
Beiträge: 4
Registriert: 28. September 2010 12:58
Wohnort: Bergisch Gladbach

Re: Umsetzung QAbstractProxyModel

Beitrag von ziegenbart »

Jetzt verstehe ich bald gar nichts mehr...

Nachdem ich für die 2 Spaltige Tabelle (property, value) ein eigenes Model von QAbstractTableModel abgeleitet habe und diesem Model das jeweils ausgewählte Object aus dem Übersicht-Baum als RootItem untergeschoben habe, hatte ich das gewünschte Ergebnis. Jedoch musste ich mich dann selber um die Synchronisierung der beiden Modelle kümmern...

Dann habe ich mich nochmal daran gemacht ein ProxyModel abzuleiten um die Tabelle zu erstellen. Ich habe das Tablemodel als Grundlage verwendet, jedoch habe ich einen Slot [setRootFromIndex(QModelIndex srcIndex)] hinzugefügt, der mir das jeweilige RootItem für die Tabelle als QPersistentModelIndex speichert.
Ausgehend von diesem RootIndex kann mein ProxyModel die Tabelle darstellen, nur wird kein Editor zum bearbeiten sichtbar :(
Als Resultat kann ich den Text des Items blind ändern, was dann sogar ins Model übernommen wird!

Code: Alles auswählen

PropertyModel::PropertyModel(QObject *parent)
    : QAbstractProxyModel(parent)
{
}

PropertyModel::~PropertyModel()
{
}

int PropertyModel::rowCount(const QModelIndex&) const
{
    if(!rootIndex.isValid() || !sourceModel())
        return 0;
    return sourceModel()->columnCount(rootIndex);
}

int PropertyModel::columnCount(const QModelIndex &parent) const
{
    return 2;
}

QVariant PropertyModel::data(const QModelIndex &index, int role) const
{
    if(!rootIndex.isValid() ||
            (role != Qt::DisplayRole && role != Qt::DecorationRole))
        return QVariant();
    if(rowCount(rootIndex) <= index.row() || index.column() > 1)
        return QVariant();
    PropertyPair *prop = static_cast<PropertyPair*>(
                mapToSource(index).internalPointer()
                );
    if(!prop)
        return QVariant();
    if(index.column() == 0){
        return prop->name();
    } else if(index.column() == 1){
        return prop->value();
    }
    return QVariant();
}

QVariant PropertyModel::headerData( int section, Qt::Orientation orientation, int role) const
{
    if(orientation == Qt::Vertical || role != Qt::DisplayRole)
        return QVariant();
    if(section == 0)
        return tr("Property");
    if(section == 1)
        return tr("Value");
    return QVariant();

}

QModelIndex PropertyModel::index( int row, int column, const QModelIndex & parent) const
{
    return createIndex(row, column, mapToSource(parent.sibling(row,column)).internalPointer());
}

QModelIndex PropertyModel::parent ( const QModelIndex&) const
{
    return rootIndex;
}

QModelIndex PropertyModel::mapFromSource ( const QModelIndex & sourceIndex ) const
{
    return index(sourceIndex.row(), sourceIndex.column(), rootIndex.sibling(rootIndex.row(), sourceIndex.row()));
}

QModelIndex PropertyModel::mapToSource ( const QModelIndex & proxyIndex ) const
{
    return rootIndex.sibling(rootIndex.row(), proxyIndex.row());
}

bool PropertyModel::setData( const QModelIndex & index, const QVariant & value, int role)
{
    if(!rootIndex.isValid() || !sourceModel() || role != Qt::EditRole)
        return false;
    bool erg = sourceModel()->setData(mapToSource(index), value, role);
    emit dataChanged(index,index);
    return erg;
}

Qt::ItemFlags PropertyModel::flags ( const QModelIndex & index ) const
{
    Qt::ItemFlags flags = QAbstractItemModel::flags(index);
    if(index.column() == 1)
        flags |= Qt::ItemIsEditable;
    return flags;
}

void PropertyModel::setRootFromIndex(QModelIndex srcIndex)
{
    rootIndex = srcIndex.sibling(srcIndex.row(), 0);
    reset();
}
Wieso wird kein Editor erstellt?
ziegenbart
Beiträge: 4
Registriert: 28. September 2010 12:58
Wohnort: Bergisch Gladbach

Re: Umsetzung QAbstractProxyModel

Beitrag von ziegenbart »

OK, habe mein ProxyModel jetzt am laufen :D

Folgend möchte ich meine Schritte zum Ziel erläutern, vielleicht hilft es dem einen oder anderen und ich kann diesen Thread als gelöst schließen...

Zu meinem Ansatz dem ProxyModel lediglich einen RootIndex zuzuweisen: Dies ist grundlegend der falsche Weg!
Das Model muss immer den gesamten Datensatz beschreiben können, auch wenn es nur einen Ausschnitt davon zeigt. Als Beispiel ist hier zu nennen, das man ansonsten keine Undo/Redo Funktion implementieren kann. Ändert sich der RootIndex werden alle alten Daten ungültig und das ProxyModel kann nicht mehr darauf zugreifen (was zur Folge hat das auch keine Undo Funktion es kann).

Abgeschaut habe ich mir die Lösung hier http://developer.qt.nokia.com/faq/answe ... _just_sort

Das ProxyModel muss also das gesamte Model beschreiben:
Hierzu habe ich einen SLOT erstellt, der immer dann aufgerufen wird, wenn sich der currentIndex aus meiner Übersicht (Baumdiagramm) ändert. (Zur Erinnerung: ich möchte in meinem Proxy lediglich einen Zweig aus dem Übersichtsbaum darstellen)
In diesem SLOT->fixModel() durchlaufe ich mit der Methode addSourceIndexToMap(RootIndex) das gesamte SourceModel und bilde für alle Indexe einen neuen, der dann zum ProxyModel gehört. Sowohl den Source- als auch den ProxyIndex speicher ich in einer QMap. Zusätzlich speicher ich noch den passenden ParentIndex aus dem SourceModel.

Code: Alles auswählen

void PropertyModel::fixModel()
{
    if(!sourceModel())
        return;
    mapping.clear();
    proxySourceParent.clear();

    QModelIndex root = sourceModel()->index(0,0);
    addSourceIndexToMap(root);
    currentOriginIndex = root;
}

void PropertyModel::addSourceIndexToMap(QModelIndex index)
{
    if(!index.isValid())
        return;

    QModelIndex source;
    for(int r=0; r < sourceModel()->rowCount(index.parent()); r++)
    {
        int countMax = sourceModel()->columnCount(index.parent());
        for(int c=0; c < countMax; c++)
        {
            source = sourceModel()->index(r, c, index.parent());
            if(!source.isValid())
                break;
            QModelIndex proxy = createIndex(c, 1, source.internalPointer());
            mapping.insert(QPersistentModelIndex(source),proxy);

            QModelIndex sourceParent;
            if(sourceModel()->parent(source).isValid())
                sourceParent = sourceModel()->parent(source);
            proxySourceParent.insert(proxy, sourceParent);
        }
        if(sourceModel()->hasChildren(index.sibling(r,0)))
            addSourceIndexToMap(index.sibling(r,0).child(0,0));
    }
}
Nun hatte ich folgendes Problem bei meiner Darstellung: Ich benötigte für jeden SourceIndex zwei Indexe im ProxyModel. Der Index für die Spalte 0 soll mir immer den Namen meines Objektes zeigen und der Index für die Spalte 1 den eigentlichen Wert im Objekt. Ich habe dies gelöst, indem ich in der Map lediglich Indexe für die Spalte 1 abgebildet habe. Zu sehen in addSourceIndexToMap(RootIndex):
QModelIndex proxy = createIndex(c, 1, source.internalPointer());
Die Indexe für die Spalte 0 werden erst in der geerbten Methode index(QModelIndex &parent) erstellt.

Code: Alles auswählen

QModelIndex PropertyModel::index(int row, int column, const QModelIndex &parent) const
{
    if(!sourceModel())
        return QModelIndex();
    QModelIndex srcParent = mapToSource(parent);
    QModelIndex srcChild;
    srcChild = sourceModel()->index(currentOriginIndex.row(), row, srcParent);

    QModelIndex prxChild = mapping.value(srcChild);
    if(column == 1){
        return prxChild;
    }
    return createIndex(row, column, prxChild.internalPointer());
}
Hätte ich beide Indexe in der Map abgelegt hätten meine beiden Spalten immer den gleichen Inhalt gehabt, da QMap::key() und QMap::value() immer den ersten passenden Eintrag zurückgeben. Der zweite wird ignoriert und nie aufgerufen...

Zum Schluß zeige ich noch die geerbten Methoden mapToSource(QModelIndex) bzw. mapFromSource(QModelIndex). Diese konnte ich aus dem Beispiel des oben angegebenen Blogs entnehmen.

Code: Alles auswählen

QModelIndex PropertyModel::mapToSource(const QModelIndex &proxyIndex) const
{
    if(!sourceModel())
        return QModelIndex();
    return mapping.key(proxyIndex);
}

QModelIndex PropertyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
    if(!sourceModel())
        return QModelIndex();
    return mapping.value(sourceIndex);
}
Antworten