Seite 2 von 2
Verfasst: Dienstag 16. Februar 2010, 22:01
von BlackJack
@Goswin: Das ist doch logisch, dass die Schleife die Animation "aufhält", denn wenn die Schleife läuft, dann läuft ja ganz offensichtlich nicht die Tkinter-Hauptschleife. Da GUI-Kram immer im gleichen Thread passieren muss, kann entweder nur Dein Code etwas tun, oder die GUI-Hauptschleife.
Verfasst: Mittwoch 17. Februar 2010, 13:56
von Goswin
BlackJack hat geschrieben:@Goswin: Das ist doch logisch, dass die Schleife die Animation "aufhält", denn wenn die Schleife läuft, dann läuft ja ganz offensichtlich nicht die Tkinter-Hauptschleife. Da GUI-Kram immer im gleichen Thread passieren muss, kann entweder nur Dein Code etwas tun, oder die GUI-Hauptschleife.
Ist ja toll, was für euch Insider alles logisch ist

Leider muss ich meine Schleife unterbrechen können, indem ich auf einen Button drücke. Andere GUIs können das, folglich muss es irgendeine Art geben, das zu machen. Sollte die undurchsichtige after()-Methode *befürcht* vielleicht die einzige Möglichkeit dazu sein?
Verfasst: Mittwoch 17. Februar 2010, 14:04
von wuf
Hallo Goswin
Es gibt sicher noch andere Möglichkeiten als mit der after()-Methode. Hier ein Lösungsansatz der nur auf after()-Methoden basiert:
http://paste.pocoo.org/show/179359/
Das ganze kann natürlich noch optimiert evtl. vereinfacht werden.
Gruss wuf

Verfasst: Mittwoch 17. Februar 2010, 18:18
von Goswin

Geschafft
Es geht auch OHNE die after()-Methode, einfach und direkt, im Anschluss folgt mein Code.
Aber ich weiß noch immer nicht genau, warum es jetzt funktioniert und vorher nicht. Wenn ich Blackjack richtig verstanden habe, dürfte der Code unten nämlich nicht funktionieren, weil die while-Schleife angeblich nicht unterbrochen werden kann. Unter anderem habe ich update_idletasks() durch update() ersetzt, aber ob so etwas die Schleife unterbricht, bin ich mir nicht sicher.
Falls das Ganze einen Pferdefuß hat, lasst es mich bitte wissen...
Code: Alles auswählen
#!/usr/bin/python3
import tkinter as t
from math import pi,cos
from time import sleep
class Oval(object):
def __init__(self, canvas, cx, cy, rx, ry, color='black'):
#
self.canvas = canvas
self.cx = cx
self.cy = cy
self.rx = rx
self.ry = ry
self.color = color
#
self.tt = 0
self.coords = (
self.cx-self.rx, self.cy-self.ry,
self.cx+self.rx, self.cy+self.ry )
self.graph_obj = self.canvas.create_oval(
self.coords, fill=self.color)
#
self.run = False
def animate_step(self):
#
self.tt += 1
self.xx = self.rx*cos(self.tt*pi/128)
self.coords = (
self.cx - self.xx,
self.cy - self.ry,
self.cx + self.xx,
self.cy + self.ry
)
#
self.canvas.coords(self.graph_obj, self.coords)
class Bild(object):
def __init__(self):
#
root = t.Tk()
#
self.canvas = t.Canvas(root,bg='white')
self.canvas.pack()
button = t.Button(root,text='start/stop',command=self.startstop)
button.pack()
#
cx = 150; cy = 120; rx = 100; ry = 100
self.oval = Oval(self.canvas, cx, cy, rx, ry)
#
cx = 150; cy = 120; rx = 80; ry = 80
self.oval_red = Oval(self.canvas, cx, cy, rx, ry, 'red')
#
self.run = False
def startstop(self):
self.run = not self.run
#
while self.run:
self.oval.animate_step()
self.oval_red.animate_step()
#
self.canvas.update()
sleep(0.01)
bild = Bild()
t.mainloop()
Verfasst: Mittwoch 17. Februar 2010, 18:30
von wuf
@Goswin: Super! Gratuliere!
Gruss wuf

