Seite aktualisieren

Fragen zu Tkinter.
Antworten
Mark288
User
Beiträge: 2
Registriert: Freitag 4. Dezember 2020, 15:26

Hallo,
ich habe ein kleines Programm geschrieben und zwar eine Speisekarte.
Gespeichert wird diese in einer SQLite Datenbank und beinhaltet die Kategorie, den Namen und den Preis.
Das Programm gibt mir geordnet nach den Kategorien die Gerichte aus, Name und Preis.
Wenn ich auf eine Kategorie klicke kann ich dort ein neues Gericht hinzufügen.
Wenn ich auf eine bereits vorhandene Speise klicke, kann ich diese in allen drei Bereichen modifizieren oder ganz löschen.
Mein Problem liegt im aktualisieren der Seite, nachdem ich etwas verändert habe. Beim ersten Laden kann ich zwischen oberen und unterem Ende der Ausgabe hin und her scrollen. Wenn ich die Seite allerdings neu Lade, ist der untere Teil der Speisekarte an den oberen Rand des Fensters verschoben und ich muss den Inhalt per Mousescroll nach unten holen.

Code: Alles auswählen

class Window:

    def __init__(self, master):
        self.master = master
        self.is_window_open = False  # Marker ob 2. Fenster auf
        self.canvas = None
        self.scrollbar = None
        self.menu_frame = None
        self.end_button = None
        self.refresh_button = None
        self.set_properties()  # Gestaltet das Fenster
        self.print_menu()  # gibt das Fenster aus

    def print_menu(self):
        categories = ['Vorspeise', 'Hauptspeise', 'Nachspeise', 'Getränk']
        # Für jede Kategorie die entsprechenden Gerichte und Preise aus der Datenbank abfragen
        for category in categories:
            cursor.execute('SELECT gericht, preis FROM test_table WHERE kategorie = ?', (category,))
            menu_items = cursor.fetchall()
            # Kategorien lassen sich anklicken um neue Speisen einzufuegen
            category_label = tk.Label(self.menu_frame, text=category, font=("Helvetica", 16, 'bold'), bg="#feebed",
                                      padx=5, pady=5)
            category_label.pack(fill=tk.BOTH)
            category_label.bind('<Button-1>', lambda event, cat=category: self.add_item_window(cat))
            # Wenn es Gerichte in dieser Kategorie gibt, diesem Button angehangen
            if menu_items:
                for item in menu_items:
                    label_text = f'{item[0]:<20} {item[1]:>10} €'
                    item_label = tk.Label(self.menu_frame, text=label_text, font=("Helvetica", 14), bg="#efeaec")
                    item_label.pack()
                    item_label.bind('<Button-1>', lambda event, category=category, name=item[0],
                                                         preis=item[1],: self.configure_item_window(category, name,
                                                                                                    preis))

    def refresh_menu(self):
        # Zerstöre die Labels und die Scrollbar
        for widget in self.menu_frame.winfo_children():
            widget.destroy()
        for widget in self.button_frame.winfo_children():
            widget.destroy()
        self.menu_frame.destroy()
        self.button_frame.destroy()
        self.canvas.destroy()
        self.scrollbar.destroy()
        # Erstelle die Scrollbar und rufe print_menu() erneut auf, um die aktualisierten Labels anzuzeigen
        self.set_properties()
        self.print_menu()
        
     def set_properties(self):
        # Style Hauptfenster
        self.master.title("Speisekarte")
        self.master.geometry("800x600")
        screen_width = self.master.winfo_screenwidth()
        screen_height = self.master.winfo_screenheight()
        window_width = 800
        window_height = 600
        x_coordinate = (screen_width / 2) - (window_width / 2)
        y_coordinate = (screen_height / 2) - (window_height / 2)
        self.master.geometry("%dx%d+%d+%d" % (window_width, window_height, x_coordinate, y_coordinate))
        self.master.configure(bg="#efeaec")
        # Erstelle ein Canvas
        self.canvas = tk.Canvas(self.master, bg="#efeaec", highlightthickness=0, width=400, height=600)
        self.canvas.pack(side=tk.LEFT, fill=tk.Y, expand=True)

        # Füge einen Scrollbar hinzu
        self.scrollbar = ttk.Scrollbar(self.master, orient=tk.VERTICAL, cursor="arrow", command=self.canvas.yview)
        self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # Verbinde den Scrollbar mit dem Canvas und konfiguriere die scrollregion
        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        self.scrollbar.configure(command=self.canvas.yview)
        self.canvas.bind('<Configure>', lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
        self.canvas.bind_all("<MouseWheel>", self.scroll_canvas)
        self.scrollbar.set(0.0, 0.0)

        # Erstelle ein Frame, um die Labels(Gerichte) zu halten
        self.menu_frame = tk.Frame(self.canvas, bg="#efeaec", width=500)
        self.menu_frame.pack(side=tk.TOP, fill=tk.X, padx=20, pady=20)

        # Erstelle ein Frame, um die Buttons(Beenden/Refresh) zu halten
        self.button_frame = tk.Frame(self.canvas, bg="#efeaec", width=500)
        self.button_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=20, pady=20)

        self.canvas.create_window((0, 0), window=self.button_frame, anchor='nw')
        self.canvas.create_window((0, 0), window=self.menu_frame, anchor='sw')

        # Beenden und Refresh Button
        self.end_button = tk.Button(self.button_frame, text="Beenden", command=self.master.quit,  # Button zum beenden
                                    font=("Helvetica", 16), bg="#bf717e", fg="black")
        self.end_button.pack(side=tk.LEFT, padx=10, pady=10, anchor='w')

        self.refresh_button = tk.Button(self.button_frame, text="Refresh", command=lambda: self.refresh_menu(),
                                        font=("Helvetica", 16), bg="#bf717e", fg="black")
        self.refresh_button.pack(side=tk.LEFT, padx=10, pady=10, anchor='w')

    def scroll_canvas(self, event):
        if event.delta:
            direction = 1 if event.delta > 0 else -1
            self.canvas.yview_scroll(direction, "units")
        
