tkinter, Probleme bei Bilddarstellung, Abfrage Checkboxen

Fragen zu Tkinter.
Antworten
NinoBaumann
User
Beiträge: 71
Registriert: Samstag 25. April 2020, 19:03

Hallo,

ich bin derzeit dran mich ein wenig in Python einzuarbeiten, um mir den Arbeitsalltag zu erleichtern. Hier trifft es sich gut, dass sich meine Partnerin eine Anwendung wünscht, mit der man sich Gerichte aus einer Übersicht aussuchen kann und im Nachgang eine Einkaufsliste bekommt. Ich habe mir gedacht, dass wäre eine ganz gute Übung für eine GUI. Bisher habe ich folgendes programmiert:

Code: Alles auswählen

import tkinter as tk
from PIL import Image, ImageTk

class App(tk.Frame):
    def __init__(self, master):
        super().__init__(master)
        
        self.scrollbar = tk.Scrollbar(master)
        self.scrollbar.pack(side = tk.RIGHT, fill = tk.Y )
        
        def meal(food_name, image_name):
            self.frame                      = tk.Frame(master)
            self.frame.pack()
            self.food_image                 = Image.open(image_name)
            self.food_image                 = self.food_image.resize((300,200), Image.Resampling.LANCZOS)
            self.food_image                 = ImageTk.PhotoImage(self.food_image)
            self.food_image_label           = tk.Label(self.frame, image=self.food_image)
            self.food_image_label.pack()
            
            self.food_selection             = tk.IntVar()
            self.food_checkbox              = tk.Checkbutton(self.frame, text=food_name, variable=self.var1)
            self.food_checkbox.pack()
        
        meal('Spaghetti', 'Spaghetti1.png')
        meal('Spaghetti', 'Spaghetti2.png')
        meal('Spaghetti', 'Spaghetti3.png')
        meal('Spaghetti', 'Spaghetti3.png')
        
        def check_selection():
            pass            
            
def main():
    root = tk.Tk()
    app = App(root)
    app.mainloop()

if __name__ == "__main__":
    main()
Dazu habe ich zwei Fragen:

Frage 1: Wenn ich das Programm ablaufen lasse wird nur beim letzten Frame ein Bild angezeigt. Wieso und wie kann ich das ändern?
Frage 2: Wie kann ich bspw. mit einer Funktion, in dem Fall check_selection, auf die Checkboxen zugreifen? Dies benötige ich, um im Anschluss die Einkaufsliste zu erstellen.
Gerne auch einige Knigge Regeln für den allgemeinem Umgang mit Python (Benennungen, Abstände, etc.).

Besten Dank im Voraus!
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

Funktionen definiert man nicht innerhalb von Funktionen.
Man rückt die Gleichheitszeichen nicht einheitlich ein, weil das das Lesen nur sehr schwer macht. Bei Deinem Code habe ich Probleme, die Variable mit der pasenden Anweisung zusammenzubringen.
`self.food_image` wird innerhalb von drei Zeilen dreimal überschrieben.
Und da alle Mahlzeiten die selben Attribute benutzen, werden die Attribute frame, food_image und food_selection auch viermal überschrieben.
Was Du willst, ist eine eigene Klasse für die Mahlzeiten und eine Liste, in der Du alle Mahlzeitinstanzen sammelst.
Benutzeravatar
__blackjack__
User
Beiträge: 13063
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@NinoBaumann: Da gibt es gar keine Checkbuttons weil `self.var1` undefiniert ist und zu einem `AttributeError` führt bevor ein Checkbutton erstellt werden kann. Und hier liegt auch das ”Problem” mit Frage 2: Zugriff auf die Checkboxen nützt Dir nichts, Du willst pro Checkbox eine Variable, also etwas vom Typ `tkinter.BooleanVar`, darüber kann man den Zustand des Checkbutton dann abfragen (und setzen).

