Python Tkinter Fehler: "_tkinter.TclError: invalid command name ".!entry""

Fragen zu Tkinter.
Antworten
vitellinho
User
Beiträge: 13
Registriert: Dienstag 7. März 2023, 11:05

Hallo zusammen,

aus Übungszwecken bin ich die letzten Wochen dabei ein Python Code mitsamt einer GUI zu einer Registrierungs- und Loginstrecke zu schreiben. Diese soll grundlegend keine wirkliche Funktion haben, außer sämtliche Eingaben zu usernames und passwords speichern, auf Anforderungen prüfen und miteinander vergleichen. (Wie man das halt von einem login-formular kennt)

Nun bin ich eigentlich schon fast durch, beim letzten Schritt, wo die login-daten mit den registrierungsdaten verglichen werden (unter def accept_button_clicked2(self):) bekomme ich aber jedoch die fehlermeldung: "_tkinter.TclError: invalid command name ".!entry"". Nach Recherche habe ich herausgefunden, dass diese Fehlermeldung auftaucht, sobald die entrys durch get "geholt" werden, bevor diese überhaupt gefüllt wurden. Nun bin ich aber echt überfragt und weiß nicht wie ich den code umschreiben kann um diesen fehler zu beheben.

PS: Ich weiß, dass mein Code grauenvoll geschrieben ist und das ich es hätte besser machen können.. Es geht mir bei diesem Code aber tatsächlich darum gewisse Funktionalitäten umzusetzen, über den kosmetischen Teil möchte ich mir lieber später Gedanken machen.

Hier mein Code:

Code: Alles auswählen

import tkinter as tk
from tkinter import ttk


class LoginFormular():
    def __init__(self):
        self.window1 = tk.Tk()
        self.window1.title("Registrierung")

        tk.Label(self.window1, text="Neuer Benutzername: ").grid(row=0, column=0)
        self.new_username = ttk.Entry(self.window1, width=15)
        self.new_username.grid(row=0, column=1)

        tk.Label(self.window1, text="Neues Passwort: ").grid(row=1, column=0)
        self.new_password = ttk.Entry(self.window1, width=15, show="*")
        self.new_password.grid(row=1, column=1)

        self.repeat_password_label = tk.Label(self.window1, text="")
        self.repeat_password_label.grid(row=2, column=0)
        self.repeat_password_entry = ttk.Entry(self.window1, width=15, show="*")

        self.error1 = tk.Label(self.window1, text="")
        self.error1["justify"] = "left"
        self.error1.grid(row=3, column=0)

        self.error2 = tk.Label(self.window1, text="")
        self.error2["justify"] = "left"
        self.error2.grid(row=3, column=0)

        accept = ttk.Button(self.window1, text="Bestätigen", command=self.accept_button_clicked)
        accept.grid(row=4, column=1, sticky="nswe")

        quit = ttk.Button(self.window1, text="Beenden", command=self.quit_button_clicked)
        quit.grid(row=4 ,column=0, sticky="nswe")

        self.window1.mainloop()

    def quit_button_clicked(self):
        self.window1.destroy()

    def quit_button_clicked2(self):
        self.window2.destroy()

    def accept_button_clicked2(self):
        if self.new_username.get() != self.login_username.get() or self.new_password.get() != self.login_password.get():
            self.login_alert["text"] = "Anmeldung fehlgeschlagen!"
        else:                                                           
            self.login_alert["text"] = "Anmeldung erfolgreich!"

    def accept_button_clicked(self):
        self.x = 0
        self.error1["text"] = ""
        self.error2["text"] = ""
        self.repeat_password_label["text"] = ""

        username = self.new_username.get()
        password = self.new_password.get()
        repeat_password = self.repeat_password_entry

        if len(username) < 8 or len(password) < 8 or not any(char.isdigit() for char in password) or not any(char.isalpha() for char in password) or not any(char.isupper() for char in password) or not any(char.islower() for char in password):
            self.error1["text"] = "Falsche Eingabe, beachte bitte die Anforderungen:\n \nBenutzername: mind. 8 Zeichen\nPasswort: mind. 8 Zeichen, Klein- und Großbuchstaben sowie Ziffern"
        elif len(username) > 8 or len(password) > 8 or any(char.isdigit() for char in password) or any(char.isalpha() for char in password) or any(char.isupper() for char in password):
            self.new_username.config(state="disabled")
            self.new_password.config(state="disabled")
            self.repeat_password_label["text"] = "Wiederhole dein Passwort: "
            repeat_password.grid(row=2, column=1)

        if len(repeat_password.get()) > 0:
            if repeat_password.get() != self.new_password.get():
                self.error2["text"] = "Die Passwörter stimmen nicht überein. Probier es nochmal."
            else:
                self.x += 1
                self.window1.destroy()
                self.window2 = tk.Tk()
                self.window2.title("Login")

        if self.x == 1:
            self.login_alert = tk.Label(self.window2, text="")
            self.login_alert["justify"] = "left"
            self.login_alert.grid(row=3, column=0)

            self.username_label = tk.Label(self.window2, text="Benutzername: ").grid(row=0, column=0)
            self.login_username = ttk.Entry(self.window2, width=15)
            self.login_username.grid(row=0, column=1)

            self.password_label = tk.Label(self.window2, text="Passwort: ").grid(row=1, column=0)
            self.login_password = ttk.Entry(self.window2, width=15, show="*")
            self.login_password.grid(row=1, column=1)

            self.accept2 = ttk.Button(self.window2, text="Bestätigen", command=self.accept_button_clicked2)
            self.accept2.grid(row=4, column=1, sticky="nswe")

            self.quit2 = ttk.Button(self.window2, text="Beenden", command=self.quit_button_clicked2)
            self.quit2.grid(row=4, column=0, sticky="nswe")