Verfasst: Mittwoch 17. Februar 2010, 18:44
von numerix
Goswin hat geschrieben:Es geht auch OHNE die after()-Methode, einfach und direkt, im Anschluss folgt mein Code.
Aber ich weiß noch immer nicht genau, warum es jetzt funktioniert und vorher nicht. Wenn ich Blackjack richtig verstanden habe, dürfte der Code unten nämlich nicht funktionieren, weil die while-Schleife angeblich nicht unterbrochen werden kann. Unter anderem habe ich update_idletasks() durch update() ersetzt, aber ob so etwas die Schleife unterbricht, bin ich mir nicht sicher.
Falls das Ganze einen Pferdefuß hat, lasst es mich bitte wissen...
Dein Code funktioniert,
weil die Schleife den update()-Aufruf enthält.
Ansonsten würde deine Anwendung durch die Schleife komplett blockiert.
Sofern dein Programm nicht
mehr machen soll, als diese Animation, ist damit alles in Ordnung. Wenn nicht, wäre das der Pferdefuß. Willst du, dass die Animation quasi im Hintergrund abläuft und das Programm noch andere Aufgaben erledigt, dann landest du wieder bei after() ....
Aber abgesehen davon: Es wäre gewiss nicht das Schlechteste, wenn du deine Aversionen gegen after() abbauen könntest. Wenn du das einmal verstanden hast, wirst du after() auch gerne einsetzen ...
Verfasst: Mittwoch 17. Februar 2010, 19:02
von BlackJack
@Goswin: Jetzt machst Du mit dem `update()` den Job den sonst die Tkinter-Hauptschleife machen würde. Also auch hier läuft wieder nur die eine *oder* die andere der beiden Schleifen.
Der Pferdefuss ist, dass das nicht skaliert. Zwei Animations-Schleifen "gleichzeitig" gehen nicht.
Verfasst: Donnerstag 18. Februar 2010, 09:31
von wuf
......Hier noch ein Hinweis:
w.update()
Code: Alles auswählen
This method forces the updating of the display. It should be used only if you know what you're
doing, since it can lead to unpredictable behavior or looping. It should never be called from an event
callback or a function that is called from an event callback.
Verfasst: Montag 22. Februar 2010, 11:47
von Goswin
numerix hat geschrieben:Es wäre gewiss nicht das Schlechteste, wenn du deine Aversionen gegen after() abbauen könntest. Wenn du das einmal verstanden hast, wirst du after() auch gerne einsetzen ...
Na, dann will ichs mal versuchen; Unklarheiten habe ich haufenweise, da ich noch nie Threads programmiert habe!
(1)
widget.after(1000,tuwas) bedeutet offenbar _nicht_, dass nach 1_Sekunde <tuwas> ausgeführt wird, sondern wohl eher:
- Sobald der Tk-Mainloop-Thread der after()-Methode Rechenzeit zuweist, wird die Uhr aufgerufen, um den Zeitpunkt <jetzt> zu erhalten.
Dann wird im Mainloop-Thread für den Zeitpunkt <jetzt>+1_Sekunde ein Termin eingetragen mit dem Auftrag <tuwas> aufzurufen.
Sobald das Mainloop seinem Terminkalender Zeit zuweist, (was lange nach der vorgesehenen Zeit sein kann) wird wieder die Uhr aufgerufen und mit dem Terminzeitpunkt verglichen.
Und wenn die Uhrzeit nach dem Terminzeitpunkt liegt, wird (hoffentlich unmittelbar und synchron) die Methode <tuwas> ausgeführt.
Könnt ihr mir so etwas bestätigen?
(2)
widget.after(1000,tuwas) ist immer an ein Widget gebunden. Welches Widget soll das sein und welche Bedeutung hat es? Ist es völlig egal, mit welchem Widget ich after(1000,tuwas) aufrufe? Das Tk-Mainloop muss ja sowieso alle Widgets befragen.
Verfasst: Montag 22. Februar 2010, 14:59
von BlackJack
@Goswin: Das ist jetzt auch noch keine Programmierung mit Threads. Also eigentlich noch ein wenig unkomplizierter, denn ein paar hässliche Sachen, die einem mit Threads passieren können, treten bei `after()` nicht auf.
zu (1): Die `after()`-Methode wird *sofort* aufgerufen, also <jetzt> wird auch gleich bestimmt. Und nach ungefähr der angegebenen Zeit wird ein Ereignis in die Event-Queue von der Hauptschleife gesteckt, das dafür sorgt, dass `tuwas` aufgerufen wird. Wie das Ereignis in die Queue kommt, ist ein Implementierungsdetail, das kann von Plattform zu Plattform unterschiedlich gelöst werden. Es kann Verzögerungen geben, einmal bedingt durch die Art wie die Zeit ermittelt wird, wann das Ergeignis zum Aufruf fällig ist, und dann natürlich auch durch den ganzen Code der in der Mainloop abgearbeitet werden muss, inklusive von Rückruffunktionen die Du als Benutzer von `Tkinter` schreibst. Wenn Du einen Button mit einer Funktion verbindest, die sagen wir mal 3 Sekunden läuft, und innerhalb dieser 3 Sekunden das Ereignis für `tuwas` fällig ist, dann wird `tuwas` frühestens aufgerufen nachdem Dein Code für den Button-Druck die Kontrolle wieder an die Mainloop abgegeben hat. Weil ja keine verschiedenen Threads benutzt werden und nichts wirklich nebenläufig passiert.
Verfasst: Montag 22. Februar 2010, 15:13
von dahaze
Hallo Goswin!
Die Erklärung von dir unter (1) ist generell richtig.
Der Tkinter.mainloop() macht eigentlich nichts Anderes, als in einer Endlosschleife die Events der darauf aufbauenden Widgets abzufragen bzw. die Widgets zu aktualisieren. Ein Event, z.B. das Klicken auf einen Button, "unterbricht" diese Schleife und deshalb "taucht" deine Applikation in diese daran gebundene Methode ab.
Mit der Methode "update()" zwingst du dennoch deine GUI die "darunter" liegenden Widgets zu aktualisieren - sozusagen während der durch den Klick ausgelösten Methode - einen "mainloop()" bzw. eine Aktualisierung durchzuführen.
Dies kann allerdings ins Auge gehen wenn während diesem "Zwischen-Mainloop" wiederrum eine Update-Methode gerufen wird und das Ganze in einer Endlosschleife endet. Deshalb die Warnung zur Verwendung von update(), wie sie Wuf schon gepostet hat.^^
Aus dem eingebauten Hilfetext zur after-Methode:
Code: Alles auswählen
>>> import Tkinter
>>> root = Tkinter.Tk()
>>> help(root.after)
Help on method after in module Tkinter:
after(self, ms, func=None, *args) method of Tkinter.Tk instance
Call function once after given time.
MS specifies the time in milliseconds. FUNC gives the
function which shall be called. Additional parameters
are given as parameters to the function call. Return
identifier to cancel scheduling with after_cancel.
Genau genommen wird die Methode "tuwas" erst aufgerufen wenn:
1) die eingestellte Zeit ("given time") abgelaufen ist
2) der mainloop oder eine update-Methode das Widget aktualisiert hat, an das die "after-Methode" gebunden ist.
Ein bisschen was zum Testen:
Code: Alles auswählen
import Tkinter
import time
def go_btn():
textbox.insert('end', 'START')
for zaehler in range(5):
time.sleep(2)
knopf2.after(1000,tuwas)
textbox.insert('end', '\n'+str(zaehler))
# 1)
# Ist diese Zeile aktiv, wird nach jedem Schleifendurchlauf geprueft ob die
# Bedingungen fuer die after-Methode bereits erfuellt sind
root.update()
# 2)
# Ist diese Zeile aktiv, wird der Button an dem die after Methode gebunden ist
# zerstoert bevor der "mainloop" oder "update" eine Gelegenheit hat die Methode
# auszuloesen. Resultat -> "tuwas" wird nie aufgerufen
#knopf2.destroy()
# 3)
# Ist diese Zeile aktiv, werden jetzt alle after-Methoden aufgerufen deren
# "Zeitvorgabe" bereits abgelaufen ist.
#root.update()
textbox.insert('end', '\n\nENDE')
def tuwas():
textbox.insert('end','\nund jetzt kommt die Fktn "tuwas"')
root = Tkinter.Tk()
knopf1 = Tkinter.Button(root, text='GO', command=go_btn)
knopf1.grid(row=1, column=0)
knopf2 = Tkinter.Button(root, text='GO2')
knopf2.grid(row=1, column=1)
textbox = Tkinter.Text(root)
textbox.grid(row=0, column=0, columnspan=2)
root.mainloop()
In dem geposteten Beispiel-Code natürlich immer nur eine der 3 Zeilen aktivieren um den beschriebenen Effekt zu erzeugen.
EDIT: Hab den Beispiel-Quelltext nochmal leicht geändert.
Gruß,
Simon