Threads / GUI / Variablenrückgabe

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
RedSharky
User
Beiträge: 99
Registriert: Donnerstag 13. April 2006, 15:38

Sonntag 8. Februar 2009, 19:51

Hallo, ich bin's mal wieder:

Ich beschäftige mich mal wieder mit meinen Lieblingsbaustellen, die da lauten: Objektorientierung, Threads und Tkinter. Da es zu meiner Zeit (ist schon etwas her) im Informatikunterricht keine Objektorientierung gab, versuche ich mich nun als Autodidakt. Ich muss da noch einiges lernen...

Im folgenden Beispiel habe ich einen Coutdown-Timer geschrieben. Dieser nutzt als GUI Tkinter, treading zum ungestörten Runterzählen, und spielt am Ende in einem weiteren treading einen Sound ab. Das funktioniert!

Nun möchte ich aber das Feedback verbessern und unter Tk die aktuelle Countdown-Zeit im Label etwas formatierter anzeigen: nämlich als Minuten und Sekunden.
Jetzt kommt's: Dazu brauch ich aber am Besten die Zeitvariable self.cur_t aus Countdown (threading) wieder zurück in AppGUI!
Oder anders gefragt:Wie bekomme ich aus einem "untergeordneten" Thread wieder eine Variable zurück nach Tk? Wenn ich self.cur_t mit return zurückgebe, bricht die Schleife ab.

Ich hoffe, das war nicht zu unverständlich.

Vielen Dank schon mal.

Übrigens freue ich mich auch über jeden Tipp zu meinem Programm. Verbesserungsvorschläge jeder Art sind herzlich willkommen.

Code: Alles auswählen

from Tkinter import *
from time import *
import winsound
import threading

COUNTDOWNTIME = 10


class Playsound(threading.Thread):
    def __init__(self,s):
        threading.Thread.__init__(self)
        self.s = s
    
    def run(self):   
        for i in range(len(self.s)):
            print "Playing sound:", self.s[i]
            winsound.PlaySound(self.s[i],winsound.SND_PURGE) 


class Countdown(threading.Thread):
    def __init__(self, label, t, mb):
        threading.Thread.__init__(self)
        
        self.label = label # provides access to Tk widget
        self.mb = mb # provides access to Tk widget
        
        self.t = t # variable space limited to Countdown!!!
        self.cur_t = t # current time
        
    def run(self):
        while self.cur_t > 0 and self.mb.entrycget(1,"label")=="Stop":
            sleep(1) # wait a second
            self.cur_t -= 1 # count down
            
            #some output
            print "t:", self.cur_t
            self.label.config(text=str(self.cur_t))
            
            if self.cur_t <= 0:
                print "The Time is up!"
                print
                
                ps = Playsound([#"sound/0.wav",
                                    #"sound/The time is up.wav",
                                    "sound/chimes.wav"])
                ps.start()
                
                sleep(0.5)
                self.mb.entryconfig(1,label="Start")
                self.label.config(text=str(self.t))


class AppGUI: 
    def __init__(self,master):
        self.t = COUNTDOWNTIME
        self.run_button = "Start"
        self.master = master
        
        ## GUI design #########################################################
        self.mb = Menu(self.master) #menu bar
        master.config(menu=self.mb) #to attach to root window
        
        self.mb.add_command(label=self.run_button,
                                command=self.run_button_action)
        
        self.label = Label(master,font=("Arial","30"))
        self.label.pack()
        self.label.config(text=self.t)
        
    def run_button_action(self):
        print "start/stop:", self.mb.entrycget(1,"label")
        if self.mb.entrycget(1,"label")=="Start":
            self.mb.entryconfig(1,label="Stop") #change label of 1st menu item
            
            cd = Countdown(self.label, self.t, self.mb)
            cd.start()

        else:
            print self.mb.entrycget(1,"label")
            self.mb.entryconfig(1,label="Start")
            sleep(1.1)
            self.label.config(text=self.t)
    
    
def main():
    root=Tk()
    root.title("Countdown")
    root.wm_geometry('170x50+200+200')
    root.resizable(False,False)
    root.deiconify()
    app=AppGUI(root)   
    root.mainloop()


if __name__ == '__main__':
    main()
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Sonntag 8. Februar 2009, 20:01

Ohne mir den Quellcode genauer angeschaut zu haben: Sowas kann man gut mit dem Queue Modul machen.

[wiki]Threading Beispiel1[/wiki]?highlight=%28Threading%29
RedSharky
User
Beiträge: 99
Registriert: Donnerstag 13. April 2006, 15:38

Sonntag 8. Februar 2009, 21:13

Danke für deine schnelle Antwort.

