Search code examples
pythonmayaqitemdelegatepyside2

QItemDelegate CheckBox


So, I'm using python and PySide2 in Maya for custom tools.

I have a model (QTableModel), and a QTableView.

every row in the model will have a bunch of information and a checkbox.

I got to do a QItemDelegate and use it as a check box. That is fine. I having trouble getting if that delegate is Checked or not.

I iterate thru the model getting its data (to store it inside a Maya scene in a node)

    data = []
    rows = self.rowCount(1) #self is the model in this snnipet
    for row in range(rows):
        array = []
        for column in range (6): #6 for the fixed number of columns
            info = index.data()
            array.append(index.data())
        data.append(array)

And it happens that the first item in every row is a check box (delegate). In the final data array I end up getting the actual QItemDelegate Object when I really wanted to get its state, checked or not, but it has not an isChecked() method.

Ideas on how to get that?

Thank you very much!

######## EDIT 1

So, the model does have the flag mentioned in the comments:

def flags(self, index):
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable

I think i am doing the delegate wrong, as I confess, i found this snnipet on line and I'm trying to understand it.... but it goes like this:

class CBDelegate(QItemDelegate):

def __init__(self, parent):

    QItemDelegate.__init__(self, parent)


def paint(self, painter, option, index):

    self.cb = QCheckBox()

    try:
        self.cb.setChecked(index.data())
    except:
        pass

    if not self.parent().indexWidget(index):
        self.parent().setIndexWidget(index, self.cb)

and then, at the TableView:

 self.setItemDelegateForColumn(0, CBDelegate(self))

