Time events auslösen / Film aus Images abspielen

Fragen zu Tkinter.
Antworten
Anton
User
Beiträge: 4
Registriert: Mittwoch 7. November 2012, 14:32

Hi,

ich bin Python-Neuling und möchte gern Images hintereinander anzeigen lassen
(Film abspielen). Ich habe ein Beispiel gefunden, wo man durch Mausklicks jeweils
das nächste Image anzeigen lassen kann (s.u.). Möchte nun aber anstatt der
Mausklick-Events Zeitevents als Auslöser nehmen, habe aber nichts gefunden,
was mir dabei hilft.
(Wie) Kann ich mir selbst events herstellen (hier wird ja das vorgegebene <Button>
event verwendet)?
Oder hat jemand vielleicht eine andere Idee? In Matlab gibt es movies, da ist es
extrem simpel, man schreibt einfach nur seine Bilder in eine große Variable und läßt
dann alles laufen. Gibt es so etwas einfaches auch in Python? Ich habe gesucht
und nichts gefunden.

ich bin für jeden Hinweis dankbar,
Anton

Code: Alles auswählen

import os, sys
import Tkinter
import Image, ImageTk

def myevent (event):
    event.widget.quit() # this will cause mainloop to unblock.

root = Tkinter.Tk()
root.bind("<Button>", myevent)
root.geometry('+%d+%d' % (100,100))
dirlist = os.listdir('.')
old_label_image = None

for f in dirlist:
    try:
        image1 = Image.open(f)
        root.geometry('%dx%d' % (image1.size[0],image1.size[1]))
        tkpi = ImageTk.PhotoImage(image1)
        label_image = Tkinter.Label(root, image=tkpi)
        label_image.place(x=0,y=0,width=image1.size[0],height=image1.size[1])
        root.title(f)
        if old_label_image is not None:
            old_label_image.destroy()
        old_label_image = label_image
        root.mainloop() # wait until user clicks the window
    except Exception, e:
        # This is used to skip anything not an image.
        # Image.open will generate an exception if it cannot open a file.
        # Warning, this will hide other errors as well.
        pass
Zuletzt geändert von Anonymous am Mittwoch 7. November 2012, 16:05, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
BlackJack

@Anton: Jedes `Widget` hat eine `after()`-Methode, mit der man eine Funktion zeitverzögert ausführen kann.

Der gezeigte Quelltext ist IMHO nicht besonders schön. Mit `geometry()` und `place()` werden Sachen gemacht, die man auch Tkinter selbst erledigen lassen kann. `geometry()` ist ja nur nötig weil bei `place()` sich die Grösse des Containerwidgets sich nicht selbst anpasst. Statt ständig `Lable` zu erstellen und zu zerstören könnte man auch eines erstellen und da halt das Bild austauschen. Und immer neue `mainloop()`\s starten und wieder verlassen sieht mir nicht danach aus, als wenn man das gut in ein grösseres Programm integrieren kann.
Anton
User
Beiträge: 4
Registriert: Mittwoch 7. November 2012, 14:32

Danke, mit der after-Methode hat's geklappt! (über die anderen Sachen muss ich
erst noch in Ruhe nachdenken)
Anton
User
Beiträge: 4
Registriert: Mittwoch 7. November 2012, 14:32

geometry und place habe ich rausgenommen. Ohne den Label zu zerstören habe ich es aber nicht
hingekriegt. Hinzu kommt, dass ich es zwar für normale Labels so hinkriege, dass root.mainloop()
außerhalb von printsomething steht:

Code: Alles auswählen

import Image, ImageTk

old_label = None
image_number = 0

def printsomething():
    global old_label
    global image_number

    image_name = ""
    if image_number<10:
        image_name = 'anim00' + str(image_number) + '.png'
    elif image_number<100:
        image_name = 'anim0' + str(image_number) + '.png'        
    elif image_number<250:
        image_name = 'anim' + str(image_number) + '.png'
    else:
        image_number=0

    ausgabe = image_name
    print "counter = " , image_number
    label = Tkinter.Label(root,text=ausgabe)
    label.pack()  
    
    if old_label is not None:
        old_label.destroy()
    old_label = label
    image_number += 1
    root.after(10,printsomething)
    #root.mainloop()
 