Das Wikibeispiel kannte ich schon. Leider läuft es bei mir nicht in Verbindung mit einem GUI. Mir würde ein Beispiel sehr helfen, das auch darauf eingehen würde. Oder ein paar Hinweise, was man da beachten muss. Ich habe im unten angegebenen Beispiel mal versucht, das Wikibeispiel mit einzubauen. Aber irgendwie stören die Queues den Aufbau von Tkinter. Vielleicht ein Problem in Verbindung mit mainloop()... Dazu reichen meine begrenzten Fähigkeiten aber nicht aus.

Code: Alles auswählen

from Tkinter import *
from time import *
import winsound
import threading
import Queue
import types



COUNTDOWNTIME = 10


class WartenderThread(threading.Thread):
    """
    Dieser Thread wartet auf die Anweisung, verschiedenste Jobs abzuarbeiten.
    """

    #----------------------------------------------------------------------
    def __init__(self, jobqueue, ausschalter):
        """
        Uebernimmt die Queue, die spaeter die Jobanweisungen uebergibt.
        """

        threading.Thread.__init__(self)

        self.jobqueue = jobqueue
        self.ausschalter = ausschalter


    #----------------------------------------------------------------------
    def hallo_welt_1(self):
        """
        Diese Funktion wird aufgerufen, wenn an die
        Jobqueue "hallo_welt_1" uebergeben wurde.
        """

        print "    Ausgefuehrt: Hallo Welt 1"


    #----------------------------------------------------------------------
    def hallo_welt_2(self, **dargs):
        """
        Diese Funktion wird aufgerufen, wenn an die
        Jobqueue "hallo_welt_2" uebergeben wurde.
        """

        print "    Ausgefuehrt: Hallo Welt 2"
        if dargs:
            for key, value in dargs.items():
                print "        %s: %s" % (key, value)


    #----------------------------------------------------------------------
    def run(self):
        """
        'run' ist die Methode, die aufgerufen wird, wenn 'WartenderThread.start()'
        ausgefuehrt wird.
        """

        # In einer Schleife wird darauf **gewartet** bis ein neuer Job
        # an die Jobqueue uebergeben wird.
        while True:

            # Hier wird gewartet
            anweisung = self.jobqueue.get()

            # Diesen Thread kurz schlafen schicken, damit wird auch die
            # Unabhaengigkeit des Threades demonstriert.
            sleep(1)

            print "Anweisung: %s" % str(anweisung)

            # Wenn die Anweisung ein Tupel oder eine Liste ist, dann
            # wird diese(r) in die Anweisung und die Argumente zerlegt.
            if isinstance(anweisung, (types.ListType, types.TupleType)):
                anweisung, dargs = anweisung
            else:
                dargs = {}

            # Wenn Quit uebergeben wurde, dann darf die Schleife
            # nicht weitergefuehrt werden.
            if anweisung == "quit":
                print "    Ausschalter setzen"
                self.ausschalter.set()
                break

            # Pruefen ob die Methode mit dem Namen %(anweisung)s
            # existiert. Wenn Ja, ausfuehren.
            if hasattr(self, anweisung):
                getattr(self, anweisung)(**dargs)


class AnweisenderThread(threading.Thread):
    """
    Dieser Thread gibt Anweisungen an den anderen Thread weiter.
    """

    #----------------------------------------------------------------------
    def __init__(self, jobqueue):
        """
        Uebernimmt die Queue, mit der die Jobanweisungen an den anderen
        Thread uebergeben werden.
        """

        threading.Thread.__init__(self)

        self.jobqueue = jobqueue


    #----------------------------------------------------------------------
    def run(self):
        """
        'run' ist die Methode, die aufgerufen wird, wenn 'AnweisenderThread.start()'
        ausgefuehrt wird.
        """

        # Ein paar Anweisungen in einen Tupel stecken...
        # "hallo_welt_3" ist nur ein Platzhalter, ohne zugeh. Funktion
        # im wartenden Thread.
        anweisungen = ("hallo_welt_1", "hallo_welt_2", "hallo_welt_3")

        # Die ersten Anweisungen in die Queue stellen.
        for anweisung in anweisungen:
            self.jobqueue.put(anweisung)

        # Durch diese Wartezeit, sieht man ziemlich gut, dass der wartende
        # Thread wirklich auf die naechste Anweisung aus der Queue wartet.
        sleep(3)

        # An die Anweisung "hallo_welt_2" werden auch die Parameter
        # uebergeben.
        anweisungen = (
            "hallo_welt_1",
            ("hallo_welt_2", {"vorname": "Gerold", "nachname": "Penz"}),
        )
        # Anmerkung: Die Klammern in der Zeile mit hallo_welt_2 beachten! (->Tupel)

        # Die naechsten zwei Anweisungen in die Queue stellen.
        for anweisung in anweisungen:
            self.jobqueue.put(anweisung)

        # Die Anweisung zum Beenden schicken.
        self.jobqueue.put("quit")
        return


#------------------------------------------------------------------------------

class Playsound(threading.Thread):
    def __init__(self,s):
        threading.Thread.__init__(self)
        self.s = s
    
    def run(self):   
        for i in range(len(self.s)):
            print "Playing sound:", self.s[i]
            winsound.PlaySound(self.s[i],winsound.SND_PURGE) 


class Countdown(threading.Thread):
    def __init__(self, label, t, mb,jobqueue):
        threading.Thread.__init__(self)
        
        self.jobqueue = jobqueue # get job queue for sending commands
        
        self.label = label # provides access to Tk widget
        self.mb = mb # provides access to Tk widget
        
        self.t = t # variable space limited to Countdown!!!
        self.cur_t = t # current time
        
        
    def run(self):
        while self.cur_t > 0 and self.mb.entrycget(1,"label")=="Stop":
            sleep(1) # wait a second
            self.cur_t -= 1 # count down
            
            anweisungen = ("hallo_welt_1", "hallo_welt_2", "hallo_welt_3")
            for anweisung in anweisungen:
                self.jobqueue.put(anweisung)
            #sleep(1)
            
            self.jobqueue.put("quit")
            
            #some output
            print "t:", self.cur_t
            self.label.config(text=str(self.cur_t))
            
            if self.cur_t <= 0:
                print "The Time is up!"
                print
                
                ps = Playsound([#"sound/0.wav",
                                    #"sound/The time is up.wav",
                                    "sound/chimes.wav"])
                ps.start()
                
                sleep(0.5)
                self.mb.entryconfig(1,label="Start")
                self.label.config(text=str(self.t))
            
        self.jobqueue.put("quit")



class AppGUI: 
    def __init__(self, master):
        self.t = COUNTDOWNTIME
        self.run_button = "Start"
        self.master = master
        
        # Jobqueue erstellen (Queue)
        self.q = Queue.Queue()
        # Ausschalter erstellen (Event)
        ausschalter = threading.Event()
    
        # Threads initialisieren und dabei die Jobqueue und den
        # Ausschalter uebergeben
        wt = WartenderThread(self.q, ausschalter)
        #at = AnweisenderThread(self.q)
        
        # Threads starten
        wt.start() # Dieser wartet so lange bis Jobanweisungen 
                # geschickt werden.
        #at.start() # Dieser schickt Jobanweisungen ueber die Jobqueue.
        
        
        
        # Warten bis der Ausschalter betaetigt wurde. Sonst wuerde das
        # Programm sofort beendet werden.
        ausschalter.wait()
        
        ## GUI design #########################################################
        self.mb = Menu(self.master) #menu bar
        master.config(menu=self.mb) #to attach to root window
        
        self.mb.add_command(label=self.run_button,
                                command=self.run_button_action)
        
        self.label = Label(master,font=("Arial","30"))
        self.label.pack()
        self.label.config(text=self.t)
        
    def run_button_action(self):
        print "start/stop:", self.mb.entrycget(1,"label")
        if self.mb.entrycget(1,"label")=="Start":
            self.mb.entryconfig(1,label="Stop") #change label of 1st menu item
            
            cd = Countdown(self.label, self.t, self.mb, self.q)
            cd.start()

        else:
            print self.mb.entrycget(1,"label")
            self.mb.entryconfig(1,label="Start")
            sleep(1.1)
            self.label.config(text=self.t)
    
    
def main():
    root=Tk()
    root.title("Countdown")
    root.wm_geometry('170x50+200+200')
    root.resizable(False,False)
    root.deiconify()
    app=AppGUI(root)   
    root.mainloop()


if __name__ == '__main__':
    main()
RedSharky
User
Beiträge: 99
Registriert: Donnerstag 13. April 2006, 15:38

Sonntag 8. Februar 2009, 21:31

Ich habe jetzt mal ausschalter.wait() rausgenommen. Nun geht es!

Melde mich.
RedSharky
User
Beiträge: 99
Registriert: Donnerstag 13. April 2006, 15:38

Sonntag 8. Februar 2009, 22:45

So, ich hab jetzt alles drin.
Es funktioniert sogar.

Vielen Dank.

Code: Alles auswählen

from Tkinter import *
from time import *
import winsound
import threading
import Queue
import types


COUNTDOWNTIME = 10

#------------------------------------------------------------------------------

class UpdateThread(threading.Thread):
    """
    This threads waits for jobs from a queue.
    """
    def __init__(self, jobqueue, offswitch, label):
        """
        Get the queue. Later on filled with jobs.
        """
        self.label = label
        threading.Thread.__init__(self)
        self.jobqueue = jobqueue
        self.offswitch = offswitch
    
    def set_time_label(self,t):
        """
        Update the time label (Tkinter)
        """
        print "Time label set to:", t
        self.label.config(text=str(t))
        
    def run(self):
        """
        'run' will be started automatically.
        """
        while True:
            job = self.jobqueue.get() # Get from queue
            #print "job: %s" % job
            
            # This thread is indepentent. You can let it sleep.
            #sleep(1)
            
            if job[0] == "time_label":
                self.set_time_label(job[1]) # Update time label.
            else:
                print "Unknown job:", job

            # If job equals 'quit', finish it.
            if job == "quit":
                print "    set offswitch"
                self.offswitch.set()
                break


#------------------------------------------------------------------------------

class Playsound(threading.Thread):
    """
    Plays some wav sounds.
    """
    def __init__(self,s):
        threading.Thread.__init__(self)
        self.s = s
    
    def run(self):   
        for i in range(len(self.s)): # Go through list of WAVs.
            print "Playing sound:", self.s[i]
            winsound.PlaySound(self.s[i],winsound.SND_PURGE) 


class Countdown(threading.Thread):
    def __init__(self, label, t, mb,jobqueue):
        threading.Thread.__init__(self)
        
        self.jobqueue = jobqueue # get job queue for sending commands
        
        self.label = label # provides access to Tk widget
        self.mb = mb # provides access to Tk widget
        
        self.t = t # variable space limited to Countdown!!!
        self.cur_t = t # current time
        
    def run(self):
        while self.cur_t > 0 and self.mb.entrycget(1,"label")=="Stop":
            sleep(1) # wait a second
            self.cur_t -= 1 # count down
            
            # send to job queue !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            self.jobqueue.put(("time_label",self.cur_t))
            
            #some output
            print "t:", self.cur_t
            
            if self.cur_t <= 0:
                print "The Time is up!"
                print
                
                ps = Playsound([#"sound/0.wav",
                                    #"sound/The time is up.wav",
                                    "sound/chimes.wav"])
                ps.start()
                
                sleep(0.5)
                self.mb.entryconfig(1,label="Start")
                self.label.config(text=str(self.t))
            

class AppGUI: 
    '''
    Application GUI. Made with Tkinter.
    '''
    def __init__(self, master):
        self.t = COUNTDOWNTIME
        self.run_button = "Start"
        self.master = master
  
        #--- GUI design -------------------------------------------------------  
        self.mb = Menu(self.master) #menu bar
        master.config(menu=self.mb) #to attach to root window
        
        self.mb.add_command(label=self.run_button,
                                command=self.run_button_action)
        
        self.label = Label(master,font=("Arial","30"))
        self.label.pack()
        self.label.config(text=self.t)
        
        #--- Job queue --------------------------------------------------------
        self.q = Queue.Queue() # Make job queue (Queue)
        offswitch = threading.Event() # Make offswitch (Event)
        # Initialaze thread and hand over queue and offswitch
        ut = UpdateThread(self.q, offswitch, self.label)
        ut.start() # Wait for jobs until quit command
        # Switch off jobqueue. Not necessary here?!
        #self.jobqueue.put("quit")
        
    def run_button_action(self):
        '''
        Button event: Start countdown!
        '''
        print "start/stop:", self.mb.entrycget(1,"label")
        if self.mb.entrycget(1,"label")=="Start":
            self.mb.entryconfig(1,label="Stop") #change label of 1st menu item
            cd = Countdown(self.label, self.t, self.mb, self.q)
            cd.start()
        else:
            print self.mb.entrycget(1,"label")
            self.mb.entryconfig(1,label="Start")
            sleep(1.1)
            self.label.config(text=self.t)
    
    
def main():
    root=Tk()
    root.title("Countdown")
    root.wm_geometry('170x50+200+200')
    root.resizable(False,False)
    root.deiconify()
    app=AppGUI(root)   
    root.mainloop()


if __name__ == '__main__':
    main()
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Montag 9. Februar 2009, 11:39

Für die Anzeige eines Countdown reicht die Tkinter-Methode after() völlig aus. Das ist - ganz ohne Threads - in wenigen Zeilen gemacht.
RedSharky
User
Beiträge: 99
Registriert: Donnerstag 13. April 2006, 15:38

Montag 9. Februar 2009, 14:26

Das mit after() war die Vorgängerversion von diesem Programm. Threads extra einzubinden, eröffnet dagegen ganz neue Möglichkeiten.
Antworten