【问题标题】:qt pyside - qsql*model, qabstractitemmodel and qtreeview interactionqt pyside - qsql*model、qabstractitemmodel 和 qtreeview 交互
【发布时间】:2011-10-22 09:45:33
【问题描述】:

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

我一直认为以下过程是最好的方法:

  1. 连接到数据库
  2. 查询数据
  3. 从数据创建和填充自定义 QAbstractItemModel(通过 dict 对其进行操作以动态创建节点、父节点和子节点 - 对于每个 dict 条目,都会生成一个带有关联父节点的“节点”)
  4. 使用 QDatawidgetmapper 填充其他小部件
  5. 用户编辑数据
  6. QAbstractItemModel (QAIM) 已更新
  7. 然后必须使用 QAIM 模型中的新值运行 UPDATE、INSERT 或任何查询。
  8. 刷新 QAIM 和相关的小部件。

我意识到,如果我只是使用 QTableView 或 QListView,我就不需要自定义模型,可以直接写回数据库。我在上面概述的过程似乎意味着必须保持两组数据的运行——即 SQLite 表和自定义 QAIM,并确保它们都保持最新。这对我来说似乎有点麻烦,我确信必须有更好的方法来做到这一点,其中 QTreeView 直接从 SQLite 表中获取其数据 - 显然需要进行一些操作以将平面数据转换为分层数据。

当然,我想知道,我是否完全误解了 QAbstractItemModel 和 QSQL*Models 之间的关系,并且我因为无知而将其复杂化了?

谢谢

【问题讨论】:

  • 你的分层数据是什么格式的?我通常使用标题和行(例如发票),但他们每个都使用一个模型。
  • 为了(相对)简单,我目前只使用一个带有“父”列的表。这表明应该将子节点附加到哪个节点/记录。
  • 好吧,不看具体数据我不能肯定你应该以其他方式存储它,但是将父母和孩子存储在同一张表中的机会很小,我在生产中有一个案例,无论如何我认为你必须坚持你的想法,保持你的模型和 SQL 同步,Qt 不必从对编辑 SQL 表的完美支持开始,我已经创建了一个 QSqlQueryModel子类有一个更好的QSqlTableModel,也许你也可以这样做,但是如果你只打算使用一次,我不建议你这样做,会更多的工作。

标签: python model-view-controller qt sqlite pyside


【解决方案1】:

您想要的是充当QSql*Model 和视图之间的桥梁的代理模型。为此,您需要继承 QAbstractProxyModel。您必须以一致的方式在代理模型中查找父子关系并将它们映射到源模型,因此可能需要在代理模型中保留一些计数。

当您对QAbstractProxyModel 进行子类化时,您至少需要重新定义这些方法:

  • 行数
  • 列数
  • 父母
  • 索引
  • 数据
  • mapToSource
  • mapFromSource

另外,请记住QAbstractProxyModel 不会自动传播信号。因此,为了让视图知道源模型的变化(如插入、删除、更新),您需要在代理模型中传递它们(当然,在代理模型中更新您的映射)。

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

编辑

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

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()

【讨论】:

  • 谢谢。我一直在做一些研究,并得出结论 QAbstractProxyModel 可能是路线。不过现在对我来说看起来很复杂:)
  • @StevenLee:现在,我没有一个可行的例子。对不起。但是我正在开发一个自定义代理,它根据给定的列对表模型(如 QSqlTableModel)中的项目进行分组。如果你喜欢,我完成后可以分享。
  • 非常感谢。谢谢
  • 是的,这可能是获得您想要的东西的好方法,我从来没有使用过分层数据的代理,但它确实可以让您灵活地做。
  • @StevenLee:我包含了我的自定义代理,它将平面模型中的项目分组。抱歉耽搁了,我几乎没有时间完成这个。老实说,这可能有点粗糙,可能需要进行一些清理,但现在它按预期工作。它可能会给你一些想法。
猜你喜欢
  • 2016-07-20
  • 2011-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-26
  • 2015-10-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多