Search code examples
pythonpyqtpyqt5qtablewidget

'Table' object has no attribute 'tableWidget' PyQt5


I'm trying to change the QTableWidget mouse pressed event method to do something different to a cell item depending if the user right-clicks or left-clicks. I am getting the following error when I left click a cell in self.tableWidget:

AttributeError: 'Table' object has no attribute 'tableWidget' (line 114 [commented below])

Here is my code below:

from PyQt5 import QtCore, QtGui, QtWidgets
from WordSearch import WordSearch as ws

ws = ws()


class Table(QtWidgets.QTableWidget): #overwriting QTableWidgets to allow for right click events
    def __init__(self, *args, **kwargs):
        QtWidgets.QTableWidget.__init__(self, *args, **kwargs)

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            item = self.itemAt(event.pos())
            print (item.row(), item.column()) #FOR DEBUGGUNG
            Ui_MainWindow.leftClickLetter(self, item.row(), item.column())

        elif event.button() == QtCore.Qt.RightButton:
            print("RIGHT CLICK!") #FOR DEBUGGING
        QtWidgets.QTableWidget.mousePressEvent(self, event)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
         .....

         self.generate.clicked.connect(self.generateTable)

         .....

    def generateTable(self):

        self.tableWidget = Table(self.centralwidget)
        self.tableWidget.setObjectName("tableWidget")

        self.tableWidget.setColumnCount(int(self.size.text()))
        self.tableWidget.setRowCount(int(self.size.text()))

        self.tableWidget.show()

    def leftClickLetter(self, row, col): 
        item = self.tableWidget.item(row, col) #Line 114
        item.setBackground(QtGui.QColor(216,191,216))
        if (row, col) not in self.coordsToCheck:
            self.coordsToCheck.append((row,col))

        self.tableWidget.clearSelection()

I cut out some code that I didn't think was relevant but let me know if I should add it. generateTable() is always called before leftClickLetter() so self.tableWidget should be a correct variable name of type Table.


Solution

  • Explanation:

    To understand the problem, the following example should be analyzed:

    class A:
        def foo(self, arg1, arg2):
            print(self)
    
    
    class B:
        pass
    
    
    b = B()
    A.foo(b, "2", 3)
    

    Output:

    <__main__.B object at 0x7fb364501070>
    

    As you can see, the object "self" does not refer to the "A" instance but to the first parameter passed to the method, and why does that problem happen? Well, because you are trying to use a method through the class but you must use the instance such as:

    b = B()
    a = A()
    a.foo(b, "2")
    

    Output:

    <__main__.A object at 0x7fd3139a4f40>
    

    Where we see that the "self" refers to the object of class "A".

    The above is an error caused by little knowledge of OOP so my recommendation is to check your notes on that topic.

    Solution:

    Note: First of all, it is not recommended to modify the class generated by Qt Designer, so you must restore the class so that my solution can be applied.

    In your case it is not necessary to override any method since there are several signals that notify you if an item has been clicked:

    • itemClicked

      from PyQt5 import QtCore, QtGui, QtWidgets
      
      
      class Ui_MainWindow(object):
          def setupUi(self, MainWindow):
              # ..
      
      
      class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
          def __init__(self, parent=None):
              super().__init__(parent)
              self.setupUi(self)
              self.generate.clicked.connect(self.generate_table)
              self.coordsToCheck = []
              self.tables = []
      
          def generate_table(self):
      
              try:
                  size = int(self.size.text())
              except ValueError as e:
                  print("error", e)
              else:
                  tableWidget = QtWidgets.QTableWidget()
                  tableWidget.setColumnCount(size)
                  tableWidget.setRowCount(size)
                  tableWidget.show()
                  tableWidget.itemClicked.connect(self.on_item_clicked)
                  self.tables.append(tableWidget)
      
          @QtCore.pyqtSlot("QTableWidgetItem*")
          def on_item_clicked(self, it):
              row, col = it.row(), it.column()
              it.setBackground(QtGui.QColor(216, 191, 216))
              if (row, col) not in self.coordsToCheck:
                  self.coordsToCheck.append((row, col))
              it.tableWidget().clearSelection()
      
      
      if __name__ == "__main__":
          import sys
      
          app = QtWidgets.QApplication(sys.argv)
          w = MainWindow()
          w.show()
          sys.exit(app.exec_())
      
    • clicked

      from PyQt5 import QtCore, QtGui, QtWidgets
      
      
      class Ui_MainWindow(object):
          def setupUi(self, MainWindow):
              # ...
      
      
      class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
          def __init__(self, parent=None):
              super().__init__(parent)
              self.setupUi(self)
              self.generate.clicked.connect(self.generate_table)
              self.coordsToCheck = []
              self.tables = []
      
          def generate_table(self):
      
              try:
                  size = int(self.size.text())
              except ValueError as e:
                  print("error", e)
              else:
                  tableWidget = QtWidgets.QTableWidget()
                  tableWidget.setColumnCount(size)
                  tableWidget.setRowCount(size)
                  tableWidget.show()
                  tableWidget.clicked.connect(self.on_clicked)
                  self.tables.append(tableWidget)
      
          @QtCore.pyqtSlot(QtCore.QModelIndex)
          def on_clicked(self, index):
              tableWidget = self.sender()
              row, col = index.row(), index.column()
              tableWidget.model().setData(
                  index, QtGui.QColor(216, 191, 216), QtCore.Qt.BackgroundRole
              )
              if (row, col) not in self.coordsToCheck:
                  self.coordsToCheck.append((row, col))
              tableWidget.clearSelection()
      
      
      if __name__ == "__main__":
          import sys
      
          app = QtWidgets.QApplication(sys.argv)
          w = MainWindow()
          w.show()
          sys.exit(app.exec_())
      

    Update:

    If you want to detect the right clicked then you must create a signal instead of invoking a method directly:

    class TableWidget(QtWidgets.QTableWidget):
        rightClicked = QtCore.pyqtSignal(QtCore.QModelIndex)
    
        def mouseReleaseEvent(self, event):
            super().mouseReleaseEvent(event)
            if event.button() == QtCore.Qt.RightButton:
                self.rightClicked.emit(self.indexAt(event.pos()))
    
    def generate_table(self):
        try:
            size = int(self.size.text())
        except ValueError as e:
            print("error", e)
        else:
            tableWidget = TableWidget(size, size)
            tableWidget.show()
            tableWidget.clicked.connect(self.on_left_clicked)
            tableWidget.rightClicked.connect(self.on_right_clicked)
            self.tables.append(tableWidget)
    
    @QtCore.pyqtSlot(QtCore.QModelIndex)
    def on_left_clicked(self, index):
        self.change_color(self.sender(), index, QtGui.QColor(216, 191, 216))
    
    @QtCore.pyqtSlot(QtCore.QModelIndex)
    def on_right_clicked(self, index):
        self.change_color(self.sender(), index, None)
    
    def change_color(self, view, index, color):
        if not isinstance(view, QtWidgets.QAbstractItemView):
            return
        model = view.model()
        if model is None:
            return
        model.setData(index, color, QtCore.Qt.BackgroundRole)
        view.clearSelection()