2


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.

passion053
  • 473
  • 3
  • 8
  • 21

1 Answers1

4

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.

Community
  • 1
  • 1
Daniele Pantaleone
  • 2,657
  • 22
  • 33
  • Thank you Daniele Pantaleone!. You suggested great solution. But your code's result looks something wrong. 'item.data()' shows only None. Not a incremental number. Can you teach me why? – passion053 Nov 15 '15 at 23:38
  • I have changed the 'item.setData(index, Qt.userRole) to 'item.setData(index, Qt.userRole + 1)' and item.data(Qt.UserRole + 1). This works as I expected. Now, It is clear what are setData(), data(), Qt.UserRole. Thanks again. Daniele Pantaleone. – passion053 Nov 16 '15 at 00:25
  • Thanks for noticing the issue. I update my answer with the correction in case someone else need it ;) – Daniele Pantaleone Nov 16 '15 at 12:06