Hier mal (ungetestet) eine eigene Klasse zur Anzeige einer einzelnen Mahlzeit:

Code: Alles auswählen

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

from PIL import Image, ImageTk

SELF_PATH = Path(__file__).parent


class MealFrame(tk.Frame):
    def __init__(self, master, name, image_path):
        tk.Frame.__init__(self, master)
        self.name = name
        self._food_image = ImageTk.PhotoImage(
            Image.open(image_path).resize((300, 200), Image.Resampling.LANCZOS)
        )
        tk.Label(self, image=self._food_image).pack()
        self._selected_var = tk.BooleanVar()
        tk.Checkbutton(self, text=name, variable=self._selected_var).pack()

    @property
    def is_selected(self):
        return self._selected_var.get()


class App(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)

        self.scrollbar = tk.Scrollbar(master)
        self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        self.meal_frames = []
        for name, image_filename in [
            ("Spaghetti", "Spaghetti1.png"),
            ("Spaghetti", "Spaghetti2.png"),
            ("Spaghetti", "Spaghetti3.png"),
            ("Spaghetti", "Spaghetti3.png"),
        ]:
            frame = MealFrame(master, name, SELF_PATH / image_filename)
            frame.pack()
            self.meal_frames.append(frame)

    def check_selection(self):
        for frame in self.meal_frames:
            print(frame.name, frame.is_selected)


def main():
    root = tk.Tk()
    app = App(root)
    app.mainloop()


if __name__ == "__main__":
    main()
Wobei ich persönlich ja auch die Mahlzeit selbst noch mal in einer eigenen Klasse kapseln würde, ohne irgendwelche Bezüge zu einer GUI.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
NinoBaumann
User
Beiträge: 71
Registriert: Samstag 25. April 2020, 19:03

__blackjack__ hat geschrieben: Mittwoch 31. Mai 2023, 13:33 @NinoBaumann: Da gibt es gar keine Checkbuttons weil `self.var1` undefiniert ist und zu einem `AttributeError` führt bevor ein Checkbutton erstellt werden kann. Und hier liegt auch das ”Problem” mit Frage 2: Zugriff auf die Checkboxen nützt Dir nichts, Du willst pro Checkbox eine Variable, also etwas vom Typ `tkinter.BooleanVar`, darüber kann man den Zustand des Checkbutton dann abfragen (und setzen).

Hier mal (ungetestet) eine eigene Klasse zur Anzeige einer einzelnen Mahlzeit:

Code: Alles auswählen

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

from PIL import Image, ImageTk

SELF_PATH = Path(__file__).parent


class MealFrame(tk.Frame):
    def __init__(self, master, name, image_path):
        tk.Frame.__init__(self, master)
        self.name = name
        self._food_image = ImageTk.PhotoImage(
            Image.open(image_path).resize((300, 200), Image.Resampling.LANCZOS)
        )
        tk.Label(self, image=self._food_image).pack()
        self._selected_var = tk.BooleanVar()
        tk.Checkbutton(self, text=name, variable=self._selected_var).pack()

    @property
    def is_selected(self):
        return self._selected_var.get()


class App(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)

        self.scrollbar = tk.Scrollbar(master)
        self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        self.meal_frames = []
        for name, image_filename in [
            ("Spaghetti", "Spaghetti1.png"),
            ("Spaghetti", "Spaghetti2.png"),
            ("Spaghetti", "Spaghetti3.png"),
            ("Spaghetti", "Spaghetti3.png"),
        ]:
            frame = MealFrame(master, name, SELF_PATH / image_filename)
            frame.pack()
            self.meal_frames.append(frame)

    def check_selection(self):
        for frame in self.meal_frames:
            print(frame.name, frame.is_selected)


def main():
    root = tk.Tk()
    app = App(root)
    app.mainloop()


if __name__ == "__main__":
    main()
