2009-07-08 27 views
10

Tengo una lista de cadenas y quiero crear una entrada de menú para cada una de esas cadenas. Cuando el usuario hace clic en una de las entradas, siempre se llamará a la misma función con la cadena como argumento. Después de algún tratando y la investigación me encontré con algo como esto:Crear menú PyQt a partir de una lista de cadenas

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_()) 

Ahora el problema es que cada uno de los elementos del menú se imprimirá la misma salida: "Artículo 3" en lugar de la correspondiente. Estoy agradecido por cualquier idea sobre cómo puedo hacer esto bien. Gracias.

Respuesta

23

Usted está cumpliendo lo que se ha referido a menudo (tal vez no del todo correcta pedante-;-) como el "problema de alcance" en Python - la unión es tarde (de búsqueda léxica en llamadas en tiempo) mientras lo desea temprano (en tiempo de desinfección). Entonces, ¿dónde ahora tiene:

for item in menuitems: 
     entry = menu.addAction(item) 
     self.connect(entry,QtCore.SIGNAL('triggered()'), lambda: self.doStuff(item)) 

tratan en su lugar:

for item in menuitems: 
     entry = menu.addAction(item) 
     self.connect(entry,QtCore.SIGNAL('triggered()'), lambda item=item: self.doStuff(item)) 

Este "anticipa" la unión, ya que los valores por defecto (como el item uno aquí) conseguir calculado una vez que una de todos a la definición en tiempo . La adición de un nivel de la función de anidación (por ejemplo, un doble lambda) funciona también, pero es un poco de una exageración aquí -!)

Se podría, alternativamente, utilizar functools.partial(self.doStuff, item) (con un import functools en la parte superior, por supuesto) que es otra buena solución , pero creo que elegiría el modismo más simple (y el más común) de "valor predeterminado falso para el argumento".

+1

@Alex: +1 !!! ¡Me ha estado molestando toda la mañana! lambda x = x: func (x) FTW :) –

+0

¡Excelente respuesta! También funciona con la nueva sintaxis de conexión: 'entry.triggered.connect (lambda item = item: self.doStuff (item))' – Leistungsabfall

2

Esto debería funcionar, pero estoy bastante seguro de que había una manera mejor que no puedo recordar en este momento.

def do_stuff_caller(self, item): 
    return lambda: self.doStuff(item) 

... 
self.connect(entry, QtCore.SIGNAL('triggered()'), self.do_stuff_caller(item)) 

Editar: versión más corta, que todavía no es lo que estoy pensando ... o tal vez fue en otro idioma? :)

(lambda x: lambda self.do_stuff(x))(item)
Cuestiones relacionadas