Model View Ansatz, nur wie?

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Hi Community,

ich arbeite gerade an einem kleinen Programm, bei dem man in einer Tabelle eine Zeile auswählen kann und man zu der ausgewählten Zeile in einem anderen Widget A mehr Informationen zu dieser einen Zeile bekommt, aber auch die Informationen, die in der Tabelle bereits vorhanden sind. (langeer Satz :P)

Das Problem bei dem Ganzen ist, dass die Tabelle editierbar ist. Sprich, wenn ich eine Checkbox anwähle dann soll sich das ganze auch in der andere View ändern. Auch umgekehrt soll es möglich: Wenn ich was an Widget A ändern, so soll es die Tabelle mit editiern.

Das ganze habe ich schon in einer Richtung geschafft: Tabelle -> Widget A, indem ich das dataChanged Signal abgreife und darauf hin reagiere. Die andere Richtung ist zwar auch möglich, aber mit sehr viel Aufwand verbunden.

Gibt es für solche geschichten kein Framework? :) Ja das gibt es, aber ich komm damit irgendwie nicht zurecht.

Eine QAbstractItemView ist ja nicht das was ich suche. Ich will ja nur ein Item anzeigen! Stellt das ein Problem da? Bei einer Implementierung von QAbstractItemView sieht mir die ganze Sache auch nach viel Arbeit aus, obwohl das ganze ja nicht kompliziert ist.


Kann mir jemand Tipps geben, wie ich das Problem lösen kann?

Grüße,
anogayales
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Schau mal, ob Dir ProxyModels weiterhelfen. Versteh leider nicht ganz Dein Problem...
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Hier ein Screenshot meines Programms:
Bild

In der Tabelle wählt man einen Eintrag aus und will dazu auf der rechten Seite mehr Informationen erhalten.
Jetzt kann man aber in jeweiliger Ansicht (Tabelle oder Widget Rechts) Daten verändern, die sich in der jeweiligen View updaten.

Wählt man die Checkbox "Seen it" aus, so soll diese auch in der Tabelle geändert werden.

Das ganze habe ich schon hinbekommen, benutzte aber nicht das Model/View Framework, da das definieren einer eigen AbstractItemView nicht das ist was ich brauch, da ich ja nicht viele Items anzeigen will, sondern immer nur ein Spezielles.

Grüße,
anogayales
ichisich
User
Beiträge: 134
Registriert: Freitag 1. Januar 2010, 11:52

Das Model View konzept hat aber nichts damit zu tun ob Du ein oder mehrere Items zeigen willst. Es geht nur darum EINE Datenquelle zu haben und MEHRERE Ansichten.
Jede Ansicht schreibt und liest die Daten aus dem, in das Model und alle Views werden darüber in Kenntnis gesetzt das sie was neues Anzeigen sollen wenn was geändert wurde.

In deinem Fall müssen Teile deiner Daten, bsp. die Checkboxen oder das Textfeld in zwei Views gezeigt werden und alles von einem Model verwaltet werden.

Wenn Du schon mal gemodelt und geviewt hast sollte das kein Problem sein. Geschenkt bekommt man nichts, also ein bisschen Aufwand ist sowas immer.
Beispiel gibts ja hier auch im Forum mein ich...

Gruß
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@anogayales:
Die Frage Model/View hat erstmal nichts mit der Menge der darzustellenden Items zu tun (wie ischisch schon geschrieben hat). So zeigt ein Model mit nur einem Eintrag im View eben nur einen Eintrag an ;)

Zu Deinem Problem - hier führen mehrere Wege nach Rom.

Der unter Geschichtspunkten der Model/View-Programmierung sauberste Weg ist, Dein TableView um einen SubView zu erweitern und die jeweils aktive Zeile der Tabelle zum Model des Editorviews (rechtes Widget) zu erklären. Alles weitere geht dann wie gehabt über Delegates und Änderungen sollten nach einem setModelData() zu den Views durchtröpfeln. Der Trick ist, den View nicht auf das ganze Model abbilden zu wollen, sondern nur mit einem Subset der Daten zu agieren (einer Zeile Deines TableViews). Im Prinzip ist Dein Editorwidget nichts anderes als eine Liste, deren Einträge sehr verschieden zu behandeln sind. Das spricht für die Erstellung eines eigenen Views (also von QAbstractItemView abgeleitet). Alternativ könnte man es auch als Mapping verstehen (Key-Value) und mit einem angepassten TableView arbeiten.

