PushButton-Funktion greift immer nur auf einen Tab zu

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
NoX5
User
Beiträge: 4
Registriert: Montag 29. Mai 2017, 18:14

Hallo Leute, ich sitze schon ein paar Tage an einem Problem, dass ich leider nicht mehr ohne Hilfe lösen kann.
Allgemein geht es darum, dass ich Fenster erstelle, das dynamisch auf die Eingaben in einem vorherigen Fenster reagieren soll. Ich übergebe z.B. die Zahl 5 und im neuen Fenster werden mir 5 Tabs geöffnet. Dies funktioniert auch problemlos. Jeder Ok-Button (Pushbutton) in meinem Tab soll nun alle Eingaben z.B. Lineedits in dem jeweiligen Tab an eine neue Klasse übergeben und mir diese zur Kontrolle ausgeben. Mein Problem ist nun, dass jeder Ok-Button mir nur die Daten des letzten Tabs gibt, aber nicht die Daten im eigentlichen Tab.
Ich geb euch hier im Folgenden mal ein Minimalbeispiel.
Achja, ich programmiere zwar schon etwas länger in Python, aber erstelle zum erstenmal eine Gui. Vieles könnte man sehr wahrscheinlich sehr viel schöner machen, ich bin also für Tipps aller Art sehr dankbar.

Code: Alles auswählen

class Ui_window_schutz(object):
	def __init__(self):

        	self.anzahl = 1

	def setupUi(self, Dialog):
        	Dialog.setObjectName("Dialog")
        	Dialog.resize(1050, 700)
    		(...)
        	self.tabWidget = QtWidgets.QTabWidget(Dialog)
        	self.tabWidget.setObjectName("tabWidget")	
        
       		self.tab = []
        	self.pushButton_tab = []      
        	self.label_schutzbezeichnung = []
        	self.lineEdit_schutzbezeichnung = []
        	(...)
        	for i in range(self.anzahl): #self.anzahl ist hier der übergebene Wert des ersten Fensters
        	
        		self.tab.append(QtWidgets.QWidget())
        		self.pushButton_tab.append(QtWidgets.QPushButton(self.tab[i]))
        		(...)
        		self.pushButton_tab[i].clicked.connect(lambda: self.click_tab_ok(i))
        		self.tabWidget.addTab(self.tab[i], "")
        	
	def click_tab_ok(self, zahl):
		print(zahl)
		print(self.lineEdit_schutzbezeichnung[zahl].text())
		(...)
Er gibt mir hier als Ausgabe für jeden Ok-Button immer die Zahl (self.anzahl-1) aus.
Ich hab mir überlegt, dass ich das Fenster ja zunächst initialisiere, die gesamte for-Schleife also natürlich durchläuft und der letzte Wert für i immer übergeben wird, sobald ich auf irgendeinen Ok-Button drücke (es quasi erzwungen wird i zu speichern, bis ich Ok-Drücke und dies ist dann natürlich der höchste Wert).
Ich hab es danach auch mal mit einer Push-Button-Klasse versucht und über set_zahl schon bei der Initalisierung des Fensters jedem Button auch seine jeweilige Tab-Zahl zu übergeben. Aber selbst hier tritt das Problem auf, dass ich sobald ich den Wert in der Funktion abrufen möchte, nur die vorher initialisierte, nicht aber die zugeordnete Zahl erhalte, obwohl ich durch Abfrage, beim Initialisieren des Fensters die richtige Zahl erhalte. Tatsächlich muss ich also einen gewaltigen Fehler beim Zuordnen der Funktion beim Drücken des Buttons falsch gemacht haben.

Ich bin für jede Hilfe dankbar, da ich ohne dieses Problem zu lösen, nicht bei meinem Programm weiterkomme. Wenn ich außerdem etwas grundlegend falsch beim Gui-Programmieren, wie z.B. alles über Listen zu lösen, gemacht habe, wäre ich auch hier dankbar Tipps zu bekommen.
Ansonsten noch einen schönen Abend!
Zuletzt geändert von Anonymous am Montag 29. Mai 2017, 19:46, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ne Pulle Bier für jedes mal, dass dieses Problem gelöst wird.... ist nicht deine Schuld. Ist trickreich.

Dein lambda stellt einen closure dar. Das ist ein ad-hoc Objekt, das nicht nur den auszuführenden Code beinhaltet, sondern auch die Werte, die der braucht um zu funktionieren. Wie zB das i in deinem Code.

