Vor kurzem hatte ich das Vergnügen mit Tkinter. Ich schreibe normalerweise nur Kommandozeilentools mit Python, aber irgendwann ist ja immer das erste mal.

Kurzum, ich habe eine kleine Oberfläche geschrieben und das klappte auch ganz gut, nur der Button meiner Messagebox blieb irgendwie eingedrückt. Okay dachte ich, habe ich so noch bei keiner Oberflächen Bibliothek gesehen. Außerdem dachte ich mir, so oben halb links in der Ecke soll das Programmfenster nun auch nicht starten, ein kurzer Blick ins Internet zeigte mir, dass das scheinbar ein riesiges, und wenn überhaupt nur umständlich zu lösendes Problem sei. Doch hier erstmal das Programm, welches beide Probleme aufzeigt. Doch muss ich noch dazu sagen, ich hatte das Beispiel grad nicht zur Hand, also hab ich schnell über ChatGPT ein Beispiel generiert. Dazu habe ich folgenden Prompt verwendet:

Schreibe ein Programm mit Python Tkinter, welches ein Fenster zur Anmeldung zeigt. Wenn der Benutzername "Max" und das Passwort "123" verwendet wurden, soll sich das Programm schließen, wenn etwas anderes eingegeben wurde, soll eine Fehlermeldung angezeigt werden. Das Programm soll mittig auf dem Bildschirm starten. Benutze beim Anmelden-Button bitte bind und nicht den command-Parameter. Label und Entry sollen jeweils in einer Zeile stehen.

ChatGTP hat mir dann folgendes Programm ausgegeben (Ich habe ein paar kleinere Änderungen vorgenommen):

import tkinter as tk
import tkinter.messagebox as msgbox

class LoginWindow(tk.Tk):
    def __init__(self):
        super().__init__()

        # Fensterkonfiguration
        self.title("Anmeldung")
        self.geometry("300x150")
        self.resizable(False, False)
        self.configure(bg="white")

        # Widget-Initialisierung
        self.username_frame = tk.Frame(self, bg="white")
        self.username_frame.pack(pady=5)
        self.username_label = tk.Label(self.username_frame, text="Benutzername:", bg="white")
        self.username_label.pack(side="left")
        self.username_entry = tk.Entry(self.username_frame)
        self.username_entry.pack(side="right")

        self.password_frame = tk.Frame(self, bg="white")
        self.password_frame.pack(pady=5)
        self.password_label = tk.Label(self.password_frame, text="Passwort:", bg="white")
        self.password_label.pack(side="left")
        self.password_entry = tk.Entry(self.password_frame, show="*")
        self.password_entry.pack(side="right")

        self.submit_button = tk.Button(self, text="Anmelden")
        self.submit_button.pack(pady=10)

        # Zentrieren des Fensters
        self.update_idletasks()
        screen_width = self.winfo_screenwidth()
        screen_height = self.winfo_screenheight()
        size = tuple(int(_) for _ in self.geometry().split('+')[0].split('x'))
        x = screen_width/2 - size[0]/2
        y = screen_height/2 - size[1]/2
        self.geometry("+%d+%d" % (x, y))

        # Binden des Anmelden-Buttons an eine Methode
        self.submit_button.bind("<Button-1>", self.check_login)

    def check_login(self, event):
        username = self.username_entry.get()
        password = self.password_entry.get()

        if username == "Max" and password == "123":
            self.destroy()
        else:
            error_message = msgbox.showerror("Fehler", "Falscher Benutzername oder Passwort.")

if __name__ == "__main__":
    app = LoginWindow()
    app.mainloop()

