images list / dropdown menü/ select as background

Fragen zu Tkinter.
Antworten
SkyEnd
User
Beiträge: 5
Registriert: Freitag 12. August 2022, 19:13

Hallo,

ich habe Bilder geladen und in einer Liste gespeichert. Im Dropdownmenu möchte ich ein Bild auswählen und diese als Hintergrund festlegen.

Baustellen:
  • Elemente werden als Pyimage im Dropdownmenu angezeigt, lieber wäre mir der Name aus der Liste
  • Fehlermeldung: AttributeError: 'PhotoImage' object has no attribute 'set' #sofern ich die Globale Variable als PhotoImage "wähle"
  • Fehlermeldung: _tkinter.TclError: image "PY_VAR0" doesn't exist #sofern ich die Globale Variable als StringVar()"wähle"
Code:

Code: Alles auswählen

from tkinter import *


#creat GUI interface
root = Tk()
root.title("1 WLT - Paring")
root.iconbitmap("wesnoth_editor-icon.ico")
root.configure(bg="#272a33")
root.geometry("1400x700")
root.resizable(False, False)


#global vars
Gamemode = StringVar()


#8 Players
bg1 = PhotoImage(file ="brackets/2x4 DE Top4.png")
bg2 = PhotoImage(file ="brackets/Swiss Top4.png")
bg3 = PhotoImage(file ="brackets/bo3 Top4.png")
#4 Players
bg4 = PhotoImage(file ="brackets/4 DE.png")
bg5 = PhotoImage(file ="brackets/bo3 Top2.png")
bg6 = PhotoImage(file ="brackets/RR 3 cases.png")

bg= [bg1, bg2, bg3, bg4, bg5, bg6]

print(type(bg[1]))


def background():
    lbl_bg=Label(root, image=Gamemode).place(x=0, y=0, relwidth=1, relheight=1)    

    
lbl_bg=OptionMenu(root, Gamemode, *bg)
lbl_bg.config(bg = "RED", activebackground = "RED", fg="BLUE", activeforeground="BLUE", font=('arial', 12), width=10)
lbl_bg.place(x=10, y= 50)

btt_bg = Button(root, text="Gamemode", command=background).pack()


root.mainloop()
Ich hoffe mir kann hier jemand weiterhelfen.

lg SkyEnd
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@SkyEnd: Sternchen-Importe sind Böse™. Da holt man sich gerade bei `tkinter` fast 140 Namen ins Modul von denen nur ein kleiner Bruchteil verwendet wird. Auch Namen die gar nicht in `tkinter` definiert werden, sondern ihrerseits von woanders importiert werden. Das macht Programme unnötig unübersichtlicher und fehleranfälliger und es besteht die Gefahr von Namenskollisionen.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Man verwendet keine globalen Variablen. Alles was eine Funktion oder Methode ausser Konstanten benötigt, sollte als Argument(e) übergeben werden. Bei jeder nicht-trivialen GUI kommt man deshalb nicht um objektorientierte Programmierung (OOP) herum. Also eigene Klassen schreiben.