Das sind die Methoden, die ich beim aktualisieren nutze.
Benutzeravatar
__blackjack__
User
Beiträge: 13121
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Mark288: Da ist ein Einrückungsfehler drin der syntaktisch falsch ist.

Da fehlt mindestens ein ``import`` und es scheint ein magisch existierendes `cursor` auf Modulebene als globale Variable zu existieren, was nicht so sein sollte. Funktionen und Methoden bekommen alles was sie ausser Konstanten benötigen, als Argument(e) übergeben.

Die Methoden `add_item_window()` und `configure_item_window()` werden aufgerufen, sind aber nicht definiert.

Wenn ein ``lambda`` nur die Argumente 1:1 an eine weitere Funktion oder Methode durchreicht, dann braucht man dafür keinen ``lambda``-Ausdruck, sondern kann gleich diese Funktion oder Methode verwenden.

Wenn es im `tkinter`-Modul Konstanten für Zeichenketten mit einer besonderen Bedeutung gibt, sollte man die verwenden. Das betrifft `anchor`-Argumente im Quelltext.

`is_window_open` wird nirgends verwendet.

`set_properties()` ist kein guter Name. Viel zu generisch und Tk kennt das nicht als besonderen Begriff, dafür hat „property“ in Python eine Bedeutung, die nichts damit zu tun hat, was diese Methode macht.

`print_menu()` druckt auch kein Menü oder gibt das auf der Konsole als Text aus, wie man das von der `print()`-Funktion erwarten würde.

Da beide Methoden immer zusammen, beziehungsweise direkt nacheinander benutzt werden, würde auch *eine* Methode mit einem passenden Namen ausreichen.

Die Fenstergrösse steht zweimal im Programm, einmal als Variablen, und einmal in einer Zeichenkette. Da würde man eher Konstanten für definieren.

Die Fenstergeometrie wird zweimal hintereinander gesetzt, und das wird (zusammen mit der Position) erneut gemacht, wenn der Inhalt des Fensters aktualisiert werden soll. Genau wie der Fenstertitel, der sich nicht ändert. Das muss man alles nur *einmal* machen.

Den ``%``-Operator zur Zeichenkettenformatierung würde ich nicht mehr verwenden wenn das nicht einen wirklich guten Grund dafür gibt. Schon in Python 2 hat man dafür die `format()`-Methode verwendet, und in aktuellen Python-Versionen gibt es f-Zeichenkettenliterale.

Die beiden Buttons müssen nicht an das Objekt gebunden werden.

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

Das ``if menu_items:`` ist überflüssig, denn die ``for``-Schleife wird auch nicht durchlaufen wenn man das vorher abbrüft.

Statt `item` und Indexzugriffen mit ”magischen” Zahlen, wäre es lesbarer die beiden Elemente an passende Namen zu binden.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from tkinter import ttk

WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
BACKGROUND_COLOR = "#efeaec"