Das zeigt es im Prinzip schon deutlich: 7 Zeilen (mit Kommentar 8 Zeilen kümmern sich um das zentrieren des Fensters,davon mal abgesehen, das dieser Code nicht dem KISS (Keep it simple stupid) Prinzip entspricht, sieht man, dafür gibt es erstaunlicherweise keine einfache Lösung bei Tkinter. Was ich hier dran auch ein bißchen blöd finde, dass der Code auch noch von einer weiteren Zeile nämlich "self.geometry("300x150")" ganz oben bei Fenstergeometrie abhängig ist. Fehlt die Zeile, dann steht in "self.winfo_screenwidth()" und "self.winfo_screenheight()" kein sinnvoller Wert drin, und das zentrieren funktioniert nicht. ich stelle euch gleich eine Lösung vor, wie man das ohne das angeben von einer Fenstergröße hinbekommt. Der Code wir darüber hinaus ein bisschen einfacher und lesbarer als der hier von ChatGPT. Und ja ich weiß es gibt eine "billige" Lösung mit:

root.eval('tk::PlaceWindow . center')

Aber das ist zielmlich LowLevel (greift auf den TK-Unterbau zu) und benötigt einen komplexeren Code bei den Unterfenstern (der "." steht fürs Hauptfenster). Zu guter Letzt werden hier nicht die Fensterkoordinaten der linken oberen Ecke verwendet, somit auch nicht "wirklich" Mittig.

Doch erstmal zum zweiten Problem: Wenn man das Programm startet, und den Button drückt, dann erscheint die Fehlermeldung, aber der Button bleibt eingedrückt. Das liegt daran, dass  Der Event für das drücken des Buttons ausgelöst wird, der dann check_login aufruft, welcher dann ein weiteres Fenster, nämlich die Msgbox aufruft. Um wiederum dieses Fenster zu schließen drückt man den "OK" Button, welcher wiederum eine "Mouse Press" Event auslöst, gefolgt von einem "Mouse Release" Nun scheint in diesem Moment die Information verlorenzugehen, das noch ein weiteres "Mouse Release" erforderlich ist, um den Button des Hauptfensters in den Ausgangszustand zu bringen. Ich hatte gelesen, das es wohl mit der internen Struktur der Abarbeitung zu tun hat, für mich ist das ganz klar ein Bug. Der Bug tritt nicht auf wenn man mit dem "command" Parameter bei der Erstellung des Buttons arbeitet, das finde ich aber inkonsistent zum restlichen Verhalten von Tkinter, zudem ist die Lösung auch ein wenig unflexibler. Doch auch dafür habe ich eine einfache Lösung parat. 

Nach soviel Text nun endlich ein Code welcher beide Probleme löst:

import tkinter as tk
import tkinter.messagebox as msgbox

class LoginWindow(tk.Tk):
    def __init__(self, title="window", height=-1, width=-1):
        super().__init__()

        # Fensterkonfiguration
        self.title(title)
        if height >= 0 and width >= 0:
            self.geometry(f"{width}x{height}")

        self.resizable(False, False)
        self.configure(bg="white")

        # Widget-Initialisierung
        self.username_frame = tk.Frame(self, bg="white")
        self.username_frame.pack(pady=5)
        self.username_label = tk.Label(self.username_frame, text="Benutzername:", bg="white")
        self.username_label.pack(side="left")
        self.username_entry = tk.Entry(self.username_frame)
        self.username_entry.pack(side="right")

        self.password_frame = tk.Frame(self, bg="white")
        self.password_frame.pack(pady=5)
        self.password_label = tk.Label(self.password_frame, text="Passwort:", bg="white")
        self.password_label.pack(side="left")
        self.password_entry = tk.Entry(self.password_frame, show="*")
        self.password_entry.pack(side="right")

        self.submit_button = tk.Button(self, text="Anmelden")
        self.submit_button.pack(pady=10)

        # Zentrieren des Fensters
        self.update_idletasks()
        window_width = self.winfo_reqwidth()
        window_height= self.winfo_reqheight()
        position_left = int(self.winfo_screenwidth() / 2 - window_width / 2)
        position_top = int(self.winfo_screenheight() / 2 - window_height / 2)
        self.geometry(f"+{position_left }+{position_top }")

        # Binden des Anmelden-Buttons an eine Methode
        self.submit_button.bind("<ButtonRelease-1>", self.check_login)

    def check_login(self, event):
        username = self.username_entry.get()
        password = self.password_entry.get()

        if username == "Max" and password == "123":
            self.destroy()
        else:
            error_message = msgbox.showerror("Fehler", "Falscher Benutzername oder Passwort.")

if __name__ == "__main__":
    app = LoginWindow("Anmelden")
    app.mainloop()

Im Falle des ersten Problems arbeiten wir nun mit self.winfo_reqwidth() bzw self.winfo_reqheight() welches wohl generell ungenauer ist, aber uns in diesem Falle die gewünschten Werte liefert.  Die Werte, und auch die Bildschirmwerte werden durch 2 geteilt, um jeweils die Mitte zu  erhalten. Wenn man nun von der Fenstermitte nun noch die hälfte des Fensters abzieht (jeweils in Höhe und Breite) bekommt man nun die linke obere Koordinate welche wir nun für das verschieben des Fensters verwenden können (Geometrie mit "+" Parametern).

Soviel Innovation könnte natürlich der Beginn einer kleinen Helper-Klasse sein, welche einige Dinge im Umgang mit Tkinter vereinfacht:

import tkinter as tk

class TkiHelper(tk.Tk):
    def move(self, left, top):
        self.geometry(f"+{left}+{top}")

    def setDimensions(self, width, height):
        if height >= 0 and width >= 0:
            self.geometry(f"{width}x{height}")

    def center(self):
        # Zentrieren des Fensters
        self.update_idletasks()
        window_width = self.winfo_reqwidth()
        window_height = self.winfo_reqheight()
        position_left = int(self.winfo_screenwidth() / 2 - window_width / 2)
        position_top = int(self.winfo_screenheight() / 2 - window_height / 2)
        self.move(position_left, position_top)

Das Hauptprogramm sähe dann etwas einfacher aus:

import tkinter as tk
import tkinter.messagebox as msgbox
from tkihelper import TkiHelper

class LoginWindow(TkiHelper):
    def __init__(self, title="window", height=-1, width=-1):
        super().__init__()

        # Fensterkonfiguration
        self.title(title)
        self.setDimensions(width, height)
        self.resizable(False, False)
        self.configure(bg="white")

        # Widget-Initialisierung
        self.username_frame = tk.Frame(self, bg="white")
        self.username_frame.pack(pady=5)
        self.username_label = tk.Label(self.username_frame, text="Benutzername:", bg="white")
        self.username_label.pack(side="left")
        self.username_entry = tk.Entry(self.username_frame)
        self.username_entry.pack(side="right")

        self.password_frame = tk.Frame(self, bg="white")
        self.password_frame.pack(pady=5)
        self.password_label = tk.Label(self.password_frame, text="Passwort:", bg="white")
        self.password_label.pack(side="left")
        self.password_entry = tk.Entry(self.password_frame, show="*")
        self.password_entry.pack(side="right")

        self.submit_button = tk.Button(self, text="Anmelden")
        self.submit_button.pack(pady=10)

        self.center()

        # Binden des Anmelden-Buttons an eine Methode
        self.submit_button.bind("<ButtonRelease-1>", self.check_login)

    def check_login(self, event):
        username = self.username_entry.get()
        password = self.password_entry.get()

        if username == "Max" and password == "123":
            self.destroy()
        else:
            error_message = msgbox.showerror("Fehler", "Falscher Benutzername oder Passwort.")

if __name__ == "__main__":
    app = LoginWindow("Anmelden")
    app.mainloop()

 

Ich hoffe der Artikel hilft den einen oder anderen weiter, seinen Tkinter Code zu vereinfachen.

cheers

VoSs2o0o


Keine Kommentare

Kommentar hinterlassen

Als Antwort auf Some User