"command" (Button) wartet nicht auf klick

Fragen zu Tkinter.
Antworten
powaaah
User
Beiträge: 19
Registriert: Samstag 24. Oktober 2015, 12:42

Ich versuche gerade Python zu lernen und schreibe parallel dazu ein kleines Programm. Im ersten Schritt möchte ich einen Auswahlknopf für jede angelegte Person erstellen. Klickt man auf den Button soll der Nutzer ausgewählt sein und dann ein paar Daten angezeigt bekommen. Das Programm funktioniert soweit, dass die Buttons dynamisch erzeugt werden, je nachdem wer sich angelegt hat. Im echten Programm lasse ich eine Datei auslesen in der die Nutzer stehen. Hier zum anschauen habe ich einfach zwei Nutzer in einer Liste festgelegt.

Mein Problem ist nun, dass das Programm nicht auf einen Klick, auf einen Button, wartet und dann die Funktion (nutzer_aufrufen) ausführt, sondern einfach beide Möglichkeiten direkt ausführt.

Daran angefügt, wie schaffe ich es, dass das alte Fenster geschlossen wird und ein neues Fenster sich öffnet? Zum Beispiel: Drücke ich auf neuer Nutzer erscheint das Eingabefeld bisher unter den drei Buttons. Es sollte jedoch ein komplett neues Fenster entstehen mit Eingabefeld usw.. Es müsste also irgendein Befehl in die Funktion neuer_nutzer eingefügt werden, der das vorherige Fenster schließt.

Code: Alles auswählen

from tkinter import *

daten_nutzer1 = open("nutzer1.txt", "w")
schreiben1 = daten_nutzer1.write("1 2 3")
daten_nutzer1.close()

daten_nutzer2 = open("nutzer2.txt", "w")
schreiben2 = daten_nutzer2.write("4 5 6")
daten_nutzer2.close()

class Program:
    def __init__(self):
        benutzer = ["Nutzer1", "Nutzer2"]
        for i in benutzer:
            nameButton=Button(text=i, width=20, command=self.nutzer_aufrufen(i)).pack()

    def nutzer_aufrufen(self, Name):
        dateiname = Name+".txt"
        ausgewaehlter_benutzer = open(dateiname, "r")
        for daten in ausgewaehlter_benutzer:
            daten = list(daten.split(" "))
            print(daten)

    def neuer_nutzer():
        nameEntry = Entry().pack()
        pass

    neuerNutzer_Button = Button(text="Neuer Nutzer?", width=20, command=neuer_nutzer).pack()



program = Program()
mainloop()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Ist klar, dass das direkt gleich ausgeführt wird. Denn hier rufst Du das ja gleich auf:

Code: Alles auswählen

command=self.nutzer_aufrufen(i)
Richtig wäre:

Code: Alles auswählen

command=lambda index = i: self.nutzer_aufrufen(index)
Noch richtiger:

Code: Alles auswählen

command=lambda index = i, function=self.nutzer_aufrufen: function(index)
Man kann auch partial nehmen statt lambda.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Und für Fenster benutze ein Toplevel. Das kannst Du dann mit destroy schließen.
powaaah
User
Beiträge: 19
Registriert: Samstag 24. Oktober 2015, 12:42

Vielen Dank es funktioniert.

Was hat command aber für einen Sinn, wenn es eben nicht wartet bis der Button geklickt wird? Im Endeffekt entsteht durch die lambda ... Funktion ja nichts anderes als der gleiche Befehl, den ich reingeschrieben hatte. Warum wird also diese lambda Funktion nicht sofort ausgeführt der direkte Verweis auf die auszuführende Funktion jedoch schon?

Mit dem Toplevel bin ich mir nicht sicher ob es das ist was ich meine. Mir scheint, das Toplevel immer ein komplett neues Fenster anlegt.
Nehmen wir an ich habe ein Formular mit vielen Eingabefeldern und benötige für alle Eingabefelder 2 "Seiten". Das heißt nach der ersten Seite klicke ich auf "weiter" und es erscheint die nächste Seite im gleichen Fenster mit dem neuen Inhalt. Das wird sicher ein ziemlich einfacher Befehl sein. Bei meinen Recherchen bin ich jedoch noch nicht darauf gestoßen. Oder nutze ich in dem Fall die gleiche Funktion nur mit anderen Parametern?
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@powaaah: Du mußt zwischen einer Funktion self.nutzer_aufrufen und dem Aufrufen der Funktion self.nutzer_aufrufen(i) unterscheiden. In Deinem Fall kommt noch hinzu, dass Du Dir den aktuellen Zustand der Variable i beim Aufruf von Button merken mußt. Da ist es mMn verständlicher partial zu benutzen:

Code: Alles auswählen

Button(text=i, width=20, command=functools.partial(self.nutzer_aufrufen, i))
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@powaaah: Wenn Du mehrere Seiten anzeigen willst, nimmst Du am besten mehrere Frames im Hauptfenster.

Zum Programm selbst: *-importe solltest Du nicht verwenden, man lädt bei Tk damit mehrere hundert Namen in den eigenen Namensraum, ohne Kontrolle darüber zu haben. Ich nehme immer "import tkinter as tk" und kann dann über "tk.xxx" auf alle Attribute des Moduls zugreifen.
Auf oberster Ebene sollten außer Funktions-/Klassendefinitionen und Konstanten nichts stehen. Meist definiert man sich eine main-Funktion die über das Idiom:

