Images bei tkinter mit after

Fragen zu Tkinter.
Antworten
Duardo
User
Beiträge: 54
Registriert: Mittwoch 2. Juli 2014, 16:56

Hallo, zum einen habe ich bei diesem Programm das Problem, dass die Bilder nicht angezeigt werden und stattdessen nur ein Fehler kommt wenn sie gebraucht werden. Auch wüßte ich gerne, wie ich die beiden Bilder mit after() nacheinander zeigen könnte. Ich bin da völlig ratlos. Schonmal danke im voraus! :) Hier noch der Code:

Code: Alles auswählen

# -*- coding: cp1252 -*-
import sys
from itertools import cycle
import pygame
import Tkinter as tk
from Tkinter import *
from functools import partial
import random

pygame.init()

class SampleApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        
        self.title("test")
        self.state('zoomed')

        container= tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames={}
        for F in (fenster, mode1):
            frame= F(container, self)
            self.frames[F]=frame
            frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame(fenster)

    def show_frame(self, c):
        frame=self.frames[c]
        frame.tkraise()

class fenster(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        label=tk.Label(self, text="Das ist die Startseite")
        label.pack()

        button=tk.Button(self, text="Start",
                         command=lambda: controller.show_frame(mode1))
        button.pack()

class mode1(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        label1=tk.Label(self, text=" ")
        label1.pack()
        
        def ok(label1):
            def do_a():
                self.test1=pygame.mixer.Sound("test1.wav")
                self.test1.play()

                label2=Label(self)
                im=PhotoImage(self, file="test.gif")
                label2["image"]=im
                label2.pack()

                label1=Label(self)
                im2=PhotoImage(self, file="test2.gif")
                label1["image"]=im
                label1.pack()
                
            def do_b():
                self.test2=pygame.mixer.Sound("test2.wav")
                self.test2.play()
                
            WORDS={"test1":do_a,
                   "test2":do_b}
            choice=random.choice(list(WORDS))
            label1['text']=choice
            WORDS[choice]()

        button1=tk.Button(self, text='Knopf', command=partial(ok, label1))
        button1.pack()

if __name__== "__main__":
    app=SampleApp()
    app.mainloop()
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo.
Duardo hat geschrieben:Hallo, zum einen habe ich bei diesem Programm das Problem, dass die Bilder nicht angezeigt werden und stattdessen nur ein Fehler kommt wenn sie gebraucht werden.
Und du möchtest die Fehlermeldung natürlich unter keinen Umständen verraten, oder? ;-)

Ich habe den Code mal kurz überflogen, so wie ich das sehe, hast du einen ganz typischen Fehler gemacht. Tkinter speichert nicht die Referenzen auf Bilder, also muss sich dein Code darum kümmern. Tut er das nicht, so wird das Bild durch den Garbage Collector gelöscht und steht Tkinter nicht mehr zur Verfügung. Halte also einfach eine Referenz zu den Bildern, indem du daraus ein Attribut deiner Model-Klasse machst.

Bei deinem Code gibt es allerdings noch allerhand zu verbessern. Ich gehe das mal von oben nach unten durch. Als erstes fällt auf, dass du Tkinter doppelt importierst. Einmal als ``import Tkinter as tk`` und dann noch einmal mittels ``from Tkinter import *``. Ein Import reicht, in diesem Fall der erste. Die zweite Variante solltest du dir gar nicht erst angewöhnen, damit müllst du dir den ganzen Namensraum zu, überschreibst dir andere Namen und kannst die Herkunft von Namen nicht mehr feststellen. Das führt zu interessanten und schwer zu findenden Fehlern. Benutze also keine *-Importe.

Dann fällt auch gleich die Namensgebung auf. Nur Typen beginnen mit einem Großbuchsten, bzw. Konstanten werden durchgängig in Großbuchstaben geschrieben, alles andere klein. Das "F" in Zeile 25 sollte also nicht "F" heißen, sondern "f". Allerdings sollte es auch nicht "f" heißen, sondern ein vernünftigen, aussagekräftigen Namen bekommen. Sonst weißt du bei deinem eigenem Code in drei Monaten nicht mehr, was du damit ausdrücken wolltest. Genau anders herum ist der Fall bei deinen Klassen "fenster" und "model". Die sollten "Fenster" und "Model" heißen. Bzw., sie sollten ebenfalls Namen bekommen, welche weniger generisch sind.

Ganz allgemein solltest du dir mal PEP 8 durchlesen, dort sind einige sinnvolle Vorgaben für Python-Code aufgelistet. Die solltest du beachten, wenn du in Python programmierst. Ganz besonders dann, wenn andere deinen Code zu sehen bekommen. Wie zum Beispiel hier im Forum.

Deine ok-Funktion in Zeile 54 ist absolut gruselig. Die Verschachtelung von Funktionen in eine Funktion in einer Methode ist absolut unübersichtlich. Mache entweder eine Methode draus oder ziehe den ganzen Code in externe Funktionen. Dein ganzen Konstrukt hat außerdem den Nachteil, dass bei jedem Aufruf die Musik und die Bilder vollständig neu geladen werden müssen. Hinzu kommt, dass der Code in der "do_a"-Funktion, was wieder ein nichtssagender Name ist, quasi doppelt vorkommt. Wenn du Code kopierst oder abschreibst, dann musst du das in eine generischere Funktion auslagern. Ebenfalls solltest du hier an deiner Namensgebung arbeiten. Wenn du Namen durchnummerierst, wie label1 und label2, dann machst du etwas falsch. Entweder möchtest du dann einen richtige Datenstruktur, wie eine Liste oder ein Tupel, oder du musst richtige Namen vergeben.

Die Auswahl in Zeile 75 ist etwas umständlich. Statt der Umwandlung der Schlüssel in eine Liste, solltest du besser gleich die Items aus dem Dictionary holen. Das geht mit der items-Methode. Dann musst du in Zeile 77 nicht noch einmal auf das Wörterbuch zugreifen.
Das Leben ist wie ein Tennisball.
Duardo
User
Beiträge: 54
Registriert: Mittwoch 2. Juli 2014, 16:56

@EyDu: Erstmal vielen Dank für die schnelle und ausführliche Antwort. Hier ist der Fehlercode der auftaucht wenn man ein paar mal auf den Knopf drückt:

Code: Alles auswählen

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 1470, in __call__
    return self.func(*args)
  File "C:\Python27\tester.py", line 77, in ok
    WORDS[choice]()
  File "C:\Python27\tester.py", line 61, in do_a
    label2["image"]=im
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 1269, in __setitem__
    self.configure({key: value})
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 1262, in configure
    return self._configure('configure', cnf, kw)
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 1253, in _configure
    self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
TypeError: __str__ returned non-string (type instance)
Wenn man ein weiteres Mal auf den Knopf drückt, kommt dieser Fehlercode hinzu:

Code: Alles auswählen

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 1470, in __call__
    return self.func(*args)
  File "C:\Python27\tester.py", line 44, in <lambda>
    command=lambda: controller.show_frame(mode1))
  File "C:\Python27\tester.py", line 34, in show_frame
    frame.tkraise()
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 719, in tkraise
    self.tk.call('raise', self._w, aboveThis)
