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
,因为您需要覆盖dragEnterEvent
和dragMoveEvent
以无条件地接受拖动进入和拖动移动。否则即使您将视图的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区别
pySide: ExtensionLoader_Pyside_QtGUI.py 找不到指定的模块
Python/pyside,pyqt(pyside,pyqt optional): 控制文本选择的函数