mehrere Buttons mit Bild erstellen

Fragen zu Tkinter.
Antworten
christian76
User
Beiträge: 2
Registriert: Montag 1. März 2021, 14:12

Hallo,

ich bin neu hier im Forum. Daher stelle ich mich mal kurz vor.
Ich bin 44, aus dem Südwesten Deutschlands und interessiert in Elektronik, Computern und Sport.

Und jetzt zu meinem Anliegen. Programmiert habe ich schon ein wenig in Visual-Basic und PHP. Aber Python ist richtig neu für mich.
Ein wenig eingelesen habe ich mich aber bei weitem nicht alles auf Anhieb verstanden.
Warum Python? Weil ich einen Raspberry habe und dort mit dem I2C Protokoll arbeite und sich da für mich Python anbietet.
Ich möchte ein Feld mit mehreren Buttons mit Bildern erstellen. Die Daten kommen aus einer CSV Textdatei und werden in eine Liste eingelesen.
Soweit funktioniert das auch. Ich komme nur beim Erstellen der Buttons nicht weiter.
Hier mal mein Code

Code: Alles auswählen

def button_erstellen():
    for zeile in spielplan:
        button = str(zeile[0])
        anweisung = str(zeile[6])
        x = zeile[2]
        y = zeile[3]
        bild = zeile[4]
        rotation = zeile[5]

        img = Image.open('res/' + bild + '.gif')
        # img = img.resize(newsize)
        # img = img.rotate(int(rotation))
        tkimg = ImageTk.PhotoImage(img)

        cmd = partial(button_click, button, anweisung)
        Button(rightFrame, image=tkimg, command=cmd).grid(column=y, row=x, padx=0, pady=0)
In Spielplan sind die Daten die ich benötige. Wie gesagt, das passt auch soweit. Ich bekomme nur die Buttons mit den Bildern nicht angezeigt. Da erscheint nur ne graue Fläche ohne Funktion.
Wenn ich mir drei Buttons händisch erstelle wie in der Schleife funktioniert es. Irgendwo ist ein Fehler beim Erstellen der Bilder. Muss das auch Partial gemacht werden?

Vielleilleicht hat ja von euch einer eine Idee.
Gruß Christian
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ein (ärgerlicher) Klassiker in tkinter. Warum man das nicht mal repariert in der Standardbibliothek, ist mir ein Rätsel.

Das Problem ist garbage collection. Das PhotoImage wird nicht festgehalten, und darum abgeräumt. Ein Weg damit zu umgehen ist, dem Button einfach eine Referenz auf das image anzuheften. Dann wird es am Leben erhalten.

Code: Alles auswählen

button = Button(....)
button.image = tkimg
ACHTUNG: du machst den Button & rufst dann sofort grid auf dem auf. Das ist an sich ok, nur liefert Grid None zurück. Darum muss für den Fix erst der Button erzeugt, das image angeheftet & dann mit grid gearbeitet werden.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Alles was eine Funktion braucht, muß sie über ihre Argumente bekommen, `spielplan` und `rightFrame` kommen aus dem Nichts.
Variablennamen schreibt man nach Konvention komplett klein.

`column` `y` und `row` `x` zu nennen ist verwirrend. Warum nicht `column` und `row`?
PhotoImage-Instanzen müssen irgendwo gespeichert werden, damit der Speicher und damit das Bild nicht abgeräumt werden. Wenn es nicht anders geht, kann man das an das Bild an das Widget binden.

Code: Alles auswählen

def button_erstellen(spielplan, right_frame):
    for button, _, column, row, bild, rotation, anweisung in spielplan:
        # image = Image.open(f'res/{bild}.gif').resize(newsize).rotate(int(rotation))
        # image = ImageTk.PhotoImage(image)
        image = tk.PhotoImage(file=f'res/{bild}.gif')
        cmd = partial(button_click, button, anweisung)
        button = Button(right_frame, image=image, command=cmd)
        button.image = image
        button.grid(column=column, row=row, padx=0, pady=0)
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__deets__: Ich vermute das wird nicht repariert weil man das nicht reparieren *kann*. Dafür bräuchte man einen (effizienten) Weg heraus zu finden ob Tk/Tcl die Bilddaten noch braucht oder nicht. Alternative wäre `PhotoImage` und `Image` Objekte in Python nie freizugeben auch wenn sie in Python selbst eigentlich nicht mehr erreichbar wären. Dann würden sich Leute über das Speicherleck beschweren.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