TclError: bad window path name ".41777032.41776152"
BlackJack

@Duardo: Den Quelltext kann man schlecht ausführen weil man dazu zusätzliche Dateien benötigt. Das man zwei Bilder braucht, ist nicht zu vermeiden, aber die Sound-Dateien sind zum Beispiel nicht relevant für das Problem. Streich das Programm mal soweit zusammen, dass man es nur noch das nötigste enthält um das Problem zu demonstrieren, so dass man das auch mal ausprobieren kann.
Duardo
User
Beiträge: 54
Registriert: Mittwoch 2. Juli 2014, 16:56

@BlackJack: Hier ist der reduzierte Code:

Code: Alles auswählen

# -*- coding: cp1252 -*-
import sys
from itertools import cycle
import Tkinter as tk
from Tkinter import *
from functools import partial
import random

class SampleApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        
        self.title("test")
        self.state('zoomed')

        container= tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames={}
        for F in (fenster, mode1):
            frame= F(container, self)
            self.frames[F]=frame
            frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame(fenster)

    def show_frame(self, c):
        frame=self.frames[c]
        frame.tkraise()

class fenster(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        label=tk.Label(self, text="Das ist die Startseite")
        label.pack()

        button=tk.Button(self, text="Start",
                         command=lambda: controller.show_frame(mode1))
        button.pack()

class mode1(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        label1=tk.Label(self, text=" ")
        label1.pack()

        label2=tk.Label(self, text=" ")
        label2.pack()

        
        def ok(label1):
            def do_a():
                label2=Label(self)
                im=PhotoImage(self, file="test.gif")
                label2["image"]=im
                label2.pack()

                label1=Label(self)
                im2=PhotoImage(self, file="test2.gif")
                label1["image"]=im
                label1.pack()
                
            def do_b():
                print("ngfijnwgfekö")
                
            WORDS={"test1":do_a,
                   "test2":do_b}
            choice=random.choice(list(WORDS))
            label1['text']=choice
            WORDS[choice]()

        button1=tk.Button(self, text='Knopf', command=partial(ok, label1))
        button1.pack()

if __name__== "__main__":
    app=SampleApp()
    app.mainloop()
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Das ist doch noch nicht normal, da ist noch jede Menge unnötiger Code um den Fehler zu reproduzieren. Die vermutliche Uhrsache habe ich dir in meinem langen Post ja schon genannt, vielleicht solltest du die Hinweise einfach mal umsetzen und nicht nur nach der Lösung betteln ;-)
Das Leben ist wie ein Tennisball.
BlackJack

Weil da auch nirgends `after()` drin vorkommt, hier mal ein Beispiel dafür:

Code: Alles auswählen

import Tkinter as tk


class CountdownUI(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        self.display = tk.Label(self)
        self.display.pack()
        self.start_button = tk.Button(
            self, text='Start Countdown', command=self.start_countdown
        )
        self.start_button.pack()

    def _count_down(self, value):
        if value > 0:
            text = value
            self.after(1000, self._count_down, value - 1)
        else:
            text = 'lift off'
            self.start_button['state'] = tk.NORMAL
        self.display['text'] = text

    def start_countdown(self):
        self.start_button['state'] = tk.DISABLED
        self._count_down(10)


def main():
    root = tk.Tk()
    root.title('Countdown')
    CountdownUI(root).pack()
    root.mainloop()


if __name__ == '__main__':
    main()
Antworten