PySide 填空 TableModel

Posted

技术标签:

【中文标题】PySide 填空 TableModel【英文标题】:PySide fill empty TableModel 【发布时间】:2016-08-09 13:19:04 【问题描述】:

我编写了一个并排有两个表视图的工具。我在它们之间拖放数据。一切正常,但无法让空模型正常工作。所以一开始我想要一个空的 TableView 并通过拖放添加项目。当至少有一个对象在其中时有效,但对于空对象则无效。一个空的甚至没有标题。

我认为问题在于 TableView 为空时不会调用 Models headerData ...但不知道这是否正确。 这是我的模型

如果我不设置空模型,它会起作用,但是等到模型中有一个项目然后再设置它。但这不是很好......

class myTableModel(TableModel):
    def __init__(self, headers = [], items = [[]], parent = None):
        super(myTableModel,self).__init__(headers,items,parent)
        self.__items = items
        self.__headers = headers

    def flags(self, index):
        if not index.isValid():
            return QtCore.Qt.ItemIsEnabled
        if index.column() == 0:
            return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsEditable
        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled

    def supportedDropActions(self): 
        return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction    

    def removeRow(self, row, index=QtCore.QModelIndex()):
        self.beginRemoveRows(QtCore.QModelIndex(), row, row)
        self.__items.pop(row)
        self.endRemoveRows()
        return True

    def insertRow(self, data,row=0,index=QtCore.QModelIndex()):
        self.beginInsertRows(QtCore.QModelIndex(), 0,0)
        self.__items.insert(0, data)
        self.endInsertRows()
        return True        
    def data(self,index,role=QtCore.Qt.DisplayRole):
        if role == QtCore.Qt.DisplayRole:
            row = index.row()
            column = index.column()

            if len(self.__items[row])-1 < column:
                return ""
            else:
                return self.__items[row][column]

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        if role == QtCore.Qt.EditRole:
            row = index.row()
            column = index.column()
            self.__items[row][column] = value
            self.dataChanged.emit(index, index)
            return True

    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                if len(self.__headers)-1 < section:
                    return ""
                else:
                    #print "%s in section %i" %(self.__headers[section],section)
                    return self.__headers[section]   

【问题讨论】:

【参考方案1】:

我想出答案可能为时已晚,但这个问题本身对我来说很有趣,所以我深入研究并找到了解决方案。

事实证明,在实现表格模型的空状态时有几个陷阱,它仍然显示列标题,此外还允许将内容放在自身上。

第一个问题是如果columnCount 方法为无效的QModelIndex 返回0,Qt 似乎不会绘制列标题。在这种情况下,它似乎真的不需要调用headerData 方法。解决方案是永远不要从 columnCount 返回 0 和无效的 QModelIndex,即使行数实际上是 0 并且我们的底层数据结构是 2D 数组,其中 0 行表示 0 列。

第二个问题是您似乎需要一个自定义视图子类化QTableView,因为您需要覆盖dragEnterEventdragMoveEvent无条件地接受拖动进入和拖动移动。否则即使您将视图的acceptDrops 属性设置为True,drop 事件也没有机会触发。 dragEnterEvent 和/或 dragMoveEvent 的默认实现似乎与视图的底层模型对话,如果模型没有行,则视图拒绝接受丢弃。

第三个问题并不是什么问题,只是在模型的 dropMimeData 方法中,如果没有行,则需要插入行,以便为丢弃的项目腾出空间。

这是使用 Python 2.7 测试的 PySide 1.2.1 / Qt 4.8.5 的完整解决方案:

import sys
from PySide import QtCore, QtGui