class Window:
    def __init__(self, toplevel, cursor):
        self.toplevel = toplevel
        self.cursor = cursor
        self.canvas = None
        self.scrollbar = None
        self.menu_frame = None

        self.toplevel.title("Speisekarte")
        screen_width = self.toplevel.winfo_screenwidth()
        screen_height = self.toplevel.winfo_screenheight()
        x_coordinate = (screen_width / 2) - (WINDOW_WIDTH / 2)
        y_coordinate = (screen_height / 2) - (WINDOW_HEIGHT / 2)
        self.toplevel.geometry(
            f"{WINDOW_WIDTH}x{WINDOW_HEIGHT}+{x_coordinate}+{y_coordinate}"
        )
        self.toplevel.configure(bg=BACKGROUND_COLOR)
        self.create_window_content()

    def refresh_menu(self):
        for widget in self.menu_frame.winfo_children():
            widget.destroy()
        for widget in self.button_frame.winfo_children():
            widget.destroy()
        self.menu_frame.destroy()
        self.button_frame.destroy()
        self.canvas.destroy()
        self.scrollbar.destroy()
        self.create_window_content()

    def scroll_canvas(self, event):
        if event.delta:
            self.canvas.yview_scroll(1 if event.delta > 0 else -1, "units")

    def create_window_content(self):
        self.canvas = tk.Canvas(
            self.toplevel,
            bg=BACKGROUND_COLOR,
            highlightthickness=0,
            width=400,
            height=600,
        )
        self.canvas.pack(side=tk.LEFT, fill=tk.Y, expand=True)

        self.scrollbar = ttk.Scrollbar(
            self.toplevel,
            orient=tk.VERTICAL,
            cursor="arrow",
            command=self.canvas.yview,
        )
        self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        self.scrollbar.configure(command=self.canvas.yview)
        self.canvas.bind(
            "<Configure>",
            lambda _event: self.canvas.configure(
                scrollregion=self.canvas.bbox(tk.ALL)
            ),
        )
        self.canvas.bind_all("<MouseWheel>", self.scroll_canvas)
        self.scrollbar.set(0, 0)

        self.menu_frame = tk.Frame(self.canvas, bg=BACKGROUND_COLOR, width=500)
        self.menu_frame.pack(side=tk.TOP, fill=tk.X, padx=20, pady=20)

        self.button_frame = tk.Frame(
            self.canvas, bg=BACKGROUND_COLOR, width=500
        )
        self.button_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=20, pady=20)

        self.canvas.create_window(
            (0, 0), window=self.button_frame, anchor=tk.NW
        )
        self.canvas.create_window((0, 0), window=self.menu_frame, anchor=tk.SW)

        options = {"font": ("Helvetica", 16), "bg": "#bf717e", "fg": "black"}
        pack_arguments = {
            "side": tk.LEFT,
            "padx": 10,
            "pady": 10,
            "anchor": tk.W,
        }
        tk.Button(
            self.button_frame,
            text="Beenden",
            command=self.toplevel.quit,
            **options,
        ).pack(**pack_arguments)

        tk.Button(
            self.button_frame,
            text="Refresh",
            command=self.refresh_menu(),
            **options,
        ).pack(**pack_arguments)

        for category in ["Vorspeise", "Hauptspeise", "Nachspeise", "Getränk"]:
            category_label = tk.Label(
                self.menu_frame,
                text=category,
                font=("Helvetica", 16, "bold"),
                bg="#feebed",
                padx=5,
                pady=5,
            )
            category_label.pack(fill=tk.BOTH)
            category_label.bind(
                "<Button-1>",
                lambda _event, category=category: self.add_item_window(
                    category
                ),
            )
            self.cursor.execute(
                "SELECT gericht, preis FROM test_table WHERE kategorie = ?",
                (category,),
            )
            for name, preis in self.cursor.fetchall():
                label_text = f"{name:<20} {preis:>10} €"
                item_label = tk.Label(
                    self.menu_frame,
                    text=label_text,
                    font=("Helvetica", 14),
                    bg=BACKGROUND_COLOR,
                )
                item_label.pack()
                item_label.bind(
                    "<Button-1>",
                    lambda _event, category=category, name=name, preis=preis: self.configure_item_window(
                        category, name, preis
                    ),
                )
Cursor-Objekte sollten nicht so lange ”leben”. Da würde man besser die Datenbankverbindung übergeben und die Lebensdauer des Cursor-Objekts auf den Methodenaufruf beschränken.

Da wird viel zu viel von der GUI immer wieder weggeworfen und neu erstellt. Das sollte eigentlich nur den `menu_frame` und dessen Inhalt betreffen. Also *den* Frame zerstören sollte genug sein — der Inhalt ist davon ja auch betroffen. Dann nur den Frame samt Inhalt neu erstellen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten