Python qt-pyside-qsql*模型、qabstractemodel和qtreeview交互

Python qt-pyside-qsql*模型、qabstractemodel和qtreeview交互,python,model-view-controller,qt,sqlite,pyside,Python,Model View Controller,Qt,Sqlite,Pyside,我想生成一个足够简单的应用程序,它使用QTreeView小部件显示SQLite3(平面)表中的分层数据,使用QDataWidgetMapper填充一些lineedit字段,允许用户编辑,从而更新表。简单和基本(对大多数人来说!) 我一直在努力,以下过程将是实现这一目标的最佳方式: 连接到数据库 查询数据 从数据中创建并填充自定义QAbstractItemModel(通过dict操作它以动态创建节点、父项和子项-对于每个dict条目,将生成一个带有关联父项的“节点”) 使用QDatawidgetm

我想生成一个足够简单的应用程序,它使用QTreeView小部件显示SQLite3(平面)表中的分层数据,使用QDataWidgetMapper填充一些lineedit字段,允许用户编辑,从而更新表。简单和基本(对大多数人来说!)

我一直在努力,以下过程将是实现这一目标的最佳方式:

  • 连接到数据库
  • 查询数据
  • 从数据中创建并填充自定义QAbstractItemModel(通过
    dict
    操作它以动态创建节点、父项和子项-对于每个dict条目,将生成一个带有关联父项的“节点”)
  • 使用QDatawidgetmapper填充其他小部件
  • 用户编辑数据
  • QAbstracteModel(QAIM)已更新
  • 然后必须使用QAIM模型中的新值运行更新、插入或任何查询
  • 刷新QAIM和相关小部件
  • 我意识到,如果我只是使用QTableView或QListView,我就不需要定制模型,可以直接写回数据库。我上面概述的过程似乎意味着必须保持两组数据的运行——即SQLite表和自定义QAIM,并确保它们都保持最新。这对我来说似乎有点麻烦,而且我确信在QTreeView直接从SQLite表获取数据的情况下,肯定有更好的方法来实现这一点——显然需要一些操作来将平面数据转换为层次数据

    当然,我想知道我是否完全误解了QAbstractItemModel和QSQL*模型之间的关系,并且由于无知而使其过度复杂化了


    谢谢

    您需要的是一个代理模型,它充当
    QSql*模型
    和视图之间的桥梁。为此,您需要子类化
    QAbstractProxyModel
    。您必须在代理模型中找到父子关系并将其映射到源模型,这样可能需要在代理模型中保留一些记录

    当您对QAbstractProxyModel进行子分类时,您至少需要重新定义以下方法:

    • 行数
    • 列数
    • 母公司
    • 索引
    • 资料
    • 映射源
    • mapFromSource
    另外,请记住,
    QAbstractProxyModel
    不会自动传播信号。因此,为了让视图知道源模型中的更改(如插入、删除、更新),您需要在代理模型中传递这些更改(当然,在代理模型中更新映射)

    这将需要一些工作,但最终你会有一个更灵活的结构。它将消除同步数据库和自定义
    qabstractemmodel
    所需的所有工作

    编辑

    根据给定列对平面模型中的项进行分组的自定义代理模型:

    import sys
    from collections import namedtuple
    import random
    
    from PyQt4 import QtCore, QtGui
    
    groupItem = namedtuple("groupItem",["name","children","index"])
    rowItem = namedtuple("rowItem",["groupIndex","random"])
    
    
    class GrouperProxyModel(QtGui.QAbstractProxyModel):
        def __init__(self, parent=None):
            super(GrouperProxyModel, self).__init__(parent)
    
            self._rootItem = QtCore.QModelIndex()
            self._groups = []       # list of groupItems
            self._groupMap = {}     # map of group names to group indexes
            self._groupIndexes = [] # list of groupIndexes for locating group row
            self._sourceRows = []   # map of source rows to group index
            self._groupColumn = 0   # grouping column.
    
        def setSourceModel(self, source, groupColumn=0):
            super(GrouperProxyModel, self).setSourceModel(source)
    
            # connect signals
            self.sourceModel().columnsAboutToBeInserted.connect(self.columnsAboutToBeInserted.emit)
            self.sourceModel().columnsInserted.connect(self.columnsInserted.emit)
            self.sourceModel().columnsAboutToBeRemoved.connect(self.columnsAboutToBeRemoved.emit)
            self.sourceModel().columnsRemoved.connect(self.columnsRemoved.emit)
    
            self.sourceModel().rowsInserted.connect(self._rowsInserted)
            self.sourceModel().rowsRemoved.connect(self._rowsRemoved)
            self.sourceModel().dataChanged.connect(self._dataChanged)
    
            # set grouping
            self.groupBy(groupColumn)
    
        def rowCount(self, parent):
            if parent == self._rootItem:
                # root level
                return len(self._groups)
            elif parent.internalPointer() == self._rootItem:
                # children level
                return len(self._groups[parent.row()].children)
            else:
                return 0
    
        def columnCount(self, parent):
            if self.sourceModel():
                return self.sourceModel().columnCount(QtCore.QModelIndex())
            else:
                return 0
    
        def index(self, row, column, parent):
            if parent == self._rootItem:
                # this is a group
                return self.createIndex(row,column,self._rootItem)
            elif parent.internalPointer() == self._rootItem:
                return self.createIndex(row,column,self._groups[parent.row()].index)
            else:
                return QtCore.QModelIndex()
    
        def parent(self, index):
            parent =  index.internalPointer()
            if parent == self._rootItem:
                return self._rootItem
            else:
                parentRow = self._getGroupRow(parent)
                return self.createIndex(parentRow,0,self._rootItem)
    
        def data(self, index, role):
            if role == QtCore.Qt.DisplayRole:
                parent = index.internalPointer()
                if parent == self._rootItem:
                    return self._groups[index.row()].name
                else:
                    parentRow = self._getGroupRow(parent)
                    sourceRow = self._sourceRows.index(self._groups[parentRow].children[index.row()])
                    sourceIndex = self.createIndex(sourceRow, index.column(), 0)
                    return self.sourceModel().data(sourceIndex, role)
            return None
    
        def flags(self, index):
            return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
    
        def headerData(self, section, orientation, role):
            return self.sourceModel().headerData(section, orientation, role)
    
        def mapToSource(self, index):
            if not index.isValid():
                return QtCore.QModelIndex()
    
            parent = index.internalPointer()
            if not parent.isValid():
                return QtCore.QModelIndex()
            elif parent == self._rootItem:
                return QtCore.QModelIndex()
            else:
                rowItem_ = self._groups[parent.row()].children[index.row()]
                sourceRow = self._sourceRows.index(rowItem_)
                return self.createIndex(sourceRow, index.column(), QtCore.QModelIndex())
    
        def mapFromSource(self, index):
            rowItem_ = self._sourceRows[index.row()]
            groupRow = self._getGroupRow(rowItem_.groupIndex)
            itemRow = self._groups[groupRow].children.index(rowItem_)
            return self.createIndex(itemRow,index.column(),self._groupIndexes[groupRow])
    
        def _clearGroups(self):
            self._groupMap = {}
            self._groups = []
            self._sourceRows = []
    
        def groupBy(self,column=0):
            self.beginResetModel()
            self._clearGroups()
            self._groupColumn = column
            sourceModel = self.sourceModel()
            for row in range(sourceModel.rowCount(QtCore.QModelIndex())):
                groupName = sourceModel.data(self.createIndex(row,column,0),
                                             QtCore.Qt.DisplayRole)
    
                groupIndex = self._getGroupIndex(groupName)
                rowItem_ = rowItem(groupIndex,random.random())
                self._groups[groupIndex.row()].children.append(rowItem_)
                self._sourceRows.append(rowItem_)
    
            self.endResetModel()
    
        def _getGroupIndex(self, groupName):
            """ return the index for a group denoted with name.
            if there is no group with given name, create and then return"""
            if groupName in self._groupMap:
                return self._groupMap[groupName]
            else:
                groupRow = len(self._groupMap)
                groupIndex = self.createIndex(groupRow,0,self._rootItem)
                self._groupMap[groupName] = groupIndex
                self._groups.append(groupItem(groupName,[],groupIndex))
                self._groupIndexes.append(groupIndex)
                self.layoutChanged.emit()
                return groupIndex
    
        def _getGroupRow(self, groupIndex):
            for i,x in enumerate(self._groupIndexes):
                if id(groupIndex)==id(x):
                    return i
            return 0
    
        def _rowsInserted(self, parent, start, end):
            for row in range(start, end+1):
                groupName = self.sourceModel().data(self.createIndex(row,self._groupColumn,0),
                                                    QtCore.Qt.DisplayRole)
                groupIndex = self._getGroupIndex(groupName)
                self._getGroupRow(groupIndex)
                groupItem_ = self._groups[self._getGroupRow(groupIndex)]
                rowItem_ = rowItem(groupIndex,random.random())
                groupItem_.children.append(rowItem_)
                self._sourceRows.insert(row, rowItem_)
            self.layoutChanged.emit()
    
        def _rowsRemoved(self, parent, start, end):
            for row in range(start, end+1):
                rowItem_ = self._sourceRows[start]
                groupIndex = rowItem_.groupIndex
                groupItem_ = self._groups[self._getGroupRow(groupIndex)]
                childrenRow = groupItem_.children.index(rowItem_)
                groupItem_.children.pop(childrenRow)
                self._sourceRows.pop(start)
                if not len(groupItem_.children):
                    # remove the group
                    groupRow = self._getGroupRow(groupIndex)
                    groupName = self._groups[groupRow].name
                    self._groups.pop(groupRow)
                    self._groupIndexes.pop(groupRow)
                    del self._groupMap[groupName]
            self.layoutChanged.emit()
    
        def _dataChanged(self, topLeft, bottomRight):
            topRow = topLeft.row()
            bottomRow = bottomRight.row()
            sourceModel = self.sourceModel()
            # loop through all the changed data
            for row in range(topRow,bottomRow+1):
                oldGroupIndex = self._sourceRows[row].groupIndex
                oldGroupItem = self._groups[self._getGroupRow(oldGroupIndex)]
                newGroupName = sourceModel.data(self.createIndex(row,self._groupColumn,0),QtCore.Qt.DisplayRole)
                if newGroupName != oldGroupItem.name:
                    # move to new group...
                    newGroupIndex = self._getGroupIndex(newGroupName)
                    newGroupItem = self._groups[self._getGroupRow(newGroupIndex)]
    
                    rowItem_ = self._sourceRows[row]
                    newGroupItem.children.append(rowItem_)
    
                    # delete from old group
                    oldGroupItem.children.remove(rowItem_)
                    if not len(oldGroupItem.children):
                        # remove the group
                        groupRow = self._getGroupRow(oldGroupItem.index)
                        groupName = oldGroupItem.name
                        self._groups.pop(groupRow)
                        self._groupIndexes.pop(groupRow)
                        del self._groupMap[groupName]
    
            self.layoutChanged.emit()
    

    您的分层数据是什么格式的?我通常使用标题和行(例如发票),但它们每一行都使用一个模型。为了(相对)简单,我目前只使用一个带有“父”列的表。这表明应该将子节点附加到哪个节点/记录。如果不看具体数据,我不能确定您是否应该以其他方式存储它,但是将父节点和子节点存储在同一个表中是一个好主意的可能性很小,尽管我在生产中有一个案例,无论如何,我认为你必须坚持你的想法,保持你的模型和SQL同步,Qt不必从一个完美的支持编辑SQL表开始,我已经创建了一个QSqlQueryModel子类来拥有一个更好的QSqlTableModel,也许你也可以这样做,但是如果你只打算使用一次,我不建议你这样做,我会做更多的工作。谢谢你。我已经做了一些研究,并得出结论,QAbstractProxyModel可能是路线。不过目前对我来说这看起来很复杂:)@StevenLee:现在,我还没有一个有效的例子。很抱歉但我正在开发一个自定义代理,它根据给定的列对来自表模型(如QSqlTableModel)的项进行分组。如果你愿意的话,我可以在做完后和你分享。那将非常感谢。谢谢这是一个很好的方式来获得你想要的,我从来没有使用过层次数据的代理,但它在允许你做的事情上非常灵活。@StevenLee:我包括了我的自定义代理,它可以对平面模型中的项目进行分组。抱歉耽搁了,我几乎没时间完成这件事。老实说,这可能有点粗糙,可能需要一些清理,但目前它的工作如预期。它可能会给你一些想法。