def main():
    login_formular = LoginFormular()

if __name__ == "__main__":
    main()
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

so wie ich das beim überfliegen jetzt gesehen habe, ist die Referenz von 'new_username' 'window1' und von 'loging_username' ist es 'window2'.
Bevor 'window2' entsteht hast du 'window1' zerstört. Ich denke, das könnte hier das Problem sein.
Aber so sollte man das auch nicht machen. Es gibt nur ein 'Tk'-Objekt und das behält man dann auch bei und man kann trotzdem mehrere Seiten darstellen. Als ich das mal brauchte, habe ich mir ein Programm geschrieben, das einfach irgendwas nutzloses rechnet, aber da kannst du mal schauen, wie ich das gelöst habe:

Code: Alles auswählen

import tkinter as tk
from functools import partial


class CalculationApp(tk.Frame):
    def __init__(self, master, calculator):
        tk.Frame.__init__(self, master)
        self.calculator = calculator
        self.frames = {}
        home_page = Home(self)
        home_page.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.E, tk.W))
        entry_page = Entry(self, calculator)
        entry_page.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.E, tk.W))
        result_page = Result(self)
        result_page.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.E, tk.W))
        self.frames['Home'] = home_page
        self.frames['Entry'] = entry_page
        self.frames['Result'] = result_page
        self.show_page("Home")

    def show_page(self, page_name):
        self.frames[page_name].tkraise()

    def get_page(self, page_name):
        return self.frames[page_name]


class Home(tk.Frame):
    def __init__(self, controller):
        tk.Frame.__init__(self)
        self.controller = controller
        headline = tk.Label(self, text="Hier gehts zur Berechnung")
        headline.grid(row=0, column=1)
        go_to_entry_page = tk.Button(
            self, text=">>", command=partial(self.controller.show_page, "Entry")
        )
        go_to_entry_page.grid(row=1, column=3)


