0

After creating a custom QLabel class that opens a QInputDiaglog popup when double clicked to change the value displayed in the cell, I realized that a QTableWidgetItem can be double clicked to edit its value. I prefer this over the popup from a user perspective. I need to add a new property "init_val" and override a method (not sure which one, maybe write()) to change the background color and insert an entry into the changes_dict property of the main window when the edit is completed.

I looked through the documentation and used "help(QTableWidgetItem)" in console, but still could not figure it out. How can I figure this out on my own in the future?

You can ignore most of the code since it causes a lot of errors. I just wanted to give it a shot before asking for help.

from PyQt6.QtWidgets import QApplication, QWidget, QTableWidget, QTableWidgetItem, QHBoxLayout, QVBoxLayout, QHeaderView, QPushButton, QScrollArea, QLabel, QMainWindow, QInputDialog, QLineEdit, QFormLayout, QDialogButtonBox, QDialog

from PyQt6 import QtCore import sys

class MyQTableWidgetItem(QTableWidgetItem):
    clicked = QtCore.pyqtSignal()

    def __init__(self, text, parent=None):
        super().__init__(text)

        self.init_val = text
        
    def write(self, out: QtCore.QDataStream) -> None:
        print(out, type(out))
        return super().write(out)


    def mouseDoubleClickEvent(self, event):
        print('twas double clicked')
        self.clicked.emit()


class Window(QWidget):    
    def __init__(self):
        super().__init__()
    
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        self.column_names = ['1', '2', '3']
        self.load_table()

        self.btn = QPushButton('Insert Row')
        self.layout.addWidget(self.btn)

        self.btn.clicked.connect(lambda: self.onClickMe())

        self.show()

    def load_table(self):
        if not hasattr(self, 'table'):
            self.table = QTableWidget()
            self.table.verticalHeader().setVisible(False)
            self.layout.addWidget(self.table)

        data = [
            [1, 1, 1],
            [2, 2, 2],
            [3, 3, 3]
        ]

        rows = len(data)
        cols = len(data[0])
        self.table.setRowCount(rows)
        self.table.setColumnCount(cols)
        self.table.setHorizontalHeaderLabels(self.column_names)

        for i, lis in enumerate(data):
            for j, item in enumerate(lis):
                print(i, lis, j, item)
                item_widget = MyQTableWidgetItem(str(item))
                self.table.setItem(i, j, QTableWidgetItem(str(item))) 

                item_widget.clicked.connect(lambda item_widget=item_widget: self.on_double_click(item_widget, i, j))
    
    def on_double_click(self, item_widget, r, c):
        # previously I called a QInputDialog in this function, which automatically returned the text and 
        # True if the Ok button was clicked. I checked new text vs item_widget.init_val and if they were 
        # different changed background color and added change to self.changes_dict.
        # 
        # I don't know how I would pull the new text once the QTableWidgetItem value is changed.
        text = ...
        key = ...
        self.changes_dict[key] = text
        ...

app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec())

With this code I get the following error:

File "C:\Users\ngreen\Desktop\Projects\wip_inv_adjustment_app\testing_qtablewidgetitem.py", line 65, in load_table item_widget.clicked.connect(lambda item_widget=item_widget: self.on_double_click(item_widget, i, j)) TypeError: MyQTableWidgetItem cannot be converted to PyQt6.QtCore.QObject

Previously I used self.table.setCellWidget() with a custom class inheriting QLabel instead of self.table.setItem() with a custom class inheriting QTableWidget, so this may be part of the problem.

Using @musicmante's suggestion I tried to override setData() as follows:

def setData(self, role: int, value: typing.Any) -> None:
    if value != self.init_val:
        self.setBackground(QtGui.QColor(127,255,212))
    else:
        self.setBackground(QtGui.QColor(255,255,255))

    return super().setData(role, value)

but got the following error. Perhaps I cannot change background color in the middle of the setData method?

Error in sys.excepthook:

Original exception was: Unhandled Python exception

4
  • Override setData(). Also, table widget items are not QObjects, so they don't support signals, if you need to call a function when an item is changed, connect to the table's itemChanged signal. Finally, items are not even widgets, so the mouseDoubleClickEvent() function is useless. Commented Nov 15, 2022 at 15:42
  • Thanks. That explains why switching from an inheriting QLabel to an inheriting QTableWidgetItem caused so many issues. Commented Nov 15, 2022 at 16:06
  • @musicamante I tried overriding setData(), but got an error. I added more above. Commented Nov 15, 2022 at 16:22
  • setBackground() calls setData(), and you must always check for the role anyway. Add if role in (Qt.DisplayRole, Qt.EditRole): before checking the value and setting anything, otherwise you'll get recursion. Also take your time to read more about Qt model/view programming in order to understand these concepts. Commented Nov 15, 2022 at 16:47

1 Answer 1

0

Using the table's itemChanged signal as @musicamante suggested I got it to work. This is so much easier than the way I tried before.

from PyQt6.QtWidgets import QApplication, QWidget, QTableWidget, QTableWidgetItem, QHBoxLayout, QVBoxLayout, QHeaderView, QPushButton, QScrollArea, QLabel, QMainWindow, QInputDialog, QLineEdit, QFormLayout, QDialogButtonBox, QDialog
from PyQt6 import QtCore, QtGui
import sys
import typing


class MyQTableWidgetItem(QTableWidgetItem):
    def __init__(self, text, parent=None):
        super().__init__(text)
        self.init_val = text


class Window(QWidget):    
    def __init__(self):
        super().__init__()
    
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        self.column_names = ['1', '2', '3']
        self.load_table()

        self.show()

    def load_table(self):
        if not hasattr(self, 'table'):
            self.table = QTableWidget()
            self.table.verticalHeader().setVisible(False)
            self.layout.addWidget(self.table)

        data = [
            [1, 1, 1],
            [2, 2, 2],
            [3, 3, 3]
        ]

        rows = len(data)
        cols = len(data[0])
        self.table.setRowCount(rows)
        self.table.setColumnCount(cols)
        self.table.setHorizontalHeaderLabels(self.column_names)

        for i, lis in enumerate(data):
            for j, item in enumerate(lis):
                print(i, lis, j, item)
                item_widget = MyQTableWidgetItem(str(item))
                self.table.setItem(i, j, MyQTableWidgetItem(str(item))) 

        self.table.itemChanged.connect(self.change_background)

    def change_background(self, item):
        if item.text != item.init_val:
            item.setBackground(QtGui.QColor(127,255,212))
        else:
            item.setBackground(QtGui.QColor(255,255,255))


app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec())
Sign up to request clarification or add additional context in comments.

1 Comment

Simple and easy

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.