Erstellen Sie PyQt Menü aus einer Liste von Strings
-
11-09-2019 - |
Frage
Ich habe eine Liste von Strings und will für jede dieses Strings einen Menüeintrag erstellen. Wenn der Benutzer auf einen der Einträge klickt, immer die gleiche Funktion wird mit der Zeichenfolge als Argument aufgerufen werden. Nach einiger versuchen und Forschung kam ich mit so etwas wie dies oben:
import sys
from PyQt4 import QtGui, QtCore
class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.menubar = self.menuBar()
menuitems = ["Item 1","Item 2","Item 3"]
menu = self.menubar.addMenu('&Stuff')
for item in menuitems:
entry = menu.addAction(item)
self.connect(entry,QtCore.SIGNAL('triggered()'), lambda: self.doStuff(item))
menu.addAction(entry)
print "init done"
def doStuff(self, item):
print item
app = QtGui.QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
Das Problem ist jetzt, dass jeder der Menüpunkte wird die gleiche Ausgabe drucken: „Punkt 3“ anstelle der entsprechenden. Ich bin dankbar für alle Ideen, wie ich dieses Recht bekommen. Danke.
Lösung
Sie treffen, was oft vielleicht bezeichnet ist schon auf (nicht ganz pedantisch-richtig ;-) als das „Scoping-Problem“ in Python - die Bindung ist spät (lexikalische Nachschlagen bei Call-Zeit), während Sie es möchten früh (bei def-Zeit). Also, wo Sie jetzt haben:
for item in menuitems:
entry = menu.addAction(item)
self.connect(entry,QtCore.SIGNAL('triggered()'), lambda: self.doStuff(item))
versuchen Sie stattdessen:
for item in menuitems:
entry = menu.addAction(item)
self.connect(entry,QtCore.SIGNAL('triggered()'), lambda item=item: self.doStuff(item))
Diese „geht davon aus“ die Bindung, da die Standardwerte (als item
man hier) ein für allemal zu def Zeit berechnet bekommen. Hinzufügen einer Ebene der Funktions nisten (zum Beispiel ein Doppel lambda) funktioniert auch, aber es ist ein bisschen zuviel des Guten hier! -)
Sie könnten alternativ functools.partial(self.doStuff, item)
verwenden (mit einem import functools
an der Spitze natürlich), die eine weitere feine Lösung ist, aber ich glaube, ich für die einfachste gehen würde (und am häufigsten) „fake default-Wert für Argument“ Idiom.
Andere Tipps
Das sollte funktionieren, aber ich bin mir ziemlich sicher, dass ein besserer Weg, es war, dass ich kann jetzt nicht wieder zu verwenden.
def do_stuff_caller(self, item):
return lambda: self.doStuff(item)
...
self.connect(entry, QtCore.SIGNAL('triggered()'), self.do_stuff_caller(item))
Bearbeiten : Kürzere Version, die noch nicht das, was über ich denke ... oder vielleicht war es auch in einer anderen Sprache? :)
(lambda x: lambda self.do_stuff(x))(item)