root = Tkinter.Tk()
printsomething()
root.mainloop()
ich muss aber root.mainloop() in printsomething() schreiben, wenn ich Images benutze
(sonst werden die Images nicht angezeigt):

Code: Alles auswählen

import Image, ImageTk

old_label = None
image_number = 0

def printsomething():
    global old_label
    global image_number

    image_name = ""
    if image_number<10:
        image_name = 'anim00' + str(image_number) + '.png'
    elif image_number<100:
        image_name = 'anim0' + str(image_number) + '.png'        
    elif image_number<250:
        image_name = 'anim' + str(image_number) + '.png'
    else:
        image_number=0

    ausgabe = image_name
    print "counter = " , image_number
    my_image = Image.open(image_name)
    my_image_tk = ImageTk.PhotoImage(my_image)
    label = Tkinter.Label(root, image=my_image_tk)
    label.pack()  
    
    if old_label is not None:
        old_label.destroy()
    old_label = label
    image_number += 1
    root.after(10,printsomething)
    root.mainloop()
 
root = Tkinter.Tk()
printsomething()

Das ist aber eine Rekursion und die wird nach 122 Bildern abgebrochen mit einer Exception (maximale
Rekursionstiefe erreicht).

Hat noch jemand Tipps?

Anton
Zuletzt geändert von Anonymous am Mittwoch 7. November 2012, 19:40, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
BlackJack

@Anton: Vergiss am besten gleich wieder, dass es ``global`` gibt. Wenn man ”globalen” Zustand benötigt, sollte man eine Klasse dafür verwenden, denn wirklich globalen Zustand braucht man normalerweise nicht.

Du musst das Erstellen des `Label` vom setzen des Bildes trennen, denn das eine musst Du ja nur einmal machen und das andere für jedes neue Bild.

Die ``if``/``elif``\s beim erstellen des Namens kann man mit Zeichenkettenformatierung mit dem ``%``-Operator oder der `format()`-Methode auf Zeichenketten deutlich vereinfachen. Da kann man angeben wie viele Stellen die Darstellung der Zahl haben soll und dass mit führenden Nullen aufgefüllt werden soll. Zeichenketten und Werte mit ``+`` und `str()` zusammen zu setzen hat etwas von BASIC.

Das mit der `mainloop()` liegt daran, dass Du auf Python-Seite eine Referenz auf das Bildobjekt behalten musst, sonst kann der Speicher dafür wieder freigegeben werden und die Tk-Seite hat nichts mehr zum anzeigen. Auch das spricht wieder für eine Klasse.

Bei GUI-Programmierung kommt man ohne Anwendung von objektorientierter Programmierung normalerweise nicht all zu weit.

Edit (ungetestet weil mir die Bilder fehlen :-)):

Code: Alles auswählen

#!/usr/bin/env python
import Tkinter as tk
from PIL import Image, ImageTk


class AnimatedImage(tk.Label):
    def __init__(
        self, master, filename_template, max_image_number, delay, **kwds
    ):
        tk.Label.__init__(self, master, **kwds)
        self.filename_template = filename_template
        self.image_number = 0
        self.max_image_number = max_image_number
        self.delay = delay
        self.image = None
        self.step()
    
    def step(self):
        self.image = ImageTk.PhotoImage(
            Image.open(self.filename_template % self.image_number)
        )
        self['image'] = self.image
        self.image_number = (self.image_number + 1) % self.max_image_number
        self.after(self.delay, self.step)


def main():
    root = tk.Tk()
    animated_image = AnimatedImage(root, 'anim%03d.png', 250, 50)
    animated_image.pack()
    root.mainloop()


if __name__ == '__main__':
    main()
Anton
User
Beiträge: 4
Registriert: Mittwoch 7. November 2012, 14:32

@BlackJack

es läuft ... :-)

Tja, dann danke ich vielmals. Auch für die Erklärungen, die muss
ich erst noch in Ruhe durchgehen.

Anton
Antworten