Search code examples
pythonpyqtpyqt5qtablewidgetqbuttongroup

PyQt5 QTableWidget cell select, save and copy


As extension to my previous question I add a copy_button. When user enter input to cells, and user would like select a row, and click on copy_button to copy contents and add an new row below the selected row with same content. The code below supposes to do the job it adds a new row as user wants, expect from its contents is not copying with? I tried to print to see the problem. I add Qtablewidget items to empty list, then the list is printed before for loop, sees it has content added to it. as it should, but a print of items after setItem method, returns empty. It is the same procedure as serializing of the table. I want also to serialize whole table and paste it back.

List print before and after for loop.

enter image description here

My code:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets, Qt


class loadtable(QtWidgets.QTableWidget):
def __init__(self, parent=None):
    super(loadtable, self).__init__(1, 5,parent)



    self.setColumnCount(5)
    self.setRowCount(1)
    self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))   
    headertitle = ("A","B","C","D","E")
    QtWidgets.QTableWidgetItem(headertitle[i]))
    self.setHorizontalHeaderLabels(headertitle)
    self.verticalHeader().setVisible(False)
    self.horizontalHeader().setHighlightSections(False)
    self.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
    self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
    self.setColumnWidth(0, 130)
    combox_lay = QtWidgets.QComboBox(self)
    combox_lay.addItems(["I","II"])
    self.setCellWidget(0, 4, combox_lay)


    self.cellChanged.connect(self._cellclicked)


def _cellclicked(self):
    self.value = self.currentItem()
    self.value.setTextAlignment(Qt.AlignCenter)

@QtCore.pyqtSlot()  
def _addrow(self):
    rowcount = self.rowCount()
    print(rowcount)
    self.setRowCount(rowcount+1)
    combox_add = QtWidgets.QComboBox(self)
    combox_add.addItems(["I","II"])
    self.setCellWidget(rowcount, 4, combox_add)

@QtCore.pyqtSlot()
def _removerow(self):
    if self.rowCount() > 0:
        self.removeRow(self.rowCount()-1)

@QtCore.pyqtSlot()
def _cellselected(self):
    r = self.currentRow()
    c = self.columnCount()
    cell = []
    for i in range(c):
        if i == c-1:
            it = self.cellWidget(r , i)
        else:
            it = self.item(r , i)
        cell.append(it)
    self.setcopy(cell,r,c)

def setcopy(self,cell,r,c):
    self.insertRow(r+1)
    print(cell)
    for j in range(c):
        if j < c-1:
            it = self.setItem(r+1, j,cell[j])
        else:
            it = self.setCellWidget(r+1, j, cell[j])
        print(it)
    return it    