class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, headers = [], items = [[]], parent = None):
        super(TableModel,self).__init__(parent)
        self.__items = items
        self.__headers = headers

        row_counter = 0
        for row_item in self.__items:
            column_counter = 0
            for column_item in row_item:
                idx = self.createIndex(row_counter, column_counter)
                self.setData(idx, column_item, QtCore.Qt.EditRole)
                self.dataChanged.emit(idx, idx)
                column_counter += 1
            row_counter += 1        

        num_headers = len(self.__headers)
        for section in range(0, num_headers):
            self.setHeaderData(section, QtCore.Qt.Horizontal, self.__headers[section])
        self.headerDataChanged.emit(QtCore.Qt.Horizontal, 0, num_headers)

    def index(self, row, column, parent):
        if row < 0 or row >= len(self.__items):
            return QtCore.QModelIndex()
        return self.createIndex(row, column, self.__items[row])

    def parent(self, index):
        return QtCore.QModelIndex()

    def rowCount(self, index):
        if index.isValid():
            return 

        num_rows = len(self.__items)

        # checking for empty nested columns list within a single "row"
        if num_rows == 1:
            if len(self.__items[0]) == 0:
                return 0

        return num_rows

    def columnCount(self, index):
        if index.isValid():
            return 0

        # compute the max column count within all rows
        max_column_count = 0
        for row in self.__items:
            column_count = len(row)
            if column_count > max_column_count:
                max_column_count = column_count

        # if there are no real columns, make the column count return the number of headers instead
        if max_column_count < len(self.__headers):
            max_column_count = len(self.__headers)
        return max_column_count

    def flags(self, index):
        if not index.isValid():
            return QtCore.Qt.ItemIsEnabled
        if index.column() == 0:
            return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | \
                   QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsEditable
        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | \
               QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled

    def supportedDropActions(self): 
        return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction    

    def insertRows(self, row, count, index):
        if index.isValid():
            return False
        if count <= 0:
            return False
        num_columns = self.columnCount(index)
        # inserting 'count' empty rows starting at 'row'
        self.beginInsertRows(QtCore.QModelIndex(), row, row + count - 1)
        for i in range(0, count):
            # inserting as many columns as the table currently has
            self.__items.insert(row + i, ["" for i in range(0, num_columns)])
        self.endInsertRows()
        return True

    def removeRows(self, row, count, index):
        if index.isValid():
            return False
        if count <= 0:
            return False
        num_rows = self.rowCount(QtCore.QModelIndex())
        self.beginRemoveRows(QtCore.QModelIndex(), row, row + count - 1)
        for i in range(count, 0, -1):
            self.__items.pop(row - i + 1)
        self.endRemoveRows()
        return True

    def data(self,index,role=QtCore.Qt.DisplayRole):
        if role == QtCore.Qt.DisplayRole:
            row = index.row()
            column = index.column()
            if row < 0 or row >= len(self.__items):
                return ""
            if column < 0 or column >= len(self.__items[row]):
                return ""
            else:
                return self.__items[row][column]
        return None

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        if not index.isValid:
            return False
        if role == QtCore.Qt.EditRole:
            row = index.row()
            column = index.column()
            if row < 0 or row >= self.rowCount(QtCore.QModelIndex()):
                return False
            if column < 0 or column >= len(self.__items[row]):
                return False
            self.__items[row].pop(column)
            self.__items[row].insert(column, value)
            self.dataChanged.emit(index, index)
            return True
        return False

    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                if section < 0 or section >= len(self.__headers):
                    return ""
                else:
                    return self.__headers[section]
        return None

    def mimeTypes(self):
        return ['application/vnd.tableviewdragdrop.list']

    def mimeData(self, indexes):
        mimedata = QtCore.QMimeData()
        encoded_data = QtCore.QByteArray()
        stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.WriteOnly)
        for index in indexes:
            if index.isValid():
                text = self.data(index, QtCore.Qt.DisplayRole)
                stream << QtCore.QByteArray(text)
        mimedata.setData('application/vnd.tableviewdragdrop.list', encoded_data)
        return mimedata

    def dropMimeData(self, data, action, row, column, parent):
        if action == QtCore.Qt.IgnoreAction:
            return True
        if not data.hasFormat('application/vnd.tableviewdragdrop.list'):
            return False
        if column > 0:
            return False

        num_rows = self.rowCount(QtCore.QModelIndex())

        begin_row = 0
        if row != -1:
            begin_row = row
        elif parent.isValid():
            begin_row = parent.row()
        else:
            begin_row = num_rows

        if begin_row == num_rows:
            self.insertRows(begin_row, 1, QtCore.QModelIndex())

        if column < 0:
            if parent.isValid():
                column = parent.column()
            else:
                column = 0

        encoded_data = data.data('application/vnd.tableviewdragdrop.list')
        stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.ReadOnly)
        new_items = []
        rows = 0
        while not stream.atEnd():
            text = QtCore.QByteArray()
            stream >> text
            new_items.append(str(text))
            rows += 1

        for text in new_items:
            idx = self.index(begin_row, column, QtCore.QModelIndex())
            self.setData(idx, text, QtCore.Qt.EditRole)
            begin_row += 1

        return True

class TableView(QtGui.QTableView):
    def __init__(self, parent=None):
        super(TableView, self).__init__(parent)
        self.setAcceptDrops(True)

    def dragEnterEvent(self, event):
        event.accept()

    def dragMoveEvent(self, event):
        event.accept()

class MainForm(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MainForm, self).__init__(parent)

        self.left_model = TableModel(headers=["column0", "column1", "column2"])
        #self.left_model = TableModel(items=[["left0", "left1", "left2"],["left3","left4","left5"]],
        #                             headers=["column0", "column1", "column2"])
        self.right_model = TableModel(items=[['right0', 'right1', 'right2'], ['right3', 'right4', 'right5']],
                                      headers=['column0', 'column1', 'column2'])

        self.left_view = TableView()
        self.left_view.setModel(self.left_model)
        self.left_view.setDragDropMode(QtGui.QAbstractItemView.DragDrop)

        self.right_view = TableView()
        self.right_view.setModel(self.right_model)
        self.right_view.setDragDropMode(QtGui.QAbstractItemView.DragDrop)

        self.layout = QtGui.QHBoxLayout()
        self.layout.addWidget(self.left_view)
        self.layout.addWidget(self.right_view)

        self.window = QtGui.QWidget()
        self.window.setLayout(self.layout)

        self.setCentralWidget(self.window)

def main():
    app = QtGui.QApplication(sys.argv)
    form = MainForm()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()

【讨论】:

以上是关于PySide 填空 TableModel的主要内容,如果未能解决你的问题,请参考以下文章

PySide和PySide2之间的QKeySequence区别

PySide2兼容PySide1的补丁代码

pySide: ExtensionLoader_Pyside_QtGUI.py 找不到指定的模块

Python/pyside,pyqt(pyside,pyqt optional): 控制文本选择的函数

当 slot 函数具有默认参数=None 时,PySide2 的行为与 PySide 不同

python3的Pyside-uic?