Issue
How do I properly remove items from my custom QAbstractTableModel? Do i need to change this to QStandardItemModel instead?
This is the before:
This is the after...it leaves empty rows and the selection doesn't seem to clear either.
import os
import sys
from PySide import QtCore, QtGui
import random
class CustomJobs(object):
def __init__(self, **kwargs):
super(CustomJobs, self).__init__()
# instance properties
self.name = ''
self.status = ''
# initialize attribute values
for k, v in kwargs.items():
if hasattr(self, k):
setattr(self, k, v)
class PlayblastTableModel(QtCore.QAbstractTableModel):
HEADERS = ['Name', 'Status']
def __init__(self):
super(PlayblastTableModel, self).__init__()
self.items = []
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal:
if role == QtCore.Qt.DisplayRole:
return self.HEADERS[section]
return None
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.HEADERS)
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.items)
def addItem(self, *items):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount() + len(items) - 1)
for item in items:
assert isinstance(item, CustomJobs)
self.items.append(item)
self.endInsertRows()
def removeItems(self, items):
self.beginRemoveRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self.items = [x for x in self.items if x not in items]
self.endRemoveRows()
def clear(self):
self.beginRemoveRows(QtCore.QModelIndex(), 0, self.rowCount())
self.items = []
self.endRemoveRows()
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return
row = index.row()
col = index.column()
if 0 <= row < self.rowCount():
item = self.items[row]
if role == QtCore.Qt.DisplayRole:
if col == 0:
return item.name
elif col == 1:
return item.status.title()
elif role == QtCore.Qt.UserRole:
return item
return None
class CustomJobsQueue(QtGui.QWidget):
'''
Description:
Widget that manages the Jobs Queue
'''
def __init__(self):
super(CustomJobsQueue, self).__init__()
self.resize(400,600)
# controls
self.uiAddNewJob = QtGui.QPushButton('Add')
self.uiRemoveSelectedJobs = QtGui.QPushButton('Remove')
self.playblastJobModel = PlayblastTableModel()
self.uiJobTableView = QtGui.QTableView()
self.uiJobTableView.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.uiJobTableView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.uiJobTableView.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.uiJobTableView.setModel(self.playblastJobModel)
self.jobSelection = self.uiJobTableView.selectionModel()
# sub layouts
self.jobQueueToolsLayout = QtGui.QHBoxLayout()
self.jobQueueToolsLayout.addWidget(self.uiAddNewJob)
self.jobQueueToolsLayout.addWidget(self.uiRemoveSelectedJobs)
self.jobQueueToolsLayout.addStretch()
# layout
self.mainLayout = QtGui.QVBoxLayout()
self.mainLayout.addLayout(self.jobQueueToolsLayout)
self.mainLayout.addWidget(self.uiJobTableView)
self.setLayout(self.mainLayout)
# connections
self.uiAddNewJob.clicked.connect(self.addNewJob)
self.uiRemoveSelectedJobs.clicked.connect(self.removeSelectedJobs)
# methods
def addNewJob(self):
name = random.choice(['Kevin','Suzie','Melissa'])
status = random.choice(['error','warning','successa'])
job = CustomJobs(name=name, status=status)
self.playblastJobModel.addItem(job)
def removeSelectedJobs(self):
jobs = self.getSelectedJobs()
self.playblastJobModel.removeItems(jobs)
def getSelectedJobs(self):
jobs = [x.data(QtCore.Qt.UserRole) for x in self.jobSelection.selectedRows()]
return jobs
def main():
app = QtGui.QApplication(sys.argv)
window = CustomJobsQueue()
window.show()
app.exec_()
if __name__ == '__main__':
main()
Solution
The reason for this behavior is that you're using the wrong row in beginRemoveRows()
: you should use the row number you're removing, and since you're using rowCount()
that row index is invalid.
def removeItems(self, items):
self.beginRemoveRows(QtCore.QModelIndex(), self.rowCount() - 1, self.rowCount() - 1)
self.items = [x for x in self.items if x not in items]
self.endRemoveRows()
To be more correct, you should remove the actual rows in the model. In your simple case it won't matter that much, but in case your model becomes more complex, keep in mind this.
def removeItems(self, items):
removeRows = []
for row, item in enumerate(self.items):
if item in items:
removeRows.append(row)
for row in sorted(removeRows, reverse=True):
self.beginRemoveRows(QtCore.QModelIndex(), row, row)
self.items.pop(row)
self.endRemoveRows()
The reason for the reversed row order in the for cycle is that for list consistency reasons the row removal should always begin from the bottom. This can be important if you want to remove rows arbitrarily while keeping the current selection in case the removed items are not selected.
That said, as already suggested in the comments, if you don't need specific behavior and implementation, creating a QAbstractItemModel (or any abstract model) subclass is unnecessary, as QStandardItemModel will usually be enough, as it already provides all required features (including drag and drop support, which can be rather complex if you don't know how the Qt data model works).
Well, unless it's for learning purposes, obviously.
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.