Does it help? (if you have maya 2017 i can give you the whole code... it is a kind of a mess as I'm using this as an learning exercise!

Thank you.

############ EDIT 2

Table View:

class Table(QTableView):
    def __init__(self, *args, **kwargs):
        QTableView.__init__(self, *args, **kwargs)

        # Set the delegate for column 0 of our table
        #self.setItemDelegateForColumn(6, ButtonDelegate(self)) #problem for another time
        self.setItemDelegateForColumn(0, CBDelegate(self))

Model:

class Model(QtCore.QAbstractTableModel):

    def __init__(self, cycles = [[]], headers = [], parent = None):
        QtCore.QAbstractTableModel.__init__(self, parent)

        self.cycles = cycles
        self.headers = headers

    def rowCount(self, parent):

        return len(self.cycles)

    def columnCount(self, parent):
        return len(self.cycles[0])

    def flags(self, index):
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable

    def data(self, index, role):

        if role == QtCore.Qt.DisplayRole:

            row = index.row()
            column = index.column()
            value = self.cycles[row][column]
            return value

        if role == QtCore.Qt.EditRole:

            row = index.row()
            column = index.column()
            return self.cycles[row][column]

        if (role == QtCore.Qt.TextAlignmentRole):
            return QtCore.Qt.AlignCenter;

    def setData(self, index, value, role = QtCore.Qt.EditRole):

        change = False

        if role == QtCore.Qt.EditRole:

            row = index.row()
            column = index.column()

            value = value

            if (column == 1) or (column == 4):
                try:
                    str(value)
                    change = True
                except:
                    pm.warning("Not a valid name")
                    change = False
            elif (column == 2):
                try:
                    int(value)
                    change = True
                except:
                    pm.warning("Not a valid frame")
                    change = False
            elif (column == 3):
                try:
                    int(value)
                    change = True
                except:
                    pm.warning("Not a valid frame")
                    change = False

            elif (column == 5):
                try:
                    int(value)
                    change = True
                except:
                    pm.warning("Not a valid version number")
                    change = False

            if change:
                self.cycles[row][column] = value
                self.dataChanged.emit(row, column)
                return True

            return False            

    def headerData(self, section, orientation, role):

        if role == QtCore.Qt.DisplayRole:

            if orientation == QtCore.Qt.Horizontal:
                return self.headers[section]

    def insertRows(self, position, rows, values = [] , parent = QtCore.QModelIndex()):


        self.beginInsertRows(parent, position, position+rows-1)

        self.cycles.insert(position, values)

        self.endInsertRows()

        self.getData()



    def getData(self):

        rows = self.rowCount(1)

        data = []

        for row in range(rows):
            array = []
            for column in range (6):
                index = self.index(row, column)
                info = index.data()

                if type(info) == bool:
                    array.append(info)

                elif type(info) == QItemDelegate:
                    val_is_checked = index.data(QtCore.Qt.CheckStateRole) != QtCore.Qt.Unchecked
                    array.append(val_is_checked)

                else:
                    info = str(info)
                    array.append(info)

            array.append("del")
            data.append(array)

        dic = {}
        for item in data:
            dic[item[1]]=item

        for key in dic:
            print key, dic[key]


        #this from pickle
        #newData = data2String(dic)
        # and this is what I wanna save inside Maya
        #pm.setAttr("cycleAnimationListNode.cycles", newData)

The Delegate is in Edit 1, above.

Then I guess you still need the cycles and headers to start this model:

headers = ["Select", "  Cycle Name  ", " Start ", "  End  ", "Info", "Version", " Del "]

cycles = [[False,"idle","1","70","cool information","0", "deleteBtnPlaceHolder"]]

I hope this does it.

Thank you.

##### EDIT 3

I have this custom method in the model:

def getData(self):
        rows = self.rowCount(1)
        data = []
        for row in range(rows):
            array = []
            for column in range (6):
                index = self.index(row, column)
                info = index.data()
                array.append(info)              
            data.append(array)

        dic = {}
        for item in data:
            dic[item[1]]=item

        print ""
        print "data:"
        print ''
        for key in dic:
            print(key, dic[key])

I use it to transforme the model into a dictionary, so i can serialize it and store as an string attribute of a Node inside Autodesk Maya. It runs alright but the information it gets from the first column is always None. Do i have to retrieve it differently?


Solution

  • It is not necessary to use a delegate if you want to show a QCheckBox, you only need to enable the flag Qt::ItemIsUserCheckable properly, in addition you must save the information through setData(), since in your code that information is lost since the stored in the following section shows the model with these modifications:

    Update:

    Previously disable the return of the data method for the role Qt::DisplayRole for the first column since it placed the text in the table, which for me was unnecessary, now I have enabled it but so that the text is not displayed I have placed a delegate that removes the text

    from PySide2.QtWidgets import *
    from PySide2.QtCore import *
    
    class Model(QAbstractTableModel):
        def __init__(self, cycles = [[]], headers = [], parent = None):
            QAbstractTableModel.__init__(self, parent)
            self.cycles = cycles
            self.headers = headers
            self.values_checked = []
    
        def rowCount(self, parent):
            return len(self.cycles)
    
        def columnCount(self, parent):
            return len(self.cycles[0])
    
        def flags(self, index):
            fl = Qt.ItemIsEnabled | Qt.ItemIsSelectable 
            if index.column() == 0:
                fl |= Qt.ItemIsUserCheckable
            else:
                fl |= Qt.ItemIsEditable
            return fl
    
        def data(self, index, role):
            if not index.isValid():
                return 
            row = index.row()
            column = index.column()
    
            if role == Qt.DisplayRole:
                value = self.cycles[row][column]
                return value
    
            elif role == Qt.TextAlignmentRole:
                return Qt.AlignCenter;
    
            elif role == Qt.CheckStateRole and column==0:
                return Qt.Checked if self.cycles[row][column] else Qt.Unchecked
    
    
        def setData(self, index, value, role = Qt.EditRole):
            change = False
            row = index.row()
            column = index.column()
    
            if role == Qt.CheckStateRole:
                value =  value != Qt.Unchecked
                change = True
            if role == Qt.EditRole:
                if (column == 1) or (column == 4):
                    try:
                        str(value)
                        change = True
                    except:
                        pm.warning("Not a valid name")
                        change = False
                elif (column == 2):
                    try:
                        int(value)
                        change = True
                    except:
                        pm.warning("Not a valid frame")
                        change = False
                elif (column == 3):
                    try:
                        int(value)
                        change = True
                    except:
                        pm.warning("Not a valid frame")
                        change = False
    
                elif (column == 5):
                    try:
                        int(value)
                        change = True
                    except:
                        pm.warning("Not a valid version number")
                        change = False
            if change:
                self.cycles[row][column] = value
                self.dataChanged.emit(row, column)
                return True
            return False            
    
        def headerData(self, section, orientation, role):
            if role == Qt.DisplayRole:
                if orientation == Qt.Horizontal:
                    return self.headers[section]
    
        def insertRows(self, position, rows, values = [] , parent =QModelIndex()):
            self.beginInsertRows(parent, position, position+rows-1)
            self.cycles.insert(position, values)
            self.endInsertRows()
            self.getData()
    
        def roleNames(self):
            roles = QAbstractTableModel.roleNames(self)
            roles["Checked"] = Qt.CheckStateRole
            return roles
    
    
        def getData(self):
                rows = self.rowCount(1)
                data = []
                for row in range(rows):
                    array = []
                    for column in range (6):
                        index = self.index(row, column)
                        info = index.data()
                        array.append(info)              
                    data.append(array)
    
                dic = {}
                for item in data:
                    dic[item[1]]=item
    
                print("")
                print("data:")
                print('')
                for key in dic:
                    print(key, dic[key])
    
    
    class EmptyDelegate(QStyledItemDelegate):
        def paint(self, painter, option, index):
            opt = QStyleOptionViewItem(option)
            self.initStyleOption(opt, index)
            opt.text = ""
            QApplication.style().drawControl(QStyle.CE_ItemViewItem, opt, painter)
    
    
    if __name__ == '__main__':
        import sys
    
        app = QApplication(sys.argv)
        w = QTableView()
        w.setItemDelegateForColumn(0, EmptyDelegate(w))
        headers = ["Select", "  Cycle Name  ", " Start ", "  End  ", "Info", "Version", " Del "]
        cycles = [[True,"idle","1","70","cool information","0", "deleteBtnPlaceHolder"]]
        model = Model(cycles, headers)
        w.setModel(model)
        w.show()
        sys.exit(app.exec_())