class Entry(tk.Frame):
    def __init__(self, controller, calculator):
        tk.Frame.__init__(self)
        self.controller = controller
        self.calculator = calculator
        values_for_calculation = [10, 30]
        description = tk.Label(self, text="Bitte Werte auswählen")
        description.grid(row=0, column=1)
        self.selected_button = tk.StringVar()
        for row_index, value in enumerate(values_for_calculation, 1):
            radio_button = tk.Radiobutton(
                self, text=value, value=value, variable=self.selected_button
            )
            radio_button.grid(row=row_index, column=0)
        tk.Button(
            self, text="Berechne", command=self.start_calculation
        ).grid(row=3, column=3)
        tk.Button(self, text="<<", command=partial(self.controller.show_page, "Home")).grid(
            row=3, column=0
        )

    def start_calculation(self):
        self.calculator.calculate(int(self.selected_button.get()))
        self.controller.get_page('Result').show_result.config(text=self.calculator.result)
        self.controller.show_page('Result')


class Result(tk.Frame):
    def __init__(self, controller):
        tk.Frame.__init__(self)
        self.controller = controller
        self.show_result = tk.Label(self, text='')
        self.show_result.grid(row=0, column=1)
        button = tk.Button(
            self, text="Startseite", command=partial(controller.show_page, "Home")
        )
        button.grid(row=1, column=1)


class Calculator:
    def __init__(self):
        self.result = None
        self.database = [2, 3, 5, 6]

    def calculate(self, user_choice):
        self.result = sum(number * user_choice for number in self.database)


def main():
    calculator = Calculator()
    root = tk.Tk()
    root.title("Berechnungsprogramm")
    app = CalculationApp(root, calculator)
    app.mainloop()


if __name__ == "__main__":
    main()
Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
vitellinho
User
Beiträge: 13
Registriert: Dienstag 7. März 2023, 11:05

Danke für die Antwort.

Habe nun aber einige Fragen zu deinem Code, wäre top wenn du mich da unterstützen könntest:

1. Welchen Sinn hat es, dass du in jeder Klasse (außer Calculator(), wieso?) tk.Frame übergibst? Also ich weiß, dass das wahrscheinlich etwas mit einer Vererbung zutun hat. Aber welchen Vorteil erzielst du in deinem Code explizit?
2. Verstehe ich das richtig, dass du für jede "Seite" sozusagen eine Klasse erstellt hast? Und in der ersten Klasse durch self.frames jedes window jeweils erschaffen hast?
3. Du hast partial importiert und dazu verwendet, um zwischen die jeweiligen Frames per Button zu navigieren. Ist die zwingend durch partial umsetzbar oder auch per destroy / create vom alten / neuen Frame möglich?
4. Was ich leider garnicht verstanden habe, ist wie du die Werte von Frame zu Frame übergeben hast. Ich ahne zumindest, dass es etwas mit show_result zutun haben könnte. Aber sonst blick ich da leider nicht durch. Kannst du mir das nochmal erklären?
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

die Klasse 'Calculator' hat mit dem GUI so an sich gar nichts zu tun, dann braucht es auch kein tk-Objekt. Du kannst diese Klasse unabhängig vom Programm nehmen und benutzen. Das ist der Logik-Teil, der rechnet nur und trägt nicht zur grafischen Darstellung bei.

Jede Seite hat eine Klasse ja. Es gibt eine "Hauptklasse" 'CalculationApp', diese bekommt das 'Tk'-Objekt übergeben. In dieser werden dann Instanzen der "Seiten"-Klassen erzeugt und als Referenz wird die Hauptklasse übergeben.

Es wird nichts zerstört, es gibt eine wunderschöne Methode, die die gewünschte Seite aufruft und dazu muss man den Namen der Seite übergeben. Das macht partial für dich. Wenn du nichts importieren willst, dann kannst du lambda verwenden. Ich mach doch mein Auto auch nicht kaputt, wenn ich am Sonntag mal den Sportwagen fahren will. Dann müsste ich am Montag das Auto ja wieder reparieren.

Ich reiche die Instanz der 'Calculator'-Klasse durch die Seiten und in 'start_calculation' übergebe ich die Werte und greife sie über 'result' wieder ab.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten