PyQt:使用QAbstractTableModel向QTableView添加行

15

我对Qt编程非常新手。我正在尝试创建一个简单的表格,可以通过单击按钮添加行。我可以成功地实现表格,但似乎无法在表格上显示更新后的数据。我认为我的问题源于我似乎不能正确调用任何“更改数据”方法来使用按钮。我尝试了几种不同的在线解决方案,所有这些解决方案都导致4年前的死胡同帖子。到目前为止,我只有基本结构,我无法弄清楚如何使表格使用新数据进行更新。

这是基本视图

我已经设置了一些测试数据。

在最终实现中,表格将为空,并且我想附加行并将它们显示在表格视图中。

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

class MyWindow(QWidget):
    def __init__(self):
        QWidget.__init__(self)

        # create table
        self.get_table_data()
        self.table = self.createTable()

        # layout
        self.layout = QVBoxLayout()

        self.testButton = QPushButton("test")
        self.connect(self.testButton, SIGNAL("released()"), self.test)        

        self.layout.addWidget(self.testButton)
        self.layout.addWidget(self.table)
        self.setLayout(self.layout)

    def get_table_data(self):
        self.tabledata = [[1234567890,2,3,4,5],
                          [6,7,8,9,10],
                          [11,12,13,14,15],
                          [16,17,18,19,20]]

    def createTable(self):
        # create the view
        tv = QTableView()

        # set the table model
        header = ['col_0', 'col_1', 'col_2', 'col_3', 'col_4']
        tablemodel = MyTableModel(self.tabledata, header, self)
        tv.setModel(tablemodel)

        # set the minimum size
        tv.setMinimumSize(400, 300)

        # hide grid
        tv.setShowGrid(False)

        # hide vertical header
        vh = tv.verticalHeader()
        vh.setVisible(False)

        # set horizontal header properties
        hh = tv.horizontalHeader()
        hh.setStretchLastSection(True)

        # set column width to fit contents
        tv.resizeColumnsToContents()

        # set row height
        tv.resizeRowsToContents()

        # enable sorting
        tv.setSortingEnabled(False)

        return tv

    def test(self):
        self.tabledata.append([1,1,1,1,1])
        self.emit(SIGNAL('dataChanged()'))
        print 'success'

class MyTableModel(QAbstractTableModel):
    def __init__(self, datain, headerdata, parent=None):
        """
        Args:
            datain: a list of lists\n
            headerdata: a list of strings
        """
        QAbstractTableModel.__init__(self, parent)
        self.arraydata = datain
        self.headerdata = headerdata

    def rowCount(self, parent):
        return len(self.arraydata)

    def columnCount(self, parent):
        if len(self.arraydata) > 0: 
            return len(self.arraydata[0]) 
        return 0

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

    def setData(self, index, value, role):
        pass         # not sure what to put here

    def headerData(self, col, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return QVariant(self.headerdata[col])
        return QVariant()

    def sort(self, Ncol, order):
        """
        Sort table by given column number.
        """
        self.emit(SIGNAL("layoutAboutToBeChanged()"))
        self.arraydata = sorted(self.arraydata, key=operator.itemgetter(Ncol))       
        if order == Qt.DescendingOrder:
            self.arraydata.reverse()
        self.emit(SIGNAL("layoutChanged()"))

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

1
同意。你问题中的代码示例是许多其他问题的答案。 - niCk cAMel
3个回答

14

当模型的底层数据发生变化时,模型应该发出 layoutChanged 或者 layoutAboutToBeChanged 信号,以便视图正确更新(如果您想要更新特定单元格范围,还可以使用dataChanged)。

所以你只需要像这样:

    def test(self):
        self.tabledata.append([1,1,1,1,1])
        self.table.model().layoutChanged.emit()
        print 'success'

在模型中,setData()方法也需要吗?我查看了所有文档,均提到需要使用setData()或insertRows()等方法。问题是我不确定如何实现setData()方法,更重要的是如何调用它。编辑:我刚刚尝试了您的编辑,没有使用setData()方法完美地工作,所以我猜它并不是必需的!非常感谢您的帮助! - user3439556
通常情况下,当您想要一个可编辑的网格时,需要实现setData()函数,以便在模型中更新正确的值。例如: def setData(self, index, value, role = Qt.EditRole): if role == Qt.EditRole: setattr(self.arraydata[index.row()], self.columns[index.column()], value) self.dataChanged.emit(index, index, ()) return True else: return False - TheGerm
从技术上讲,这是行不通的,因为self.tabledata只是一个list,并没有真正连接到QTableView。 - NL23codes

2

QAbstractTableModel有两个特殊的方法(beginInsertRows()endInsertRows())可用于此操作。

您可以在自定义模型中添加API点。例如:

    def insertGuest(self, guest):
        self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
        self.guestsTableData.append(guest)
        self.endInsertRows()

1
我认为这是更好的解决方案,因为layoutChanged信号很可能会导致整个视图的重绘或重新评估。而begin/endInstertRows是一种更微妙的通知视觉元素新行已添加及其位置的方式。如果添加的行不在当前视图中,则可以忽略它(或仅更新/重绘滚动条)。 - brookbot

1
我已将您的表格引用更改为类变量,而不是实例变量,因此您可以从代码的任何地方编辑表格数据。
# First access the data of the table
self.tv_model = self.tv.model()

其次,我使用类似pandas数据框编辑的方法。假设您要添加的数据已经存储在一个变量中:
# These can be whatever, but for consistency, 
# I used the data in the OP's example

new_values = [1, 1, 1, 1, 1]

根据数据是添加到表中还是更新现有值,下一步可以采用不同的方法。将数据添加为新行的方法如下。

# The headers should also be a class variable, 
# but I left it as the OP had it

header = ['col_0', 'col_1', 'col_2', 'col_3', 'col_4']

# There are multiple ways of establishing what the row reference should be,
# this is just one example how to add a new row

new_row = len(self.tv_model.dataFrame.index)

for i, col in enumerate(header):
    self.tv_model.dataFrame.loc[new_row, col] = new_values[i]

由于self.tv_model是指向表格实际数据的引用,发出以下信号将更新数据,或者可以说是将其“提交”到模型中。

self.tv_model.layoutChanged.emit()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接