Code: Alles auswählen

if __name__ == '__main__':
    main()
aufgerufen wird. Wenn Du Dateien mit dem Statement "with ..." öffnest, vergisst Du sie auch nicht wieder zu schließen, wie in "nutzer_aufrufen". "i" ist ein schlechter Variablennamen. Besser wäre "benutzer", aber der kolidiert mit Deiner Liste "benutzer" weil im Deutschen Singular und Plural gleich geschrieben werden. Im Englischen wäre das einfacher: user bzw. users.

Was ist der Rückgabewert von "pack" und was für einen Sinn hat es, diesen an eine Variable zu binden?
"ausgewaehlter_benutzer" ist ein schlechter Name für ein File-Objekt. Klassen-Attribute sind nur in speziellen Situationen sinnvoll. "neuerNutzer_Button" gehört da definitiv nicht dazu, zumal er nur den Wert None hat.
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

powaaah hat geschrieben:Was hat command aber für einen Sinn, wenn es eben nicht wartet bis der Button geklickt wird? Im Endeffekt entsteht durch die lambda ... Funktion ja nichts anderes als der gleiche Befehl, den ich reingeschrieben hatte. Warum wird also diese lambda Funktion nicht sofort ausgeführt der direkte Verweis auf die auszuführende Funktion jedoch schon?
command kann nicht warten auf irgendetwas, es ist einfach nur ein ganz normaler Parameter. Wichtig ist, was dieser Parameter ist: ein aufrufbares Objekt (callable) ist. Es gibt verschieden Moeglichkeiten, das zu erreichen - eben ein lambda, welches ein solches callable erzeugt, oder ein partial-Objekt, das ebenfalls so etwas darstellt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

powaaah hat geschrieben:Was hat command aber für einen Sinn, wenn es eben nicht wartet bis der Button geklickt wird? Im Endeffekt entsteht durch die lambda ... Funktion ja nichts anderes als der gleiche Befehl, den ich reingeschrieben hatte. Warum wird also diese lambda Funktion nicht sofort ausgeführt der direkte Verweis auf die auszuführende Funktion jedoch schon?
Um das mal etwas zu verdeutlichen. Wenn Du eine Funktion hast, die etwa heißt: myfunction

Dann schreibst Du bei command: command = myfunction
Aber bitte nicht: command = myfunction()

Wenn Du geschrieben hast: command = myfunction
dann ruft der Klick auf den Button auf: myfunction()

Das funktioniert soweit gut, solange man keine Parameter übergeben muss. Wenn man Parameter übergeben will, dann braucht man eine Funktion, welche die aufzurufende Funktion nicht ausführt, sondern lediglich eine Funktion erzeugt, welche die aufzurufende Funktion aufruft und eine Referenz auf diese Funktion zurückliefert. Und genau das macht lambda:

a = lambda: print('Hello')

Dadurch wird print("Hello") noch nicht ausgeführt. Erst wenn man a() aufruft. Und statt lambda kann man auch partial nehmen, oder sich selbst etwas stricken.

Das wäre etwa partial selber gestrickt:

Code: Alles auswählen

class MyPartial:
    def __init__(self,function,args):
        self.function = function
        self.args = args
        
    def execute(self):
        self.function(*(self.args))
    
def mypartial(function,*args):
    return MyPartial(function,args).execute

def test_function(a,b):
    print(a)
    print(b)
    
myref = mypartial(test_function,"hello","nice day")

print('Does it work?')
myref()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

powaaah hat geschrieben:Mit dem Toplevel bin ich mir nicht sicher ob es das ist was ich meine. Mir scheint, das Toplevel immer ein komplett neues Fenster anlegt.
Nehmen wir an ich habe ein Formular mit vielen Eingabefeldern und benötige für alle Eingabefelder 2 "Seiten". Das heißt nach der ersten Seite klicke ich auf "weiter" und es erscheint die nächste Seite im gleichen Fenster mit dem neuen Inhalt. Das wird sicher ein ziemlich einfacher Befehl sein. Bei meinen Recherchen bin ich jedoch noch nicht darauf gestoßen. Oder nutze ich in dem Fall die gleiche Funktion nur mit anderen Parametern?
Sorry, dass ich das mit den 'neuen Fenster' mißverstanden hatte. Ich hatte gedacht, wenn Du 'neues Fenster' schreibst, dass Du auch neues Fenster meinst.

Aber offensichtlich geht es Dir darum, zwei Seiten im selben Fenster anzuzeigen, und nicht in einem 'neuen Fenster'. Da kannst Du dann zwei Frames nehmen oder zwei Labelframes. Wenn Du ein GridLayout nimmst, kannst Du ja beide Frames in gleicher Zeile und Spalte positionieren. Vor mainloop sieht man sie ja sowieso nicht. Und beim zweiten Frame machst Du ein grid_remove().
Und umschalten tust Du dann mit:

Code: Alles auswählen

frame2.grid()
frame1.grid_remove()
bzw.

Code: Alles auswählen

frame1.grid()
frame2.grid_remove()
Oder wenn sie gleiche Größe haben, kannst Du das Layout auch so lassen und schiebst mal den einen oder anderen Frame in den Vordergrund, etwa mit:

Code: Alles auswählen

frame2.lift()
Antworten