如何从链接到 QTableView 的模型中插入和删除行

How to insert and remove row from model linked to QTableView

removeRows() 通过删除所选行按预期工作。 但是insertRows()有问题。由于某种原因,新项目没有出现在所选的索引号处。是什么导致了这个问题?

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys

class Model(QAbstractTableModel):
    def __init__(self, parent=None, *args):
        QAbstractTableModel.__init__(self, parent, *args)
        self.items = ['Item_003','Item_000','Item_005','Item_004','Item_001']
        self.numbers=[20,10,30,50,40]
        self.added=0
    def rowCount(self, parent=QModelIndex()):
        return len(self.items)      
    def columnCount(self, parent=QModelIndex()):
        return 2

    def data(self, index, role):
        if not index.isValid(): return QVariant()
        elif role != Qt.DisplayRole:
            return QVariant()

        row=index.row()
        column=index.column()

        if column==0:
            if row<len(self.items):
                return QVariant(self.items[row])
        elif column==1:
            if row<len(self.numbers):
                return QVariant( self.numbers[row] )
        else:
            return QVariant()

    def removeRows(self, row, rows=1, index=QModelIndex()):
        print "Removing at row: %s"%row
        self.beginRemoveRows(QModelIndex(), row, row + rows - 1)
        self.items = self.items[:row] + self.items[row + rows:]
        self.endRemoveRows()
        return True

    def insertRows(self, row, rows=1, index=QModelIndex()):
        print "Inserting at row: %s"%row
        self.beginInsertRows(QModelIndex(), row, row + rows - 1)
        for row in range(rows):
            self.items.insert(row + row, "New Item %s"%self.added)
            self.added+=1
        self.endInsertRows()
        return True

class Proxy(QSortFilterProxyModel):
    def __init__(self):
        super(Proxy, self).__init__()

class MyWindow(QWidget):
    def __init__(self, *args):
        QWidget.__init__(self, *args)
        vLayout=QVBoxLayout(self)
        self.setLayout(vLayout)

        hLayout=QHBoxLayout()
        vLayout.insertLayout(0, hLayout)

        tableModel=Model(self)               

        proxyA=Proxy()
        proxyA.setSourceModel(tableModel)
        proxyB=Proxy()
        proxyB.setSourceModel(tableModel)

        self.ViewA=QTableView(self)
        self.ViewA.setModel(proxyA)
        self.ViewA.clicked.connect(self.viewClicked)
        self.ViewA.setSortingEnabled(True)
        self.ViewA.sortByColumn(0, Qt.AscendingOrder)


        self.ViewB=QTableView(self) 
        self.ViewB.setModel(proxyB)
        self.ViewB.clicked.connect(self.viewClicked)
        self.ViewB.setSortingEnabled(True)
        self.ViewB.sortByColumn(0, Qt.AscendingOrder)

        hLayout.addWidget(self.ViewA)
        hLayout.addWidget(self.ViewB)

        insertButton=QPushButton('Insert Row Above Selection')
        insertButton.setObjectName('insertButton')
        insertButton.clicked.connect(self.buttonClicked)
        removeButton=QPushButton('Remove Selected Item')
        removeButton.setObjectName('removeButton')
        removeButton.clicked.connect(self.buttonClicked)

        vLayout.addWidget(insertButton)
        vLayout.addWidget(removeButton)


    def viewClicked(self, indexClicked):
        print 'indexClicked() row: %s  column: %s'%(indexClicked.row(), indexClicked.column() )
        proxy=indexClicked.model()

    def buttonClicked(self):
        button=self.sender()
        if not button: return

        tableView=None
        if self.ViewA.hasFocus(): tableView=self.ViewA
        elif self.ViewB.hasFocus(): tableView=self.ViewB
        if not tableView: return

        indexes=tableView.selectionModel().selectedIndexes()

        for index in indexes:
            if not index.isValid(): continue
            if button.objectName()=='removeButton':
                tableView.model().removeRows(index.row(), 1, QModelIndex())

            elif button.objectName()=='insertButton':
                tableView.model().insertRows(index.row(), 1, QModelIndex())

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = MyWindow()
    w.show()
    sys.exit(app.exec_())

错误在 insertRows() 方法中。传入的row参数变量(选择的是QModelIndex.row()号)不小心在for loop中re-declared:

for row in range(rows):