class thirdtabloads(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(thirdtabloads, self).__init__(parent)      
        table = loadtable()


    button_layout = QtWidgets.QVBoxLayout()
    add_button = QtWidgets.QPushButton("Add")
    add_button.clicked.connect(table._addrow)
    delete_button = QtWidgets.QPushButton("Delete")
    delete_button.clicked.connect(table._removerow)
    copy_button = QtWidgets.QPushButton("Copy")
    copy_button.clicked.connect(table._cellselected)

    button_layout = QtWidgets.QVBoxLayout()
    button_layout.addWidget(add_button, alignment=QtCore.Qt.AlignBottom)
    button_layout.addWidget(delete_button, alignment=QtCore.Qt.AlignTop)
    button_layout.addWidget(copy_button, alignment=QtCore.Qt.AlignTop)


    tablehbox = QtWidgets.QHBoxLayout()
    tablehbox.setContentsMargins(10,10,10,10)
    tablehbox.addWidget(table)

    grid = QtWidgets.QGridLayout(self)
    grid.addLayout(button_layout, 0, 1)
    grid.addLayout(tablehbox, 0, 0)        



if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = thirdtabloads()
    w.show()
    sys.exit(app.exec_())

Solution

  • First of all, the setItem() method does not return anything since it is a setter, so if you want to find out if the item exists, you must use the item() method that is a getter.

    Going to the problem, you have noticed that you must copy 2 elements: the QTableWidgetItems and the widgets that you set with setCellWidget(). If you want to copy the QTableWidgetItem you must use its clone() method, and in the case of the widget there is no method to do it so you will have to create a method that copies the necessary, in my solution I show how to copy the items from a QComboBox, if you have other widgets you will have to implement more code. Finally always check, for example currentRow can be -1 when nothing has been selected.

    import sys
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    # copy qwidgets
    def copy_widget(w):
        if isinstance(w, QtWidgets.QWidget):
            new_w = type(w)()
            if isinstance(w, QtWidgets.QComboBox):
                vals = [w.itemText(ix) for ix in range(w.count())]
                new_w.addItems(vals)
    
            # if instance(w, QtWidgets.AnotherWidget):
            #     copy values
            return new_w
    
    class LoadTable(QtWidgets.QTableWidget):
        def __init__(self, parent=None):
            super(LoadTable, self).__init__(1, 5, parent)
            self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))   
            headertitle = ("A","B","C","D","E")
            self.setHorizontalHeaderLabels(headertitle)
            self.verticalHeader().hide()
            self.horizontalHeader().setHighlightSections(False)
            self.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
    
            self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
            self.setColumnWidth(0, 130)
    
            combox_lay = QtWidgets.QComboBox(self)
            combox_lay.addItems(["I","II"])
            self.setCellWidget(0, 4, combox_lay)
    
            self.cellChanged.connect(self._cellclicked)
    
        @QtCore.pyqtSlot(int, int)
        def _cellclicked(self, r, c):
            it = self.item(r, c)
            it.setTextAlignment(QtCore.Qt.AlignCenter)        
    
        @QtCore.pyqtSlot()
        def _addrow(self):
            rowcount = self.rowCount()
            self.insertRow(rowcount)
            combox_add = QtWidgets.QComboBox(self)
            combox_add.addItems(["I","II"])
            self.setCellWidget(rowcount, 4, combox_add)
    
        @QtCore.pyqtSlot()
        def _removerow(self):
            if self.rowCount() > 0:
                self.removeRow(self.rowCount()-1)
    
        @QtCore.pyqtSlot()
        def _copyrow(self):
            r = self.currentRow()
            if 0 <= r < self.rowCount():
                cells = {"items": [], "widgets": []}
                for i in range(self.columnCount()):
                    it = self.item(r, i)
                    if it:
                        cells["items"].append((i, it.clone()))
                    w = self.cellWidget(r, i)
                    if w:
                        cells["widgets"].append((i, copy_widget(w)))
                self.copy(cells, r+1)
    
        def copy(self, cells, r):
            self.insertRow(r)
            for i, it in cells["items"]:
                self.setItem(r, i, it)
            for i, w in cells["widgets"]:
                self.setCellWidget(r, i, w)
    
    
    class ThirdTabLoads(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(ThirdTabLoads, self).__init__(parent)    
    
            table = LoadTable()
    
            add_button = QtWidgets.QPushButton("Add")
            add_button.clicked.connect(table._addrow)
    
            delete_button = QtWidgets.QPushButton("Delete")
            delete_button.clicked.connect(table._removerow)
    
            copy_button = QtWidgets.QPushButton("Copy")
            copy_button.clicked.connect(table._copyrow)
    
            button_layout = QtWidgets.QVBoxLayout()
            button_layout.addWidget(add_button, alignment=QtCore.Qt.AlignBottom)
            button_layout.addWidget(delete_button, alignment=QtCore.Qt.AlignTop)
            button_layout.addWidget(copy_button, alignment=QtCore.Qt.AlignTop)
    
            tablehbox = QtWidgets.QHBoxLayout()
            tablehbox.setContentsMargins(10, 10, 10, 10)
            tablehbox.addWidget(table)
    
            grid = QtWidgets.QGridLayout(self)
            grid.addLayout(button_layout, 0, 1)
            grid.addLayout(tablehbox, 0, 0)        
    
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        w = ThirdTabLoads()
        w.show()
        sys.exit(app.exec_())