Wobei ich persönlich ja auch die Mahlzeit selbst noch mal in einer eigenen Klasse kapseln würde, ohne irgendwelche Bezüge zu einer GUI.
Hallo blackjack,

danke für die Hilfe. Ich konnte das als sehr gutes Grundgerüst für meine weitere Bearbeitung nutzen. Vielen Dank!

Beste Grüße
Nino
NinoBaumann
User
Beiträge: 71
Registriert: Samstag 25. April 2020, 19:03

__blackjack__ hat geschrieben: Mittwoch 31. Mai 2023, 13:33 @NinoBaumann: Da gibt es gar keine Checkbuttons weil `self.var1` undefiniert ist und zu einem `AttributeError` führt bevor ein Checkbutton erstellt werden kann. Und hier liegt auch das ”Problem” mit Frage 2: Zugriff auf die Checkboxen nützt Dir nichts, Du willst pro Checkbox eine Variable, also etwas vom Typ `tkinter.BooleanVar`, darüber kann man den Zustand des Checkbutton dann abfragen (und setzen).

Hier mal (ungetestet) eine eigene Klasse zur Anzeige einer einzelnen Mahlzeit:

Code: Alles auswählen

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

from PIL import Image, ImageTk

SELF_PATH = Path(__file__).parent


class MealFrame(tk.Frame):
    def __init__(self, master, name, image_path):
        tk.Frame.__init__(self, master)
        self.name = name
        self._food_image = ImageTk.PhotoImage(
            Image.open(image_path).resize((300, 200), Image.Resampling.LANCZOS)
        )
        tk.Label(self, image=self._food_image).pack()
        self._selected_var = tk.BooleanVar()
        tk.Checkbutton(self, text=name, variable=self._selected_var).pack()

    @property
    def is_selected(self):
        return self._selected_var.get()


class App(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)

        self.scrollbar = tk.Scrollbar(master)
        self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        self.meal_frames = []
        for name, image_filename in [
            ("Spaghetti", "Spaghetti1.png"),
            ("Spaghetti", "Spaghetti2.png"),
            ("Spaghetti", "Spaghetti3.png"),
            ("Spaghetti", "Spaghetti3.png"),
        ]:
            frame = MealFrame(master, name, SELF_PATH / image_filename)
            frame.pack()
            self.meal_frames.append(frame)

    def check_selection(self):
        for frame in self.meal_frames:
            print(frame.name, frame.is_selected)


def main():
    root = tk.Tk()
    app = App(root)
    app.mainloop()


if __name__ == "__main__":
    main()
Wobei ich persönlich ja auch die Mahlzeit selbst noch mal in einer eigenen Klasse kapseln würde, ohne irgendwelche Bezüge zu einer GUI.
Hallo Blackjack,

vllt kannst Du mir nochmal helfen. Ich habe jetzt ein kleines Programm gebastelt, welches funktioniert. Alles sehr einfach, aber zweckmäßig. Es sieht so aus, dass alle Gerichte auf einer Seite sind und ich diese per Checkbox auswählen kann und mir dann über einen Button die Einkaufsliste ausgegeben werden kann. Jetzt wollte ich die Gerichte noch unterteilen in bspw. Deutsch, Italienisch, Asiatisch usw.. Also auch diese Gerichte auf separate Seiten darstellen. Ich sende jetzt mal einen Teil des Codes. Die anderen Klassen wie "Italienisch", "Asiatisch" usw sind gleich aufgebaut wie "Deutsch". Meine Frage ist, wie muss ich die Funktion auf der Startseite definieren, dass ich die in auf Seiten, bspw Deutsche Seite, ausgewählten Gerichte erhalte? Das ist mir nicht ganz verständlich. Ich habe die betreffenden Zeilen im Code markiert.

Code: Alles auswählen

import tkinter as tk
from tkinter import ttk
from pathlib import Path
from collections import Counter
from PIL import Image, ImageTk

SELF_PATH = Path(__file__).parent
LARGEFONT =("Verdana", 35)

