@nichtluke: Bevor man da einen Scrollbalken einbaut, sollte man das erst einmal aufräumen.
Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
Wenn man das Hauptprogramm in eine Funktion gesteckt hat, stellt man fest das die vorhanden Funktionen alle das Problem haben, das sie auf globalen Zustand zugreifen. Alle drei brauchen `images` als Argument, und zwei davon auch noch `gray_image` und `photo_images`.
Das könnte man auch über den Default-Argument-Hack bei dem ``lambda``-Ausdruck lösen, aber genau für so etwas ist `functools.partial()` gedacht.
Das werden dann schon ganz schön viele Argumente die da herum gereicht werden. Das ist so knapp an der Grenze wo ich sagen würde das ist schon nicht mehr sinnvoll sich hier um objektorientierte Programmierung zu drücken. Da kommt man bei GUI-Programmierung nicht weit.
Andererseits wird da auch mehr durch die Gegend gereicht als notwendig ist, denn statt alle Bilder und den Zeilen- und Spaltenindex zu übergeben um dann in den Funktionen über Zeilen- und Spaltenindex auf die Daten zuzugreifen, könnte man sich diese Indirektion sparen. Und `images` dann auch als einfache, flache Liste verwalten, statt als zweidimensionale Liste. Diese Eigenschaft wird nirgends wirklich gebraucht, macht aber den Code umständlicher.
Die Bilddaten in dem Wörterbuch brauchen "row" und "col" nicht, da wird nie drauf zugegriffen. An der passenden Stelle im Quelltext kann man das auch komplett erstellen, ohne später noch das "label" ergänzen zu müssen. Und es würde Sinn machen das `PhotoImage`-Objekt mit in das Wörterbuch zu packen um das nicht separat übergeben zu müssen.
Die leere `images`-Liste wird ziemlich früh erstellt. Recht weit weg von der Stelle im Code wo die dann endlich gefüllt wird.
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.
Wenn es im `tkinter`-Modul Konstanten für Zeichenketten mit einer besonderen Bedeutung gibt, sollte man die benutzten, statt eine Zeichenkette mit dem Wert. Und auch selber Konstanten definieren für wiederkehrende Werte mit gleicher Bedeutung.
`grid_frame` wird als *einziges* in das Hauptfenster gesetzt, da macht also weder ``row=1`` Sinn, noch ``columnspan=8``.
Zum erstellen von `PhotoImage`-Objekten wird mehrmals ein fast identischer Ausdruck verwendet, für den es sich IMHO schon lohnt eine Funktion zu schreiben.
Statt `os.path` & Co würde man in neuem Code eher `pathlib` verwenden.
Das `state` und `bit_index` in den verschachtelten Schleifen zum Erstellen von den Anzeigeelementen für das Grid manuell verwaltet werden, ist unnötig unübersichtlich und Fehleranfällig. Da würde man besser die Bitwerte mit `enumerate()` aufzählen und Spalten- und Zeilenindex daraus berechnen. Das spart auch noch drei Ebenen Einrückung für den Code der ein einzelnes Anzeigeelement erzeugt.
Zwischenstand (ungetestet):
Code: Alles auswählen
#!/usr/bin/env python3
import tkinter as tk
from functools import partial
from pathlib import Path
from PIL import Image, ImageTk
STATE_FILE_PATH = Path("state.txt")
IMAGE_PATH = Path("src")
IMAGES_PER_ROW = 8
IMAGE_ROW_COUNT = 3
BACKGROUND_COLOR = "#aaf0d1"
def load_state():
try:
return list(
map(int, STATE_FILE_PATH.read_text(encoding="ascii").split())
)
except FileNotFoundError:
return [0] * (IMAGE_ROW_COUNT * IMAGES_PER_ROW)
def save_state(images_data):
STATE_FILE_PATH.write_text(
" ".join(str(int(image_data["grayed"])) for image_data in images_data)
+ "\n",
encoding="ascii",
)
def load_photo_image(file_path):
return ImageTk.PhotoImage(Image.open(file_path))
def update_image_state(image_data, gray_image):
image_data["label"].configure(
image=gray_image if image_data["grayed"] else image_data["photo_image"]
)
def on_image_click(images_data, image_data, gray_image, _event=None):
image_data["grayed"] = not image_data["grayed"]
update_image_state(image_data, gray_image)
save_state(images_data)
def main():
root = tk.Tk()
root.title("JackOfAllChamps v0.0.1")
root.iconbitmap("l.ico")
root.configure(bg=BACKGROUND_COLOR)
grid_frame = tk.Frame(
root,
bg=BACKGROUND_COLOR,
bd=0,
relief=tk.SOLID,
highlightbackground=BACKGROUND_COLOR,
highlightcolor=BACKGROUND_COLOR,
)
grid_frame.grid(row=0, column=0, pady=10)
headline_image = load_photo_image(IMAGE_PATH / "headlineJoac.png")
tk.Label(grid_frame, image=headline_image, bg=BACKGROUND_COLOR).grid(
row=0, column=0, columnspan=IMAGES_PER_ROW, pady=10
)
gray_image = load_photo_image(IMAGE_PATH / "gray_image.jpeg")
images_data = []
for i, bit_value in enumerate(load_state()):
row_index, column_index = divmod(i, IMAGES_PER_ROW)
photo_image = load_photo_image(
IMAGE_PATH / f"image_{row_index}_{column_index}.jpeg"
)
label = tk.Label(grid_frame, image=photo_image, bg=BACKGROUND_COLOR)
image_data = {
"grayed": bool(bit_value),
"label": label,
"photo_image": photo_image,
}
update_image_state(image_data, gray_image)
label.bind(
"<Button-1>",
partial(on_image_click, images_data, image_data, gray_image),
)
label.grid(row=row_index + 1, column=column_index, padx=5, pady=5)
images_data.append(image_data)
root.mainloop()
if __name__ == "__main__":
main()
Wörterbücher die immer einen festen Satz an Schlüsseln haben sind eigentlich ein Objekt und sollten in der Regel als Klasse modelliert werden.
Und es ist eine GUI, also auch da sollte man eher eine Klasse schreiben, als das über `partial()` zu lösen.
Scrollbalken für ”beliebigen” Inhalt, macht man in Tk mit `Canvas`. Da sollten Beispiele im Netz zu finden sein.