@__blackjack__: kann ich mir schwer vorstellen, dass das nicht gehen soll. Wenn liegt der Grund das nicht zu machen in einer Generizität des Bindings. All configure calls are equal, oder so etwas. Und das könnte man sicher punktuell ändern.
christian76
User
Beiträge: 2
Registriert: Montag 1. März 2021, 14:12

Hallo,

vielen Dank für die schnellen Antworten. Das hat mich wirklich weitergebracht.
Wenn einem das "Garbage Collection Problem" bewusst ist und man den kleinen Umweg kennt passt das ja.

Nochmals herzlichen Dank.
Grüße Christian
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__deets__: Tcl hat keine automatische Speicherverwaltung, das heisst wenn das Bild existiert, dann existiert das solange bis man es explizit löscht. Man kann auch nicht testen ob das irgendwo innerhalb von Tk/Tcl, über den Namen dem man dem gegeben hat hinaus, verwendet wird. Damit bleiben für jemanden der eine Anbindung schreibt nur die beiden Möglichkeiten die Lebensdauer entweder an das Python-Objekt zu binden, oder es ”unendlich” leben zu lassen, mit der Möglichkeit für den Programmierer das manuell auf der Tk/Tcl-Seite zu löschen. Letzteres mit den Nebeneffekten, das man a) `Image`- und `PhotoImage`-Objekte in Python haben kann die so etwas wie ein NULL-Pointer sind, nachdem man eine `delete()`-Methode darauf aufgerufen hat, und b) wenn das Python-Proxy-Objekt weg ist, man das Bild nicht mehr löschen kann → Speicherleck.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Also ist es technisch unmöglich von Button abzuleiten, und das management des image Attributes zentralisiert zu erledigen? Wenn ja, wie schreibt man dann eine Anwendung, die korrekt mit Images umgeht? Wenn es doch möglich ist, dann ist es auch möglich, das in tkinter zu machen. Oder was verpasse ich da?
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__deets__: Ich weiss jetzt nicht was Du meinst? Was hat denn `Button` damit zu tun?
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Mir geht es darum, dass der empfohlene Weg (wie hier), eine Referenz auf das Bild zu halten, ein Attribut auf dem Button anzulegen ist. Das kann doch statt der User von Button der Button selbst machen. Was spräche dagegen? Und natürlich bezieht sich das auch auf alle anderen Elemente, die Bilder nutzen können. Aber das sind ja endlich viele.
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__deets__: Wäre letztendlich aber auf `tkinter` und Tk/Tcl ohne weitere Tk/Tcl-Packages beschränkt und man darf dann nicht an den Python-Objekten vorbei etwas ändern. Und müsste schauen ob das auch wenn man das selbst nicht wissentlich macht, nicht trotzdem passieren kann.

Und wie sieht das beispielsweise bei `Canvas`-Objekten aus, und `Text`, wo man ja beliebig viele Bilder einfügen kann und denen auch mehrere Id's/Tags verpassen kann über die die dann erreichbar sind. Bekommt man da wirklich eine ordentliche ”parallele” Buchführung hin? Und auch dabei gilt dann wieder das da nie irgendwas an den Python-Objekten vorbei passieren darf.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es ist immer ein Problem, unterschiedliche Resourcemanagmentparadigmen zu vereinen. Auch in den Qt-Wrappern stecken ja die ein oder anderen Fallstricke. Aber etwas, das mit solcher Regelmäßigkeit auftaucht, nicht zu machen, weil es auch esoterischer Fälle gibt - das finde ich ich nicht schlüssig. Und so weit ich weiß geht zB SIP durchaus hin und Mappt Proxy auf Objekt, und oder umgekehrt, weil es für die einfache Nutzung eben sein muss.

Ich denke mal nüchterner weise ist die Ursache, dass da nix passiert, das es einfach kaum maintained wird. Das ist auf Autopilot.
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Also ich finde die Ecken und Kanten an `tkinter` auch gut: Es ist für kleine Sachen nutzbar, aber man hat Argumente warum man auf ein ordentliches, modernes GUI-Rahmenwerk wechseln sollte. Sozusagen das `RPi.GPIO` unter den GUI-Rahmenwerken. 🙂
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Antworten