class meal():
    def __init__(self, calories, meal_name, ingredient_list):
        self.meal_name = meal_name
        self.meal_image_name = str(meal_name) + '.png'
        self.calories = calories
        self.ingredient_list = ingredient_list

# Deutsch ---------------------------------------------------
Spaghetti = meal(550, 'Spaghetti', {
  "Spaghetti [g]": 200,
  "Tomatensosse [g]": 200,
  'Tomatenmark [g]': 10,
  'Basilikum [g]': 7.5,
  'Knoblauch [Zh.]': 0.5,
  "Zwiebeln [Stk.]": 0.5})

german_meals = [Spaghetti, Spaghetti]

# Asiatisch -------------------------------------------------

asian_meals = [Spaghetti, Spaghetti]

class MealFrame(tk.Frame):
    def __init__(self, master, name, image_path):
        tk.Frame.__init__(self, master, highlightbackground="grey", highlightthickness=2)
        self.name = name
        self._food_image = ImageTk.PhotoImage(
            Image.open(image_path).resize((300, 200), Image.Resampling.LANCZOS)
        )
        tk.Label(self, image=self._food_image).pack()
        self._selected_var = tk.BooleanVar()
        tk.Checkbutton(self, text=name, variable=self._selected_var).pack(side = tk.LEFT)
        self._number_servings = tk.Entry(self, justify='center', width=10)
        self._number_servings.insert(0, 2)
        self._number_servings.pack(side = tk.RIGHT)
        tk.Label(self, text='Portionen: ', bg="light grey").pack(side = tk.RIGHT)
        
    @property
    def is_selected(self):
        return self._selected_var.get()
    @property
    def is_quantified(self):
        return self._number_servings.get()

class tkinterApp(tk.Tk):
     
    # __init__ function for class tkinterApp
    def __init__(self, *args, **kwargs):
         
        # __init__ function for class Tk
        tk.Tk.__init__(self, *args, **kwargs)
         
        # creating a container
        container = tk.Frame(self) 
        container.pack(side = "top", fill = "both", expand = True)
  
        container.grid_rowconfigure(0, weight = 1)
        container.grid_columnconfigure(0, weight = 1)
  
        # initializing frames to an empty array
        self.frames = {} 
  
        # iterating through a tuple consisting
        # of the different page layouts
        for F in (Startseite, Deutsch, Asiatisch, Vegetarisch, Italienisch, Fast_Food):
  
            frame = F(container, self)
  
            # initializing frame of that object from
            # Startseite, Deutsch, Asiatisch, Vegetarisch, Italienisch, Fast_Food respectively with
            # for loop
            self.frames[F] = frame
  
            frame.grid(row = 0, column = 0, sticky ="nsew")
  
        self.show_frame(Startseite)
  
    # to display the current frame passed as
    # parameter
    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()
  
# first window frame Startseite
  