Was auch geht:
Die Tabelle nicht editierbar machen und die Delegates in das rechte Widget umbiegen. Hätte den Vorteil eines "zentralen" Editors während die Tabelle eben nur zum Einsehen der Daten ist (Ist eine Frage der Usability, bin mir nicht sicher, ob eine stark formatierte Tabelle mit vielen verschiedenen Typen vom Benutzer überhaupt als inline editierbar erkannt wird.)

Weitere Möglichkeit:
Die Widgets rechts ohne Model/View-Unterstützung befüllen. Die Model/View-Abstraktion bringt genügend Signale etc. mit, um dies on-the-fly zu bewerkstelligen. Ist wahrscheinlich der schnellste aber eben auch unsauberste Weg. Bei einem immer gleich gearteten Ein-Element-Zugriff muss man sich halt fragen, ob es den Model/View-Aufwand wert ist. Allerdings solltest Du die Daten aus dem Model holen und nicht direkt auf der Datenquelle arbeiten. So sehen auch die Views des Models die geänderten Daten.
Ich würde diesen Weg nur für einfache Problemstellungen empfehlen, da das sehr schnell unübersichtlich werden kann.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Vielen Dank für deinen Input!

Wie würdest du aber der subView sagen, welche Zeile er gerade aus dem Model anzeigen soll? Die Selektion wird ja über das Selectionmodel geregelt, müsste escdann ein Selectionmodel für 2 verschiedene Views geben?

Grüße,
anogayales
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Das SelektionModel sagt Dir, welche Items gerade selektiert sind. Daraus kannst Du das Model für den SubView ableiten.

Vereinfachtes Bsp. mit QSqlTableModel, QTableView und QListView:

Code: Alles auswählen

from PyQt4 import QtGui, QtCore, QtSql
from PyQt4.QtCore import Qt

class MyListModel(QtCore.QAbstractListModel):
    def __init__(self, masterModel, indexes, parent=None):
        QtCore.QAbstractListModel.__init__(self, parent)
        self._masterModel = masterModel
        self._masterModel.dataChanged.connect(self.triggerUpdate)
        self._model = indexes

    # update associated views when master model has new data
    def triggerUpdate(self, topLeft, bottomRight):
        self.dataChanged.emit(self.index(0, topLeft.column()),
                                self.index(0, bottomRight.column()))

    def rowCount(self, index):
        return len(self._model)

    # implement display and edit role for now
    def data(self, index, role):
        if role == Qt.DisplayRole or role == Qt.EditRole:
            return self._masterModel.data(self._model[index.row()])

    # set appropriate flags of the items
    def flags(self, index):
        return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled

    # set data of the master model directly
    def setData(self, index, value, role):
        return self._masterModel.setData(self._model[index.row()], value, role)

class MyWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.query = QtSql.QSqlQuery('select * from person')
        self.model = QtSql.QSqlTableModel()
        self.model.setQuery(self.query)
        self.table = QtGui.QTableView(self)
        self.table.setSelectionBehavior(1)
        self.table.setModel(self.model)
        self.selectionModel = QtGui.QItemSelectionModel(self.model)
        self.table.setSelectionModel(self.selectionModel)
        self.gridLayout = QtGui.QGridLayout(self)
        self.gridLayout.addWidget(self.table, 0,0)
        self.listView = QtGui.QListView(self)
        self.gridLayout.addWidget(self.listView, 0,1)

        # change listview model on selection change
        self.selectionModel.selectionChanged.connect(
            lambda index : self.listView.setModel(
                                MyListModel(self.model, index.indexes())))

def prepareDB():
    db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
    db.setDatabaseName(":memory:")
    db.open()
    query = QtSql.QSqlQuery('CREATE TABLE person (forename char(50),surname char(50))')
    query.prepare("INSERT INTO person (forename, surname) "
                  "VALUES (:forename, :surname)")
    query.bindValue(":forename", "Bart")
    query.bindValue(":surname", "Simpson")
    query.exec_()
    query.bindValue(":forename", "Hans")
    query.bindValue(":surname", "Mustermann")
    query.exec_()
    return db

if __name__ == '__main__':
    app= QtGui.QApplication([])
    db = prepareDB()
    win = MyWidget()
    win.show()
    app.exec_()
    db.close()
    del win, db
Für Deine verschiedenen Itemtypen mußt Du natürlich noch Hand an die Delegates legen. Der Modelunterbau sollte aber analog zu Diesem umsetzbar sein.

Nachtrag :
Mit QDataWidgetMapper kannst Du übrigens relativ einfach einen "Modeleditor" bauen und musst nicht den Weg über einen zweiten View gehen. Ist bei der Verschiedenheit Deiner Items vllt. einfacher umzusetzen.

Viel Spass beim Modeln ;)
Antworten