passion053
passion053

Reputation: 493

How to pass an extra arguments to PyQt slot?


Hello everyone. I am making simple model/view application using python3.4 and PyQt5 in Windows 7.

First of all, here is my code.

import sys

from PyQt5.QtWidgets import QApplication, QWidget, QListView
from PyQt5.Qt import QStandardItemModel, QStandardItem

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.list = QListView(self)

        model = QStandardItemModel(self.list)

        carMaker = ['Ford', 'GM', 'Renault', 'VW', 'Toyota']

        for maker in carMaker:
            item = QStandardItem(maker)
            item.setCheckable(True)
            model.appendRow(item)
        self.list.setModel(model)

        model.itemChanged.connect(self.on_item_changed)
        #model.itemChanged.connect(functools.partial(self.on_item_changed, item, 1))
        #model.itemChanged.connect(lambda: self.on_item_changed(item, 2))

        self.list.setMinimumSize(300, 300)
        self.setWindowTitle("Simple modelView")
        self.show()

    def on_item_changed(self, item):
        print(item.text())


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

This works fine. But I want to add extra arguments with 'itemChanged' signal. So, I've used lambda and functools

  1. lambda

    • changed from 'def on_item_changed(self, item)' to 'def on_item_changed(self, item, num)'
    • changed from 'model.itemChanged.connect(self.on_item_changed)' to 'model.itemChanged.connect(lambda: self.on_item_changed(item, 1))'
    • It has no error. But 'item.text()' shows only 'Toyota'. (maybe last item model)
  2. functools.partial

    • changed from 'def on_item_changed(self, item)' to 'def on_item_changed(self, item, num)'
    • changed from 'model.itemChanged.connect(self.on_item_changed)' to 'model.itemChanged.connect(functools.partial(self.on_item_changed, item, 1))'
    • It has no error. But 'item.text()' shows only 'Toyota'. (maybe last item model) Same result as lambda.


The questions are...

  1. I don't know why lambda and functools shows wrong text.

  2. Is any effective way to pass an extra args with signal?

Thank you for read my question.

Upvotes: 2

Views: 4844

Answers (1)

Daniele Pantaleone
Daniele Pantaleone

Reputation: 2733

To answer your first question, the problem with functools.partial and lambda function usage lies in the fact the when you connect the signal to the slot, the variable item is set and it references the last QStandardItem you added in the QStandardItemModel. So basically you are in this situation:

def initUI(self):
    self.list = QListView(self)
    model = QStandardItemModel(self.list)
    carMaker = ['Ford', 'GM', 'Renault', 'VW', 'Toyota']

    for maker in carMaker:
        item = QStandardItem(maker) # here you create a item variable that is overwritten
        item.setCheckable(True)     # on every iteration of the for loop
        model.appendRow(item)

    self.list.setModel(model)

    # here you use again the item variable (the same applies for lambda)
    model.itemChanged.connect(functools.partial(self.on_item_changed, item, 1))

The reason why it prints "Toyota" is simply the fact that it's the last element in the carMaker list, so it's the last QStandardItem created, hence the item variable references this very object.

To answer the second question instead, you can make use of the setData() method of QStandardItem to store some data you need in the item, and then retrieve them using data().

import sys

from PyQt5.QtCore import pyqtSlot, Qt
from PyQt5.QtWidgets import QApplication, QWidget, QListView
from PyQt5.QtGui import QStandardItemModel, QStandardItem

class Example(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):

        self.list = QListView(self)
        model = QStandardItemModel(self.list)
        carMaker = ['Ford', 'GM', 'Renault', 'VW', 'Toyota']
        index = 0 # just to store something different for each QStandardItem

        for maker in carMaker:
            item = QStandardItem(maker)
            item.setCheckable(True)
            item.setData(index, Qt.UserRole + 1)
            model.appendRow(item)
            index += 1

        self.list.setModel(model)

        model.itemChanged.connect(self.on_item_changed)

        self.list.setMinimumSize(300, 300)
        self.setWindowTitle("Simple modelView")
        self.show()

    @pyqtSlot('QStandardItem')
    def on_item_changed(self, item):
        print("%s - %s" % (item.text(), item.data(Qt.UserRole + 1)))

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

Alternatively you can make use of a signal mapper as described here.

Upvotes: 4

Related Questions