class Startseite(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
         
        label = ttk.Label(self, text ="Startseite", font = LARGEFONT)
        label.grid(row = 0, column = 0, padx = 10, pady = 10)
  
        button1 = ttk.Button(self, text ="Deutsch", 
                             command = lambda : controller.show_frame(Deutsch))
        button1.grid(row = 1, column = 0, padx = 10, pady = 10)
  
        button2 = ttk.Button(self, text ="Asiatisch",
                             command = lambda : controller.show_frame(Asiatisch))
        button2.grid(row = 2, column = 0, padx = 10, pady = 10)
        
        button3 = ttk.Button(self, text ="Vegetarisch",
                             command = lambda : controller.show_frame(Vegetarisch))
        button3.grid(row = 3, column = 0, padx = 10, pady = 10)
        
        button4 = ttk.Button(self, text ="Italienisch",
                            command = lambda : controller.show_frame(Italienisch))
        button4.grid(row = 4, column = 0, padx = 10, pady = 10)
        
        button5 = ttk.Button(self, text ="Fast Food",
                            command = lambda : controller.show_frame(Fast_Food))
        button5.grid(row = 5, column = 0, padx = 10, pady = 10)
        
        ---------------------------------------------------- Auf diesen Abschnitt bezieht sich meine Frage ------------------------------------------------------------------------------
        def check_selection():
            list_meals = []
            for frame in self.meal_frames:
                if frame.is_selected == True:
                    list_meals.append(frame.name)

                        
        tk.Button(self, text='Einkaufsliste ausgeben', bg="powder blue", command=check_selection).grid(row=6, column=0, pady=10)
        ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        
class Deutsch(tk.Frame):
     
    def __init__(self, parent, controller):
         
        tk.Frame.__init__(self, parent)
        
        label = ttk.Label(self, text ="Deutsch", font = LARGEFONT)
        label.grid(row = 0, column = 0, padx = 10, pady = 10)
  
        button1 = ttk.Button(self, text ="Startseite",
                            command = lambda : controller.show_frame(Startseite))
        button1.grid(row = 1, column = 0, padx = 10, pady = 10)
  
        button2 = ttk.Button(self, text ="Asiatisch",
                            command = lambda : controller.show_frame(Asiatisch))
        button2.grid(row = 2, column = 0, padx = 10, pady = 10)
        
        button3 = ttk.Button(self, text ="Vegetarisch",
                            command = lambda : controller.show_frame(Vegetarisch))
        button3.grid(row = 3, column = 0, padx = 10, pady = 10)
        
        button4 = ttk.Button(self, text ="Italienisch",
                            command = lambda : controller.show_frame(Italienisch))
        button4.grid(row = 4, column = 0, padx = 10, pady = 10)
        
        button5 = ttk.Button(self, text ="Fast Food",
                            command = lambda : controller.show_frame(Fast_Food))
        button5.grid(row = 5, column = 0, padx = 10, pady = 10)
  
        self.meal_frames = []
        column_count = 5
        start_row_number = 6
        row_number = start_row_number
        
        for i in range(len(german_meals)):
            
            frame = MealFrame(self,
                              german_meals[i].meal_name, SELF_PATH / german_meals[i].meal_image_name)
            
            if i % column_count == 0 and i >= column_count:
                row_number = row_number+1
            column_number = i-((row_number-start_row_number)*column_count)
            
            frame.grid(row=row_number, column=column_number, padx=5, pady=5)
            self.meal_frames.append(frame)
            .
.
.
.
.
def main():
    app = tkinterApp()
    app.mainloop()


if __name__ == "__main__":
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13063
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@NinoBaumann: Das Vorgehen ist falsch. Es gibt ja gar keinen wirklichen programmatischen Unterschied zwischen Deutschen, Italienischen, … Gerichten, also auch keinen Grund für jede Sprache eine eigene Klasse zu schreiben in der dann effektiv das gleiche steht. Die Nationalität der Gerichte ist nichts für Code, sondern das sind Daten. Da könnte man ein Wörterbuch schreiben das Nationalität auf eine Datenstruktur mit Gerichten abbildet.

Um eine Auswahl von Gerichten anzuzeigen braucht man nur eine Klasse, von der man dann für jede Nationalität ein Exemplar erstellt und dem die Gerichte als Argument übergibt.

Das Umschalten zwischen den Nationalitäten gehört nicht in diese Klasse, denn auch das ist ja bei allen gleich, und wenn man eine weitere Nationalität hinzufügen möchte, oder den Namen ändern möchte, oder eine entfernen möchte, müsste man alle anderen Nationalitätsklassen auch anfassen und ändern.

Mir scheint Du versuchst da ein `ttk.Notebook` selbst noch mal erfinden zu wollen. Nimm einfach das.

Der Klassenname `meal` sollte mit einem Grossbuchstaben anfangen, wie ein Klassenname halt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten