Seite 1 von 1
GUI update
Verfasst: Mittwoch 1. September 2010, 11:08
von RedSharky
Hallo,
ich hab mal wieder ein Problemchen:
In folgendem Beispiel werden in einer Schleife aktuelle Werte erzeugt, die natürlich auch zeitnah über die netten tk/ttk-Widgets dargestellt werden sollen. Leider passiert das eben nicht. Eine Ausgabe mit print() funktioniert dagegen ohne verzögerung.
Könnte mir mal jemand die Logik des dahinterstehen Konzeptes verklickern? Und wie macht man es richtig? Ich möchte, dass die Variablenänderungen sofort an die Widgets weitergegeben werden.
Danke
Code: Alles auswählen
from tkinter import *
from tkinter import ttk
import time
def aloop():
for i in range(5):
t = time.asctime(time.localtime())
alabel['text'] = t
print(t)
time.sleep(1)
root = Tk()
atext = StringVar()
atext = "---"
alabel = ttk.Label(root, text=atext)
alabel.pack()
abutton = ttk.Button(root, text="Info", command=aloop)
abutton.pack()
root.mainloop()
Re: GUI update
Verfasst: Mittwoch 1. September 2010, 13:01
von BlackJack
@RedSharky: Verwende statt der Schleife und `time.sleep()` die `after()`-Methode auf Widgets. In der Schleife kommt `Tkinter` ja nicht dazu seine Hauptschleife abzuarbeiten, weil statt der Deine Schleife läuft.
Re: GUI update
Verfasst: Mittwoch 1. September 2010, 13:19
von yipyip
Neben der "after()"-Methode brauchst Du auch noch fuer das Label die "update()"-Methode, damit das Label auch wirklich neu gezeichnet wird.
Habe mal versucht, die von Dir gewuenschte Funktionalitaet nachzubilden.
Code: Alles auswählen
#!/usr/bin/env python3.1
import tkinter as tk
import time
class Gui(object):
def __init__(self, counts=5, ms=1000):
self.root = tk.Tk()
self.svar = tk.StringVar()
self.svar.set("---")
self.label = tk.Label(textvariable=self.svar)
self.label.pack()
self.but = tk.Button(self.root, width=30, text="Info", command=self.loop_time)
self.but.pack()
self.state = ''
self.counter = counts
self.counts = counts
self.ms = ms
def loop_time(self):
if not self.state == 'looping':
self.state = 'looping'
self.set_time()
def set_time(self):
#print(self.counter)
self.svar.set(time.asctime(time.localtime()))
self.label.update()
self.counter -= 1
if self.counter <= 0:
self.state = ''
self.label.after_cancel(self.id)
self.counter = self.counts
else:
self.id = self.label.after(self.ms, self.set_time)
def run(self):
self.root.mainloop()
if __name__ == '__main__':
Gui(50, 100).run()
yipyip
Re: GUI update
Verfasst: Mittwoch 1. September 2010, 13:31
von RedSharky
Hi,
ich hab jetzt mal probiert, das mit einem Thread zu lösen. Das ist aber nicht zu empfehlen, oder? Kommt mir irgendwie wie mit Kanonen auf Spatzen geschossen vor. An after() hab ich mich auch noch schwach erinnert, aber ich find ums Verrecken keine Doku dazu. Könnte mir da jemand aushelfen?
Danke für das Beispiel, werd ich mir mal anschauen.
Frage: Wieso geht das mit Tk nicht einfach so? Muss das so oder gibt's da irgendwelche Vorteile?
Code: Alles auswählen
from tkinter import *
from tkinter import ttk
import time
import threading
def aloop():
#for i in range(5):
while 1:
t = time.asctime(time.localtime())
alabel['text'] = t
print(t)
time.sleep(1)
def start_aloop():
thrd = threading.Thread(target=aloop)
thrd.start()
root = Tk()
atext = StringVar()
atext = "---"
alabel = ttk.Label(root, text=atext)
alabel.pack()
abutton = ttk.Button(root, text="Info", command=start_aloop)
abutton.pack()
root.mainloop()
Re: GUI update
Verfasst: Mittwoch 1. September 2010, 13:47
von yipyip
Nee, nee, nee, das wird so nix (...und dann auch noch bei jedem Button-Click einen neuen Thread starten...). Beitraege zum Thema Threads und GUI gibt's hier genuegend.
Zur "after()"-Methode:
http://effbot.org/tkinterbook/widget.htm
yipyip
Re: GUI update
Verfasst: Mittwoch 1. September 2010, 13:50
von RedSharky
Ich muss noch mal nachfragen: Oben habe ich nur eine Art Minimalbeispiel angegeben.
Eigentlich ist die Situation viel kompilizierter: Ich habe ein Programm geschrieben, dass etliche Dateien einliest, bearbeitet und Details und Zwischenstände dazu ausgibt. Dazu sind for-Schleifen zwingend notwendig. Nur leider gibt es das Feedback erst, wenn alles beendet ist. Und das kann schon mal Minuten dauern.
after() ist da glaub ich nicht das Wahre; was nun?
Re: GUI update
Verfasst: Mittwoch 1. September 2010, 14:48
von BlackJack
@RedSharky: Logik und GUI komplett trennen, Logik in einem eigenen Thread laufen lassen, und mit der GUI über eine `Queue.Queue` kommunizieren, die periodisch mit `after()` abgefragt wird.
Re: GUI update
Verfasst: Mittwoch 1. September 2010, 21:08
von problembär
RedSharky hat geschrieben:after() ist da glaub ich nicht das Wahre
Doch, siehe z.B.
hier.
Re: GUI update
Verfasst: Freitag 3. September 2010, 11:27
von RedSharky
@RedSharky: Logik und GUI komplett trennen, Logik in einem eigenen Thread laufen lassen, und mit der GUI über eine `Queue.Queue` kommunizieren, die periodisch mit `after()` abgefragt wird.
Frage: Wenn ich das so mache, muss ich dann die Funktion, die after() aufruft und mit get() die Queue abfragt zwingend in einen eigenen Thread stecken, da ja get() sonst wieder alles blockiert?! Ich habe nämlich den Eindruck. Brauche ich also zwei Threads, einen für die Logik (Sender, put()) und einen für das Update des GUI (Empfänger, get())? Oder kann man sich für get() den Thread sparen?
Re: GUI update
Verfasst: Freitag 3. September 2010, 11:58
von RedSharky
Hmm, schein auch so zu klappen. Vergesst meinen letzten Post.
Hier ein Beispiel, Kritik erwünscht:
Code: Alles auswählen
from tkinter import *
from time import *
import threading
import queue
#------------------------------------------------------------------------------
class Counting(threading.Thread):
'''
Just counting...
'''
def __init__(self, label, t, q):
threading.Thread.__init__(self)
self.jobqueue = q # get job queue for sending commands
self.t = t
def run(self):
while 1:# and self.mb.entrycget(1,"label")=="Stop":
sleep(0.1) # wait a second
self.t += 1 # count down
print(self.t)
# send to job queue !!!
self.jobqueue.put(("time_label",self.t))
class AppGUI:
'''
Application GUI.
'''
def __init__(self, master):
self.t = 0
#--- GUI design -------------------------------------------------------
self.label = Label(master,font=("Arial","30"))
self.label.pack()
self.label.config(text=str(self.t))
#--- Job queue --------------------------------------------------------
self.q = queue.Queue() # Make job queue (Queue)
offswitch = threading.Event() # Make offswitch (Event)
self.label.after(100,self.label_update)
cd = Counting(self.label, self.t, self.q)
cd.start()
def label_update(self):
print("label update")
job = self.q.get() #get job form queue!!!
if job[0] == "time_label":
self.label.config(text=str(job[1]))
else:
print("Unknown job:", job)
self.label.after(100,self.label_update)
def main():
root=Tk()
root.title("Counting")
root.wm_geometry('170x50+200+200')
root.resizable(False,False)
root.deiconify()
app=AppGUI(root)
root.mainloop()
if __name__ == '__main__':
main()
Re: GUI update
Verfasst: Freitag 3. September 2010, 12:16
von BlackJack
@RedSharky: Der `Queue.get()`-Aufruf blockiert die GUI. Entweder Du verwendest die nicht-blockierende Variante, oder Du testest vorher, ob etwas in der Queue steckt.
Sternchen-Importe sind immer noch Böse™.
Die Namen `t` und `q` sind etwas zu kurz.
Du übertreibst es ein *bisschen* mit Ausrufezeichen in Kommentaren.

