Search code examples
pythonpyqtpyqt4qlistwidgetqlistwidgetitem

Accessing to QListWidgetItem from widget inside


I'm trying to figure out how I can get the QWidget that I insert into a QListWidget as a QListWidgetItem to be able to access the list it is a part of so that it can do the following:

  • Increase/decrease it's position in the list
  • Remove itself from the list
  • Pass information from it's own class to a function in the main class

My script layout is a main.py which is where the MainWindow class is. The MainWindow uses the class generated from the main ui file. I also have the custom widget which is it's own class.

Example of GUI:

Example

Relevant code snippets:

main.py

from PyQt4.QtGui import QMainWindow, QApplication
from dungeonjournal import Ui_MainWindow
from creature_initiative_object import InitCreatureObject
from os import walk

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
       super(QMainWindow, self).__init__(parent)
       self.setupUi(self)
       etc......
    def AddToInitiative(self):
        creature = self.comboBoxSelectCharacter.currentText()
        if(creature):
            creatureInfo = ''
            with open("creatures/"+str(creature)+".creature", "r") as f:
                creatureInfo = f.read()
            creatureInfo = creatureInfo.split("|")

            customWidget = InitCreatureObject()
            customWidgetItem = QtGui.QListWidgetItem(self.initiativeList)
            customWidgetItem.setSizeHint(QtCore.QSize(400,50))
            self.initiativeList.addItem(customWidgetItem)
            self.initiativeList.setItemWidget(customWidgetItem, customWidget)
            customWidget.setName(creatureInfo[0])
        return

creature_initiative_object.py

class Ui_InitCreatureObject(object):
    def setupUi(self, InitCreatureObject):
        etc...

class InitCreatureObject(QtGui.QWidget, Ui_InitCreatureObject):
    def __init__(self, parent=None, f=QtCore.Qt.WindowFlags()):
        QtGui.QWidget.__init__(self, parent, f)

        self.setupUi(self)

Edit 1: To clarify again, I need to be able to use the buttons in the widget to modify the position of itself in the list. The list is part of the main ui. The buttons for up arrow, down arrow, Select, and Remove are the one's I'm trying to get to interact with things outside of their class.

The function they call needs to be able to determine which listItem is being called, be able to modify the list.

For example, if I click remove, it then needs to know which item in the list to remove. So it needs to first know what the list is, then it needs to know what item it is. I'm not sure how to access the instance of the widget that is occupying that listitem. I also am not sure how to get that listitem based on a button press from inside that listitem's class.

Edit 2: Per the first answer I tried to work that into my code.

main.py had the following function added

def RemoveItem(self):
        cwidget = self.sender().parent()
        item = self.initiativeList.itemAt(cwidget.pos())
        row = self.initiativeList.row(item)
        self.initiativeList.takeItem(row)
        print(row)

creature_initiative_object.py had the following added to the InitCreatureObject class

class InitCreatureObject(QtGui.QWidget, Ui_InitCreatureObject):
    def __init__(self, parent=None, f=QtCore.Qt.WindowFlags()):
        QtGui.QWidget.__init__(self, parent, f)
        self.setupUi(self)
        self.mainwidget = main.MainWindow()
        self.btnRemove.clicked.connect(self.mainwidget.RemoveItem)

Item is still not being passed. The parent object seems to be right but when I get the row it always says -1.


Solution

  • The strategy to get the QTableWidgetItem is to use the itemAt() method but for this you must know the position of some point within the QTableWidgetItem.

    Since the main objective is to get the item when a signal is sent, then the connected slot is used, so I recommend connecting all the signals to that slot. Given the above the following steps are taken:

    • Get the object that emits the signal through sender().
    • Get the sender parent() since this will be the custom widget that was added to the QListWidget() along with the item.
    • Get the position of the custom widget through pos(), this is the position that should be used in the itemAt() method.
    • Then you get the text of the button or some parameter that tells me the task to know what action you want to do.

    The above can be implemented as follows:

    def someSlot(self):
        p = self.sender().parent()
        it = self.lw.itemAt(p.pos())
        text = self.sender().text()
        if text == "task1":
            do task1
        elif text == "task2":
            do task2    
    

    From the above, the following example is proposed:

    class CustomWidget(QWidget):
        def __init__(self, text, parent=None):
            QWidget.__init__(self, parent)
            self.setLayout(QHBoxLayout())
            self.buttons = []
            vb = QVBoxLayout()
            self.layout().addLayout(vb)
            self.btnTask1 = QPushButton("task1")
            self.btnTask2 = QPushButton("task2")
    
            vb.addWidget(self.btnTask1)
            vb.addWidget(self.btnTask2)
    
            self.buttons.append(self.btnTask1)
            self.buttons.append(self.btnTask2)
    
            self.btnTask3 = QPushButton("task3")
            self.btnTask4 = QPushButton("task4")
            self.btnTask5 = QPushButton("task5")
            self.btnTask6 = QPushButton("task6")
            self.layout().addWidget(self.btnTask3)
            self.layout().addWidget(self.btnTask4)
            self.layout().addWidget(self.btnTask5)
            self.layout().addWidget(self.btnTask6)
    
            self.buttons.append(self.btnTask3)
            self.buttons.append(self.btnTask4)
            self.buttons.append(self.btnTask5)
            self.buttons.append(self.btnTask6)
    
    
    class MainWindow(QMainWindow):
        def __init__(self, parent=None):
            QMainWindow.__init__(self, parent)
            self.lw = QListWidget(self)
            self.setCentralWidget(self.lw)
            for i in range(5):
                cw = CustomWidget("{}".format(i))
                for btn in cw.buttons:
                    btn.clicked.connect(self.onClicked)
    
                item = QListWidgetItem(self.lw)
                item.setSizeHint(QSize(400, 80))
                self.lw.addItem(item)
                self.lw.setItemWidget(item, cw)
    
        def onClicked(self):
            p = self.sender().parent()
            it = self.lw.itemAt(p.pos())
            row = self.lw.row(it)
            text = self.sender().text()
            print("item {}, row {}, btn: {}".format(it, row, text))
            #if text == "task1":
            # do task1
            #elif text == "task2":
            # do task2    
    
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        w = MainWindow()
        w.show()
        sys.exit(app.exec_())
    

    In your Case:

    class MainWindow(QMainWindow, Ui_MainWindow):
        [...]
        def AddToInitiative(self):
            [...]
            customWidget = InitCreatureObject()
            customWidget.btnRemove.clicked.connect(self.RemoveItem)
            # ^^^^^
            [...]
        def RemoveItem(self):
            cwidget = self.sender().parent()
            item = self.initiativeList.itemAt(cwidget.pos())
            row = self.initiativeList.row(item)
            self.initiativeList.takeItem(row)
            print(row)