0

Using the code from Here and There, I made a GUI presenting my project on a smaller scale.

I have a qTableView,containing a large array of rows, and on each rows I have a delete and an edit button. On click, it should either edit or delete the current row. When using only the first source, it works exactly as intended, but as soon as I handle the click outside of the buttons class, it stops working.

Everytime I try to edit or delete, the button that either self.sender() or QtWidgets.qApp.focusWidget() sees as the sender has the coordinates [0,0], even if it's absolutely not it's coordinates.

I have searched on various websites and can't find this precise question. What am I doing wrong, and what could I do to solve this problem?

My code :

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog, QMessageBox

class EditButtonsWidget(QtWidgets.QWidget):
    # Credit to : https://stackoverflow.com/a/29764914/13812144

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

        # add your buttons
        layout = QtWidgets.QHBoxLayout()

        # adjust spacings to your needs
        layout.setContentsMargins(0,0,0,0)
        layout.setSpacing(0)
        
        self.editButton = QtWidgets.QPushButton('edit')
        self.deleteButton = QtWidgets.QPushButton('del')
        
        self.buttonRow = 0
        # add your buttons
        layout.addWidget(self.editButton)
        layout.addWidget(self.deleteButton)

        self.setLayout(layout)

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        QtWidgets.QMainWindow.__init__(self,parent)
        self.table = QtWidgets.QTableWidget()
        self.table.setColumnCount(3)
        self.setCentralWidget(self.table)
        data1 = ['row1','row2','row3','row4']
        data2 = ['1','2.0','3.00000001','3.9999999']

        self.table.setRowCount(4)

        for index in range(4):
            item1 = QtWidgets.QTableWidgetItem(data1[index])
            self.table.setItem(index,0,item1)
            item2 = QtWidgets.QTableWidgetItem(data2[index])
            self.table.setItem(index,1,item2)
            self.btn_sell = EditButtonsWidget()
            self.btn_sell.editButton.clicked.connect(self.handleButtonClicked)
            self.table.setCellWidget(index,2,self.btn_sell)

    def handleButtonClicked(self):
        #button = QtWidgets.qApp.focusWidget()
        button = self.sender()
        index = self.table.indexAt(button.pos())
        if index.isValid():
            print(index.row(), index.column())
            
if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = MainWindow()
    MainWindow.show()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241

2 Answers2

0

The position must be of the widget that is set in the QTableWidget, not of one of its children.

In this case it is better to consider the EditButtonsWidget as a black box and expose the clicked signals of the buttons as new signals so that the sender is EditButtonsWidget and no longer the buttons:

class EditButtonsWidget(QtWidgets.QWidget):
    edit_clicked = QtCore.pyqtSignal()
    delete_clicked = QtCore.pyqtSignal()

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

        # add your buttons
        layout = QtWidgets.QHBoxLayout(self)

        # adjust spacings to your needs
        layout.setContentsMargins(0,0,0,0)
        layout.setSpacing(0)
        
        self.editButton = QtWidgets.QPushButton('edit')
        self.deleteButton = QtWidgets.QPushButton('del')
        
        # add your buttons
        layout.addWidget(self.editButton)
        layout.addWidget(self.deleteButton)

        self.editButton.clicked.connect(self.edit_clicked)
        self.deleteButton.clicked.connect(self.delete_clicked)
for index in range(4):
    item1 = QtWidgets.QTableWidgetItem(data1[index])
    self.table.setItem(index,0,item1)
    item2 = QtWidgets.QTableWidgetItem(data2[index])
    self.table.setItem(index,1,item2)
    self.btn_sell = EditButtonsWidget()
    self.btn_sell.edit_clicked.connect(self.handleButtonClicked) # <---
    self.table.setCellWidget(index,2,self.btn_sell)
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Hey, I apologize I lost my credentials, just found them. This answered saved me, thanks a lot, your very quick response saved me a lot of time and it really helped ! – DrankRockNein Apr 26 '22 at 14:44
0

Widget positions always use the parent's coordinate system as a reference.

In your case, the button is a child of EditButtonsWidget, and since it's also the first widget and the layout has no margins, the button is placed at 0, 0 in that coordinate reference system.

A theoretical solution to your problem would be to map the widget position to the actual widget you need a reference for, which is the viewport of the scroll area (the table):

    def handleButtonClicked(self):
        button = self.sender()
        viewportPosition = button.mapTo(self.table.viewport(), QtCore.QPoint())
        index = self.table.indexAt(viewportPosition)
        if index.isValid():
            print(index.row(), index.column())

The mapping is done using an empty QPoint, since the top-left corner of a widget is always 0, 0 in local coordinates.

While this works, it's not the most logic nor elegant or safest way to do so, as you should reference the actual index instaed.

A better solution would be to map the table index, use that as argument of the widget constructor, and send that index for a custom signal.

class EditButtonsWidget(QtWidgets.QWidget):
    editClicked = QtCore.pyqtSignal(object)
    def __init__(self, index):
        super(EditButtonsWidget,self).__init__()
        self.index = index

        # ...

        self.editButton.clicked.connect(lambda: self.editClicked.emit(index))


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        # ...
        for index in range(4):
            # ...
            persistenIndex = QtCore.QPersistentModelIndex(
                self.table.indexFromItem(item2))
            self.btn_sell = EditButtonsWidget(persistenIndex)
            self.btn_sell.editClicked.connect(self.handleButtonClicked)
            self.table.setCellWidget(index,2,self.btn_sell)

    def handleButtonClicked(self, index):
        if index.isValid():
            print(index.row(), index.column())

Note that I used a QPersistentModelIndex, which ensures that the model index coordinates are always consistent even if the model changes (by deleting/inserting items or moving them).
Also note that you cannot directly use a QPersistentModelIndex for most functions that take a normal QModelIndex as parameter; in case you need that, you can recreate a QModelIndex like this:

    modelIndex = self.table.model().index(
        persistentIndex.row(), persistentIndex.column())
musicamante
  • 41,230
  • 6
  • 33
  • 58