qt pyside - qsql*model、qabstractitemmodel 和 qtreeview 交互

发布于 2024-12-11 11:14:59 字数 778 浏览 1 评论 0原文

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

我一直在努力,以下过程将是执行此操作的最佳方法:

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

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

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

谢谢

I want to produce a simple enough application which uses a QTreeView widget to show hierarchical data from a SQLite3 (flat) table, use QDataWidgetMapper to populate some lineedit fields, allow user to edit, which in turn updates the table. Simple & basic (for most!).

I have been working on the basis that the following process would be the best way of doing this:

  1. Connect to Dbase
  2. Query data
  3. Create and populate custom QAbstractItemModel from the data (manipulating it through a dict to create nodes, parents and children dynamically - for each dict entry a 'node' is generated with an associated parent)
  4. Use QDatawidgetmapper to populate other widgets
  5. User edits data
  6. QAbstractItemModel (QAIM) is updated
  7. Then have to run an UPDATE, INSERT or whatever query using new values in the QAIM model.
  8. Refresh the QAIM and associated widgets.

I realise if I were just using a QTableView or QListView I would not need the custom model and could just write straight back into the database. The process I have outlined above seems to mean having to keep two sets of data going - i.e. the SQLite table and the custom QAIM and ensure that they are both kept up to date. This seems a bit cumbersome to me and I'm sure there must be a better way of doing it where the QTreeView is taking its data straight from the SQLite table - with the obvious need for some manipulation to convert the flat data into hierarchical data.

I am wondering, of course, whether I have completely misunderstood the relationship between QAbstractItemModel and the QSQL*Models and I am overcomplicating it through ignorance?

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

ゞ花落谁相伴 2024-12-18 11:14:59

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

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

  • rowCount
  • columnCount
  • 索引
  • 数据
  • 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()

What you want is a proxy model that acts as a bridge between QSql*Model and the view. For that, you need to subclass QAbstractProxyModel. You have to have a consistent way of finding parent-child relationships in proxy model and mapping them to the source model, so that might require keeping some tally in the proxy model.

When you are sub-classing QAbstractProxyModel, you need to re-define, at minimum, these methods:

  • rowCount
  • columnCount
  • parent
  • index
  • data
  • mapToSource
  • mapFromSource

Also, keep in mind that QAbstractProxyModel does not auto-propagate signals through. So, in order to have the view be aware of changes in source model (like insert, delete, update), you need to pass them in the proxy model (while of course, updating your mappings in the proxy model).

It will require some work, but in the end you'll have a more flexible structure. And it will eliminate all the stuff that you need to do for synchronizing database and custom QAbstractItemModel.

Edit

A custom proxy model that groups items from a flat model according to a given column:

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()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文