Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

How do I properly remove items from my custom QAbstractTableModel? Do i need to change this to QStandardItemModel instead?

This is the before:

enter image description here

This is the after...it leaves empty rows and the selection doesn't seem to clear either.

enter image description here

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()
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
135 views
Welcome To Ask or Share your Answers For Others

1 Answer

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...