Und Kommentare sollten auch zum Quelltext passen. "wait a second" wo *keine* Sekunde gewartet wird, oder "count down" wo *hoch* gezählt wird, sind irgendwie nicht so toll. Wobei an den beiden Stellen der Quelltext eigentlich selbst schon genug aussagt.
Bei `Counting.run()` würde ich ``while True:`` schreiben. Und der auskommentierte Teil der Bedingung begeht wahrscheinlich den Fehler um den es hier geht: Zugriff auf die GUI von einem anderen Thread als dem, in dem die Hauptschleife der GUI läuft.
Re: GUI update
Verfasst: Samstag 4. September 2010, 00:21
von problembär
RedSharky hat geschrieben:Hier ein Beispiel, Kritik erwünscht
Ich sehe immer noch nicht, wozu Du da Threads brauchen solltest:
Code: Alles auswählen
import tkinter as tk
class Counter:
def __init__(self):
self.t = 0
def increaseT(self):
self.t += 1
class AppGUI:
def __init__(self):
self.root=tk.Tk()
self.root.title("Counting")
self.root.wm_geometry('170x50+200+200')
self.root.resizable(False, False)
self.root.deiconify()
self.c = Counter()
self.label = tk.Label(self.root, font = ("Arial", "30"))
self.label.pack()
self.label.config(text = str(self.c.t))
self.root.after(100, self.label_update)
self.root.mainloop()
def label_update(self):
self.c.increaseT()
print("label update")
self.label.config(text = str(self.c.t))
self.root.after(100, self.label_update)
def main():
app = AppGUI()
if __name__ == '__main__':
main()
Gruß
Re: GUI update
Verfasst: Samstag 4. September 2010, 07:26
von BlackJack
@problembär: Das richtige Problem ist etwas komplizierter als ein einfacher Counter. Da braucht man schon einen Thread zu. Man will ja nicht einen lang laufenden Algorithmus so umschreiben, dass er immer nur Stückweise mit `after()` läuft und dadurch Zeit verschenkt.
Re: GUI update
Verfasst: Samstag 4. September 2010, 18:04
von problembär
BlackJack hat geschrieben:@problembär: Das richtige Problem ist etwas komplizierter als ein einfacher Counter. Da braucht man schon einen Thread zu. Man will ja nicht einen lang laufenden Algorithmus so umschreiben, dass er immer nur Stückweise mit `after()` läuft und dadurch Zeit verschenkt.
Ok, das seh' ich ein.
Irgendwie ist mir bei Threads immer ein bißchen unwohl, ob das auch stabil genug läuft. Wenn das wirklich zwei ganz getrennte Sachen sind, die gleichzeitig laufen sollen, nehm' ich meistens lieber einen zweiten Prozeß, mit dem ich über "subprocess" kommuniziere. Das geht (auch) ganz gut.
Re: GUI update
Verfasst: Samstag 4. September 2010, 20:33
von Leonidas
Das kannst du mit multiprocessing aber schon einfacher haben...
Re: GUI update
Verfasst: Samstag 4. September 2010, 23:50
von DasIch
problembär hat geschrieben:Irgendwie ist mir bei Threads immer ein bißchen unwohl, ob das auch stabil genug läuft. Wenn das wirklich zwei ganz getrennte Sachen sind, die gleichzeitig laufen sollen, nehm' ich meistens lieber einen zweiten Prozeß, mit dem ich über "subprocess" kommuniziere. Das geht (auch) ganz gut.
Du willst ZeroMQ nutzen.
Re: GUI update
Verfasst: Sonntag 5. September 2010, 09:02
von RedSharky
So, ich bins nochmal.
Wie muss ich es machen, wenn ich nicht nur ein Widget updaten möchte, sondern viele, ca. 20. Soll ich dann für jedes Widget einen eigene Queue einrichten und an jedes Wiget ein after()->get() dranhängen, oder geht das irgendwie eleganter. Ist es überhaupt wichtig, an welches Widget man das after() koppelt? Eigentlich müsste das doch egal sein. Wie macht ihr denn sowas? Oder brauch man generell nur eine Queue, oder in diesem Fall tatsächlich 20? Irgendwie verstehe ich das Prinzip nicht. Ich könnte zwar alles einzeln per Hand und zu Fuß machen, aber das kann doch nicht richtig sein!
Ich möchte doch nur eine Tk-Oberfläche, die mir zeitnah ein Feedback über meine ganzen Schleifen, Dateiladenvorgänge, usw anzeigt.
Hätte jemand ein Beispiel? Mehrere Widgets, die aus komplett unanhängigen Threads aktualisiert werden - einfach erweiterbar.
Ich versuche mir gerade eine Minimalanwendung stricken, die dann nach Belieben ausbauen kann. Bisher ist es leider so, dass ich eine Idee habe und anfange zu programmieren, dann aber immer an einen Punkt komme, an dem ich nicht weiterkomme, weil irgendetwas, was mit print() noch ausgegeben werden konnte, ums Verrecken nicht über die Widgets dargestellt werden möchte und gerne mal die ganze Applikation wegreißt. Deshalb auch der Plan, ein funktionierendes Grundgerüst zu haben, als Frustrationsprävention. Leider habe ich sowas noch nirgendwo gesehen.
Re: GUI update
Verfasst: Sonntag 5. September 2010, 09:37
von BlackJack
@RedSharky: Auf welchem Widget man das `after()` aufruft ist in der Regel nicht so wichtig. Wieviele Widget pro Queue Du brauchst, hängt IMHO vom Anwendungsfall ab. Du kannst eine für alle haben, eine für jedes Widget, aber auch irgendwas dazwischen. Was halt am meisten Sinn macht.
Wobei 20 Widgets die gleichzeitig aktualisiert werden, schon ein wenig unübersichtlich klingt.
Re: GUI update
Verfasst: Sonntag 5. September 2010, 11:12
von yipyip
Da mir bis jetzt noch niemand gesagt hat, daß ich dort Unsinn verzapft habe:
http://www.python-forum.de/viewtopic.ph ... 41&start=0
und
http://www.python-forum.de/viewtopic.ph ... 90&start=0
sollten ganz hilfreich sein.
yipyip