Beim Kommentar ``# creat GUI interface`` fehlt ein "e" bei "create" und das "interface" ist zu viel, denn das "I" in "GUI" steht ja schon für "interface". Aber letztlich kann man sich den Kommentar auch komplett schreiben, denn das sieht man ja am Code was da passiert.

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.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Man nummeriert keine Namen. Man will sich dann entweder sinnvolle Namen überlegen, oder gar keine Einzelnamen und -werte verwenden, sondern eine Datenstruktur. Oft eine Liste. Bei `bg1` bis `bg6` ganz offensichtlich eine Liste, denn da landen die Werte letztlich. Warum vorher an nummerierte Namen binden?

Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur daraus bestehen. Wenn man `background_images` meint, sollte man nicht nur `bg` schreiben. Und wieso ein `OptionMenu`-Objekt` an den Namen `lbl_bg` gebunden werden sollte, verstehe ich gar nicht. Wofür ist denn `lbl` hier die Abkürzung?

Wie Du selbst bemerkt hast, sind `PhotoImage`-Objekte keine sinnvollen Werte für ein `OptionMenu`. Da bräuchte man zwei Datenstrukturen. Einmal die Texte die angezeigt werden sollen, und dann beispielsweise ein Wörterbuch das die Texte auf die passenden `PhotoImage`-Objekte abbildet.

Warum ist beim `OptionMenu` ein getrennter `config()`-Aufruf? Das kann man beim erstellen des Objekts doch bereits alles angeben.

`btt_bg` im Hauptprogramm und `lbl_bg` in der `background()`-Funktion sind unsinnig, weil da der Wert `None` dran gebunden wird. Das ist der Rückgabewert von der `pack()`-Methode.

Das vorgehen jedes mal ein neues `Label` zu erstellen ist falsch. Man erstellt das `Label` einmal und ändert dort dann einfach das Bild, das in dem Label angezeigt wird.

Funktions- und Methodennamen werden üblicherweise nach der Tätigkeit benannt, die sie durchführen, damit der Leser weiss was dort gemacht wird, und um sie von eher passiven Werten unterscheiden zu können. `background` wäre ein guter Name für einen Wert der einen Hintergrund repräsentiert, aber nicht für eine Funktion, die einen Hintergrund setzt oder aktualisiert.

Ungetestet:

Code: Alles auswählen

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

IMAGE_DIRECTORY_PATH = Path("brackets")


def update_background(text_to_image, label, text_var):
    label["image"] = text_to_image[text_var.get()]


def main():
    root = tk.Tk()
    root.title("1 WLT - Paring")
    root.iconbitmap("wesnoth_editor-icon.ico")
    root.configure(bg="#272a33")
    root.geometry("1400x700")
    root.resizable(False, False)

    game_mode_var = tk.StringVar()

    game_mode_texts = [
        # 8 Players
        "2x4 DE Top4",
        "Swiss Top4",
        "bo3 Top4",
        # 4 Players
        "4 DE",
        "bo3 Top2",
        "RR 3 cases",
    ]
    game_mode_text_to_image = {
        game_mode_text: tk.PhotoImage(
            file=IMAGE_DIRECTORY_PATH / f"{game_mode_text}.png"
        )
        for game_mode_text in game_mode_texts
    }

    background_label = tk.Label(root)
    background_label.place(x=0, y=0, relwidth=1, relheight=1)

    tk.OptionMenu(
        root,
        game_mode_var,
        *game_mode_texts,
        bg="RED",
        activebackground="RED",
        fg="BLUE",
        activeforeground="BLUE",
        font=("Arial", 12),
        width=10,
    ).place(x=10, y=50)

    tk.Button(
        root,
        text="Gamemode",
        command=partial(
            update_background,
            game_mode_text_to_image,
            background_label,
            game_mode_var,
        ),
    ).pack()

    root.mainloop()


if __name__ == "__main__":
    main()

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
SkyEnd
User
Beiträge: 5
Registriert: Freitag 12. August 2022, 19:13

@__blackjack__ danke für Ihre nützlichen Tips ^^.
Wie Sie merkten, bin ich Anfänger. Objektorientierte Programmierung verstehe ich leider noch nicht.
Das mit dem Sternchen-Import habe ich gar nicht bedacht. Da ging es mir nur darum weniger schreiben zu müssen. Jetzt weiß ich wie schlecht das ist.

Den Code für das OptionMenu habe ich in einem Tutorial gesehen und übernommen. Wieso das config() extra aufgerufen wird weiß ich nicht, aber vielleicht wird es nicht anders erkannt.

Leider funktioniert der code an dieser Stelle nicht.
Fehlermeldung: TclError: unknown option -bg
Die erste Option nach dem *game_mode_texts wird nicht erkannt.
__blackjack__ hat geschrieben: Freitag 12. August 2022, 23:46 Warum ist beim `OptionMenu` ein getrennter `config()`-Aufruf? Das kann man beim erstellen des Objekts doch bereits alles angeben.

Code: Alles auswählen

    tk.OptionMenu(
        root,
        game_mode_var,
        *game_mode_texts,
        bg="RED",
        activebackground="RED",
        fg="BLUE",
        activeforeground="BLUE",
        font=("Arial", 12),
        width=10,
    ).place(x=10, y=50)
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@SkyEnd: Man muss bei `OptionMenu` tatsächlich `configure()` oder `config()` getrennt aufrufen, weil das zwar von `Menubutton` erbt, aber Schlüsselwortargumente nicht an dessen `__init__()` durchreicht. Das überspringt die `Menubutton.__init__()` einfach und ruft selbst direkt `Widget.__init__()` auf. Schönes Beispiel für eine schlechte OOP-Lösung.

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Antworten