我已将参数 row 变量重命名为 postion 并在下面发布了固定的工作代码(proxyB 排序属性已被注释掉。它显示 ViewB 项目未排序。这样可以更容易地看到 "real" 项目顺序:

稍后编辑:

对代码进行了一些调整。 "behind of scenes" 发生了很多事情:在 insertRows()removeRows() 完成他们的工作之前或之后。

例如:

当我们调用这些方法时,提供的第一个参数必须是 QModelIndex 的行号。它将被这些方法用作 "starting point" 或 "starting row number" 从中添加(插入)或删除索引的位置:与第二个整数参数所说的一样多。

我们知道 proxy modelmodelIndexes 的行号和列号与 sourceModel 的相关模型索引不匹配。有趣的是,在 row-number-argument 甚至被这两种方法接收之前,从 proxy 的行号转换为 sourceModel 的行号。从打印输出中可以看出:buttonClicked() 方法 "sends" 第 0 行,而 insertRows() 方法打印出它收到的不是 0 行号(如果提供了 modelIndex "taken" 来自 by-Proxy-driven 启用了排序或筛选并处于活动状态的 TableView。

除此之外,还有一些 "intricate" 机制涉及这两种方法如何从 modelself.items 变量中删除或弹出数据。 removeRows() 方法类型 "returns" 如果行号不按顺序返回到自身以完成作业。 完整的工作代码发布在下面:

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys

class Model(QAbstractTableModel):
    def __init__(self, parent=None, *args):
        QAbstractTableModel.__init__(self, parent, *args)
        self.items = ['Item_A000','Item_B001','Item_A002','Item_B003','Item_B004']
        self.numbers=[20,10,30,50,40]
        self.added=0
    def rowCount(self, parent=QModelIndex()):
        return len(self.items)      
    def columnCount(self, parent=QModelIndex()):
        return 2

    def data(self, index, role):
        if not index.isValid(): return QVariant()
        elif role != Qt.DisplayRole:
            return QVariant()

        row=index.row()
        column=index.column()

        if column==0:
            if row<len(self.items):
                return QVariant(self.items[row])
        elif column==1:
            if row<len(self.numbers):
                return QVariant( self.numbers[row] )
        else:
            return QVariant()

    def removeRows(self, position, rows=1, index=QModelIndex()):
        print "\n\t\t ...removeRows() Starting position: '%s'"%position, 'with the total rows to be deleted: ', rows
        self.beginRemoveRows(QModelIndex(), position, position + rows - 1)       
        self.items = self.items[:position] + self.items[position + rows:]
        self.endRemoveRows()

        return True

    def insertRows(self, position, rows=1, index=QModelIndex()):
        print "\n\t\t ...insertRows() Starting position: '%s'"%position, 'with the total rows to be inserted: ', rows
        indexSelected=self.index(position, 0)
        itemSelected=indexSelected.data().toPyObject()

        self.beginInsertRows(QModelIndex(), position, position + rows - 1)
        for row in range(rows):
            self.items.insert(position + row,  "%s_%s"% (itemSelected, self.added))
            self.added+=1
        self.endInsertRows()
        return True

class Proxy(QSortFilterProxyModel):
    def __init__(self):
        super(Proxy, self).__init__()

    def filterAcceptsRow(self, rowProc, parentProc):  
        modelIndex=self.sourceModel().index(rowProc, 0, parentProc)
        item=self.sourceModel().data(modelIndex, Qt.DisplayRole).toPyObject()

        if item and 'B' in item:
            return True
        else: return False


class MyWindow(QWidget):
    def __init__(self, *args):
        QWidget.__init__(self, *args)
        vLayout=QVBoxLayout(self)
        self.setLayout(vLayout)

        hLayout=QHBoxLayout()
        vLayout.insertLayout(0, hLayout)

        tableModel=Model(self)               

        proxyB=Proxy()
        proxyB.setSourceModel(tableModel)

        self.ViewA=QTableView(self)
        self.ViewA.setModel(tableModel)
        self.ViewA.clicked.connect(self.viewClicked)

        self.ViewB=QTableView(self) 
        self.ViewB.setModel(proxyB)
        self.ViewB.clicked.connect(self.viewClicked)
        self.ViewB.setSortingEnabled(True)
        self.ViewB.sortByColumn(0, Qt.AscendingOrder)
        self.ViewB.setSelectionBehavior(QTableView.SelectRows)

        hLayout.addWidget(self.ViewA)
        hLayout.addWidget(self.ViewB)

        insertButton=QPushButton('Insert Row Above Selection')
        insertButton.setObjectName('insertButton')
        insertButton.clicked.connect(self.buttonClicked)
        removeButton=QPushButton('Remove Selected Item')
        removeButton.setObjectName('removeButton')
        removeButton.clicked.connect(self.buttonClicked)

        vLayout.addWidget(insertButton)
        vLayout.addWidget(removeButton)

    def getZeroColumnSelectedIndexes(self, tableView=None):
        if not tableView: return
        selectedIndexes=tableView.selectedIndexes()
        if not selectedIndexes: return
        return [index for index in selectedIndexes if not index.column()]

    def viewClicked(self, indexClicked):
        print 'indexClicked() row: %s  column: %s'%(indexClicked.row(), indexClicked.column() )
        proxy=indexClicked.model()

    def buttonClicked(self):
        button=self.sender()
        if not button: return

        tableView=None
        if self.ViewA.hasFocus(): tableView=self.ViewA
        elif self.ViewB.hasFocus(): tableView=self.ViewB
        if not tableView: print 'buttonClicked(): not tableView'; return

        zeroColumnSelectedIndexes=self.getZeroColumnSelectedIndexes(tableView)
        if not zeroColumnSelectedIndexes: print 'not zeroColumnSelectedIndexes'; return

        firstZeroColumnSelectedIndex=zeroColumnSelectedIndexes[0]
        if not firstZeroColumnSelectedIndex or not firstZeroColumnSelectedIndex.isValid():
            print 'buttonClicked(): not firstZeroColumnSelectedIndex.isValid()'; return            

        startingRow=firstZeroColumnSelectedIndex.row()
        print '\n buttonClicked() startingRow =', startingRow

        if button.objectName()=='removeButton':            
            tableView.model().removeRows(startingRow, len(zeroColumnSelectedIndexes), QModelIndex())

        elif button.objectName()=='insertButton':
            tableView.model().insertRows(startingRow, len(zeroColumnSelectedIndexes), QModelIndex())


if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = MyWindow()
    w.show()
    sys.exit(app.exec_())