Remove user selected rows from a QTableWidget



  • Hi, I am using PyQt 4.4.4
    I have a widget that is composed of a QTableWidget and a QPushButton widget. I want a user to be able to click on a row (I only have one column and a variable number of rows) and click the QPushButton to delete that row from the QTable. You'd think this would be a pretty simple task that lots of people would want, but I can't seem to find a function that can be accessed by QTableWidget to do this. I've found selectedRows() in QItemSelectionModel, which seems to be associated with QModelIndexList (which, itself, doesn't show up on the list of classes, only QModelIndex). How do I get a QTableWidget to receive a selection and act on a signal from a QPushButton?

    Here is my code. It does come up with the window, but when I select a row and hit "Delete", I get the error "TypeError: remove(takes exactly 2 arguments (0 given)"

    @
    class questionGUI(QtGui.QWidget):
    #This class is the window of the gui. The methods within this class
    #are the widgets that make up the different user input regions of the gui
    grid = QtGui.QGridLayout()
    grid.setSpacing(10)

    row = 3
    
    def __init__(self):
        super(questionGUI,self).__init__()
    
        self.widgetFive()
        #self.widgetFour()
        self.widgetThree()
        self.widgetTwo()
        self.widgetOne()
    
    def widgetOne(self):
        qTable = QtGui.QTableWidget(self.row,1)
        qTableName = QtGui.QLabel('Available Questions')
        entries = ['[Pick Image] <Default>','[Slider Question] <Default>', '[Comment Box] <Default>']
    
        addrow = 0
        for i in entries:
            item = QtGui.QTableWidgetItem(i)
            qTable.setItem(addrow,0,item)
            addrow += 1
        
        selected = qTable.selectedItems()
        print selected
    
        def remove(selected,self):
            #hopefully this will let the Delete button remove the selected table row
            qTable.removeRow(selected)
    
        deleteButton = QtGui.QPushButton('Delete',self)
        deleteButton.connect(deleteButton,QtCore.SIGNAL('clicked()'),remove)
    
        qTable.setColumnWidth(0,300)
    
        self.grid.addWidget(qTableName,1,0)
        self.grid.addWidget(qTable,2,0, 1, 2)
        self.grid.addWidget(deleteButton,3,0)
    

    ...
    @



  • If you debug your code, you'll see what you are doing wrong.
    EDIT: Missed the part in your post where you actually see what's going wrong. You get the error message because you define your remove function with two arguments (def remove(selected,self)) but you are giving none in the connect line. But even if you had fixed that, you would get another error stating that removeRow() accepts an int and not a list (selectedItems).

    Anyway, try to make these changes and see if it works:
    @
    selected = qTable.currentRow()
    def remove(self):
    qTable.removeRow(selected)
    ...
    deleteButton.connect(deleteButton,QtCore.SIGNAL('clicked()'),remove(self))
    @



  • That doesn't work. In fact, I already tried it while I was debugging my code for several hours before being desperate enough to ask for help.

    Your edits give me the error: "TypeError: argument 3 of QObject.connect() has an invalid type"

    I've seen that before. I removed "self" from the call of remove, since that just means that the function can access the variables and methods of the class and isn't actually an argument. That brought me right back to where I was before.

    currentRow doesn't react to a user input selection, which I learned during my debugging. That's why I didn't use it.



  • I quickly copy-pasted your code commenting the rest of your widgets and gave it a try with my edits (using PySide). I only see the widget with the delete button but when I click on it no errors mentioned.

    If possible, post the rest of your code and your imports so I can see the table widget.



  • Here's the whole code. The widget I'm having trouble with should run independent of the others for now, which is why I didn't include the later ones. Lines 29 - 34 have change, since I've continued to debug my code, of course.

    I need to get the Table to recognize a signal. The "State" protected type on line 29 is from QAbstractItemView. The argument is set to 2 because that indicates that the user is selecting items. I am very new to PyQt, so it is very possible that I am just using this incorrectly, but the description in the documentation (http://doc.qt.digia.com/4.4/qabstractitemview.html#State-enum) suggests that it is at least part of what I need.

    I am getting the error: "insufficient number of arguments to QItemSelectionModel()". I have been reading the documentation about this and will continue to read it long after I post, but I have yet to be able to understand what to put in those parentheses. I believe it is probably a constant or integer which indicates the Selection Mode, but I can't find a list or a table that shows the available built-in modes.

    @
    class questionGUI(QtGui.QWidget):
    #This class is the window of the gui. The methods within this class
    #are the widgets that make up the different user input regions of the gui
    grid = QtGui.QGridLayout()
    grid.setSpacing(10)

    row = 3
    
    def __init__(self):
        super(questionGUI,self).__init__()
    
        self.widgetFive()
        #self.widgetFour()
        self.widgetThree()
        self.widgetTwo()
        self.widgetOne()
    
    def widgetOne(self):
        qTable = QtGui.QTableWidget(self.row,1)
        qTableName = QtGui.QLabel('Available Questions')
        entries = ['[Pick Image] <Default>','[Slider Question] <Default>', '[Comment Box] <Default>']
    
        addrow = 0
        for i in entries:
            item = QtGui.QTableWidgetItem(i)
            qTable.setItem(addrow,0,item)
            addrow += 1
        
        qTable.State(2)
        selection = QtGui.QItemSelectionModel()
    
        select = selection.currentRowChanged()
    
        selected = qTable.selectedItems()
    
        def remove(self):
            #hopefully this will let the Delete button remove the selected table row
            qTable.removeRow(selected)
    
        deleteButton = QtGui.QPushButton('Delete',self)
        deleteButton.connect(deleteButton,QtCore.SIGNAL('clicked()'),remove)
    
        qTable.setColumnWidth(0,300)
    
        self.grid.addWidget(qTableName,1,0)
        self.grid.addWidget(qTable,2,0, 1, 2)
        self.grid.addWidget(deleteButton,3,0)
    
    def widgetTwo(self):
        #this widget is composed of a pulldown menu with the different question type options.
        qType = QtGui.QLabel('Type')
        qTypeMenu = QtGui.QComboBox()
        qTypeMenu.addItem('[Pick Image] <customtext>')
        qTypeMenu.addItem('[Slider Question] <customtext>')
        qTypeMenu.addItem('[Comment Box] <customtext>')
    
        self.grid.addWidget(qType, 4, 0)
        self.grid.addWidget(qTypeMenu,4,1)
    
    def widgetThree(self):
        #this widget allows you to edit the Question text and the Tab text.
        qText = QtGui.QLabel('Question Text')
        qTextEntry = QtGui.QLineEdit()
        tText = QtGui.QLabel('Tab Text')
        tTextEntry = QtGui.QLineEdit()
    
        self.grid.addWidget(qText, 5,0)
        self.grid.addWidget(qTextEntry, 5, 1)
        self.grid.addWidget(tText, 6, 0)
        self.grid.addWidget(tTextEntry, 6, 1)
    
    def widgetFive(self):
        addQuestionButton = QtGui.QPushButton('Add Question',self)
        addQuestionButton.connect(addQuestionButton,QtCore.SIGNAL('clicked()'),QtGui.qApp, QtCore.SLOT('quit()'))
        addQuestionButton.resize(addQuestionButton.sizeHint())
    
        
        doneButton = QtGui.QPushButton('Done',self)
        doneButton.connect(doneButton,QtCore.SIGNAL('clicked()'),QtGui.qApp, QtCore.SLOT('quit()'))
        doneButton.resize(doneButton.sizeHint())
    
        self.grid.addWidget(addQuestionButton,7,0)
        self.grid.addWidget(doneButton,7,1)
    
        self.setLayout(self.grid)
    
        self.setGeometry(300,300,400,500)
        self.setWindowTitle('Question Editor')
        self.show()
    

    def main():
    app = QtGui.QApplication(sys.argv)
    ex = questionGUI()
    sys.exit(app.exec_())

    if name == 'main':
    main()
    @



  • You don't need selection models for this simple operation. Do your connection like this and it will work:
    @
    ...
    qTable.State(2)
    #selection = QtGui.QItemSelectionModel()

        #select = selection.currentRowChanged()
    
        #selected = qTable.selectedItems()
    
        def remove():
            #hopefully this will let the Delete button remove the selected table row
            qTable.removeRow(qTable.currentRow())
    
        deleteButton = QtGui.QPushButton('Delete',self)
        #deleteButton.connect(deleteButton,QtCore.SIGNAL('clicked()'),remove)
        deleteButton.clicked.connect(remove)
    

    ...
    @



  • This edit gives me the error: TypeError: remove() takes exactly 1 argument (0 given). If it matters, this error occurs after I open the GUI, select a row and click "Delete".

    That's why I've been trying to figure out how to make a variable that translates a signal into some sort of integer value, preferably the row index, which can be passed to remove(). Clearly, my clicking is not creating the argument type that remove needs.

    The argument needed is, in your code, 'currentRow()'. Is it a default of QTableWidget to change the currentRow as the user makes selections? That's what your code suggests, but my errors suggest that the remove function is not seeing any row changes as an argument.

    If I print qTable.currentRow() before any selections are made, it has a value of -1. This makes sense because none of the rows are selected by default. I think that this must not be getting passed to remove because removeRow(-1) should give some sort of 'index value out of range' error and not a 'no argument given' error.

    If I try to pass something to remove explicitly:
    @
    selected = qTable.currentRow()

    def remove(selected,self):
    qTable.removeRow(selected)

    deleteButton = QtGui.QPushButton('Delete',self)
    deleteButton.connect(deleteButton, QtCore.SIGNAL('clicked()'),remove(selected))
    @

    I get the error "TypeError: argument 3 of QObject.connect() has an invalid type"

    So remove will work as the SLOT but only if it does not call anything. That will trigger an error about it not getting passed enough arguments. If remove has anything between the parentheses, it triggers an invalid type error.

    I can not use the syntax you provided for deleteButton.connect because it was introduced in a later version of PyQt. My version is 4.4.4 and I do not have the ability to upgrade because this is a work project and work tells me what version to use. My syntax work just fine for all of the other buttons, it's just less elegant.



  • Btw, if your intention with the selection models was to remove multiple selected rows, then use sth like this
    @
    def remove():
    rows = qTable.selectionModel().selectedRows()
    for r in rows:
    qTable.removeRow(r.row())
    @



  • I might have to add that functionality in the future, though I doubt that the users will need to remove more than one row at a time. Right now, I just need one row to be deleted.



  • [quote author="SatelliteEyes" date="1364333501"]This edit gives me the error: TypeError: remove() takes exactly 1 argument (0 given). If it matters, this error occurs after I open the GUI, select a row and click "Delete".[/quote]

    I tested the code before posting and it's working just fine on my setup. Are you importing the correct modules? QtGui, QtCore, etc?



  • Yes, I'm importing both of those. The other widgets successfully use classes from both of those modules.



  • [quote author="SatelliteEyes" date="1364333501"]I can not use the syntax you provided for deleteButton.connect because it was introduced in a later version of PyQt. My version is 4.4.4 and I do not have the ability to upgrade because this is a work project and work tells me what version to use. My syntax work just fine for all of the other buttons, it's just less elegant.
    [/quote]

    Then fix your connection like this:
    @
    self.connect(deleteButton,QtCore.SIGNAL('clicked()'),remove)
    @



  • I changed what I already had:
    @ deleteButton.connect(deleteButton,QtCore.SIGNAL('clicked()'),remove)
    @

    to:
    @ self.connect(deleteButton,QtCore.SIGNAL('clicked()'),remove)@

    and I am still getting the same error.

    I thought that it might have something to do with not specifying a receiver, so I changed things to:
    @ deleteButton.connect(deleteButton,QtCore.SIGNAL('clicked()'),qTable,remove())
    @
    This continues to give me the error that remove is not receiving arguments.



  • Don't know what's wrong then. The button code works fine on my setup (using PySide instead of PyQt though), with both connection styles. Anyway, here is the full code that works for me. Try to run it as is and see if the button works. Otherwise, you may have to dig deeper into your PyQt version.
    @
    class questionGUI(QtGui.QWidget):
    #This class is the window of the gui. The methods within this class
    #are the widgets that make up the different user input regions of the gui
    grid = QtGui.QGridLayout()
    grid.setSpacing(10)

    row = 3
    
    def __init__(self):
        super(questionGUI,self).__init__()
    
        self.widgetFive()
        #self.widgetFour()
        self.widgetThree()
        self.widgetTwo()
        self.widgetOne()
        
    
    def widgetOne(self):
        qTable = QtGui.QTableWidget(self.row,1)
        qTableName = QtGui.QLabel('Available Questions')
        entries = ['[Pick Image] <Default>','[Slider Question] <Default>', '[Comment Box] <Default>']
    
        addrow = 0
        for i in entries:
            item = QtGui.QTableWidgetItem(i)
            qTable.setItem(addrow,0,item)
            addrow += 1
       
        qTable.State(2)
    
        def remove():
            rows = qTable.selectionModel().selectedRows()
            for r in rows:
                qTable.removeRow(r.row())
    
        deleteButton = QtGui.QPushButton('Delete',self)
        self.connect(deleteButton,QtCore.SIGNAL('clicked()'),remove)
    
        qTable.setColumnWidth(0,300)
    
        self.grid.addWidget(qTableName,1,0)
        self.grid.addWidget(qTable,2,0, 1, 2)
        self.grid.addWidget(deleteButton,3,0)
    
    def widgetTwo(self):
        #this widget is composed of a pulldown menu with the different question type options.
        qType = QtGui.QLabel('Type')
        qTypeMenu = QtGui.QComboBox()
        qTypeMenu.addItem('[Pick Image] <customtext>')
        qTypeMenu.addItem('[Slider Question] <customtext>')
        qTypeMenu.addItem('[Comment Box] <customtext>')
    
        self.grid.addWidget(qType, 4, 0)
        self.grid.addWidget(qTypeMenu,4,1)
    
    def widgetThree(self):
        #this widget allows you to edit the Question text and the Tab text.
        qText = QtGui.QLabel('Question Text')
        qTextEntry = QtGui.QLineEdit()
        tText = QtGui.QLabel('Tab Text')
        tTextEntry = QtGui.QLineEdit()
    
        self.grid.addWidget(qText, 5,0)
        self.grid.addWidget(qTextEntry, 5, 1)
        self.grid.addWidget(tText, 6, 0)
        self.grid.addWidget(tTextEntry, 6, 1)
    
    def widgetFive(self):
        addQuestionButton = QtGui.QPushButton('Add Question',self)
        addQuestionButton.connect(addQuestionButton,QtCore.SIGNAL('clicked()'),QtGui.qApp, QtCore.SLOT('quit()'))
        addQuestionButton.resize(addQuestionButton.sizeHint())
    
       
        doneButton = QtGui.QPushButton('Done',self)
        doneButton.connect(doneButton,QtCore.SIGNAL('clicked()'),QtGui.qApp, QtCore.SLOT('quit()'))
        doneButton.resize(doneButton.sizeHint())
    
        self.grid.addWidget(addQuestionButton,7,0)
        self.grid.addWidget(doneButton,7,1)
    
        self.setLayout(self.grid)
    
        self.setGeometry(300,300,400,500)
        self.setWindowTitle('Question Editor')
        self.show()
    

    def main():
    app = QtGui.QApplication(sys.argv)
    ex = questionGUI()
    sys.exit(app.exec_())

    if name == 'main':
    main()
    @



  • And one more older connection style to try out :-)
    @
    QtCore.QObject.connect(deleteButton,QtCore.SIGNAL('clicked()'),remove)
    @



  • Thanks for your help yesterday. After all this, I'm pretty sure it has to be a version problem. Your code is still giving me the 'not enough arguments' error.

    Just to be safe, I change my table to a list, thinking that maybe a simpler widget wouldn't have the problem, but it does. I think I'm going to give up on this and find someone at my company to debug it the rest of the way with me. Thanks,again.



  • I've made progress! I'm not sure if you're still interested, but here is the development. I'm using QtCore.SIGNAL('itemClicked(clicked)') as the signal, rather than the regular QtCore.SIGNAL('clicked()'). This still does not delete the line, but it is not giving me the 'no arguments' error. A new error is always good!

    Now, when I select a row, the selection is blue. When I click the Delete button, the selection color (not the text, the background color in the selected cell) turns grey. You don't have to respond to this, I'll beat on this some more and post a new discussion if I get stuck.

    Here is the new code:

    @
    def widgetOne(self):
    def questionMoved(self,indexes):
    print indexes

        qList = QtGui.QListWidget(self)
        qList.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
        qList.connect(qList,QtCore.SIGNAL('indexesMoved(const QModelIndexList&)'),questionMoved)
    
        qListName = QtGui.QLabel('Available Questions')
        entries = ['[Pick Image] <Default>','[Slider Question] <Default>', '[Comment Box] <Default>']
    
        for i in entries:
            item = QtGui.QListWidgetItem(i)
            qList.addItem(item)
        
        def deleteLink(self):
            self._sql.delete()
            self._sql = None
    
        def remove(self):
            #hopefully this will let the Delete button remove the selected table row
            row = qList.currentRow()
            if (row<0): return
    
            item = qList.takeItem(row)
            item.deleteLink()
    
            for r in range(row, qList.count()):
                item = qList.item(r)
                item.setOrder(row)
                row += 1
        
        clicked = qList.currentRow()
        deleteButton = QtGui.QPushButton('Delete',self)
        deleteButton.connect(deleteButton,QtCore.SIGNAL('itemClicked(clicked)'),remove)
    
        self.grid.addWidget(qListName,1,0)
        self.grid.addWidget(qList,2,0, 1, 2)
        self.grid.addWidget(deleteButton,3,0)
    

    @



  • Hi there,

    In case you haven't already seen this, have a look at this page http://pyqt.sourceforge.net/Docs/PyQt4/old_style_signals_slots.html#pyqt4-signals-and-qt-signals

    My advice though would be to convince your team to use a more recent version of PyQt (or use PySide) :-)


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.