Das Problem daran ist: ein closure fängt keine Werte, sondern nur Namen! Das heißt jedes deiner 5 Lambadas zeigt auf ein und dassslbe i, und damit auf den letzten Wert, der daran gebunden wurde.

Die Lösung liegt darin, das closure explizit mit dem Wert zur Zeit der Erzeugung zu bestücken.

Das geht auf verschiedenen Arten, zB functions.partial. Oder mit default Argumenten im lambda:

Code: Alles auswählen

  lambda i=i: tuwas(i)
  
NoX5
User
Beiträge: 4
Registriert: Montag 29. Mai 2017, 18:14

Ok, danke erstmal für deine schnelle Antwort. Also ich hab mir mal etwas über Closures durchgelesen (erstmal gut zu wissen, was das überhaupt ist) und das was du geschrieben hast, leuchtet mir auch komplett ein.
Ich hab jetzt also mal deine Lösung ausprobiert und die Variable, auf die ich referenziere, direkt über "lambda t=i: tuwas(t)" mit zu übergeben. Er gibt mir nun zumindestens nicht wie vorher immer den letzten Wert, also self.anzahl -1, für jeden Tab aus, aber jetzt passiert was ganz komisch. Jeder Button greift mir jetzt auf den aller ersten Tab zu, also als ob i=0 für alle Buttons gilt, möchte ich aber in der Funktion den Wert selbst ausgeben lassen bekomme ich erstaunlicherweise den Boolean False raus.
Hast du da eventuell irgendeine Idee, was da passiert sein könnte?
BlackJack

@NoX5: Eigentlich hätte es bei Deiner vorher gezeigten Variante beim klicken eine Ausnahme geben müssen, weil Deine ``lambda``-Funktion kein Argument für das `clicked()`-Signal erwartet. Und genau das passiert jetzt: statt den Defaultwert zu verwenden, verwendet die neue ``lambda``-Funktion das `bool`-Argument welches das `clicked()`-Signal mitliefert. Was bei `QPushButton`-Objekten die nicht gleichzeitig ”checkable” sind, immer `False` ist. Siehe Dokumentation zum `clicked()`-Signal.
NoX5
User
Beiträge: 4
Registriert: Montag 29. Mai 2017, 18:14

Ok, also so ganz hab ich das jetzt nicht verstanden. Was meinst du damit, dass es bei meiner vorherigen Variante eine "Ausnahme" geben müsste? Und zweitens, wie kann ich denn der lambda-Funktion nun den richtigen Wert statt dem Default-Wert False übergeben? Es muss doch möglich sein, einen Wert mit in die Methode zu übergeben und den Push-Button damit die richtige click-Methode zuzuordnen.
Nichtsdestotrotz schauch mich nun mal die function.partial an und schaue, ob ich es damit eventuell lösen kann.
BlackJack

@NoX5: In dieser Zeile:

Code: Alles auswählen

                self.pushButton_tab[i].clicked.connect(lambda: self.click_tab_ok(i))
erwartet die ``lambda``-Funktion kein Argument. `QAbstractButton.clicked()` übergibt aber ein Argumen: https://doc.qt.io/qt-5/qabstractbutton.html#clicked

Also hätte ich eine Ausnahme (ohne Anführungstriche) erwartet wenn versucht wird die Funktion mit einem Argument aufzurufen.

Du musst halt für dieses Argument einen formalen Parameter vorsehen, ob Du den Wert dann in der Funktion verwendest oder nicht.

Zeug zum Nachdenken:

Code: Alles auswählen

In [29]: f = lambda: g(i)

In [30]: i = 10

In [31]: def g(x): print x

In [32]: f()
10

In [33]: f(False)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-33-04e37f7ad6ce> in <module>()
----> 1 f(False)

TypeError: <lambda>() takes no arguments (1 given)

In [34]: f = lambda i=i: g(i)

In [35]: f(False)
False

In [36]: f = lambda checked, i=i: g(i)

In [37]: f(False)
10

In [38]: i = 42

In [39]: f(False)
10
NoX5
User
Beiträge: 4
Registriert: Montag 29. Mai 2017, 18:14

Ahhh.. Jetzt machts klick. Vielen vielen Dank!!!
Ich persönlich find, dass man an Beispielen das Ganze viel besser nachvollziehen kann.
Vielen Dank nochmal, dass du dir die Zeit genommen hast!
Antworten