Problem Externes Event (Netzwerk nachricht)

Fragen zu Tkinter.
Antworten
Bonzo1993
User
Beiträge: 28
Registriert: Freitag 28. August 2009, 22:03

hi @ all
ich habe ein Programm geschrieben, dass mit einer handelsüblichen webcam eine Bewegungserkennung durchführt. Wurde eine Bewegung erkannt wird über socket eine einfache nachricht an eine fest eingegebene IP versandt. Der Client epfängt diese und gibt eine Meldung auf der Konsole aus (dass beim Server eine Bewegung erkannt wurde). Nun soll dazu eine GUI entstehen, die erstmal nur ein Label hat, das je nachdem ob eine Nachricht eingeht oder eben nicht, in grün "Keine Bewegung" bzw. rot "Bewegung erkannt!" anzeigt. Aber ich komm nicht ganz mit dem Eventhandling von Tkinter mit. Hier der bisherige Code:

Code: Alles auswählen

import socket
import Image
import Tkinter


class GUIMotionDetectionClient(Tkinter.Frame):
    def __init__(self, master = None):
        Tkinter.Frame.__init__(self, master)
        self.pack()
        self.createWidgets()
        self.checkStatus()
        
    def createWidgets(self):
        self.QUIT = Tkinter.Button(self)
        self.QUIT["text"] = "ENDE"
        self.QUIT["fg"]   = "red"
        self.QUIT["command"] =  self.quit

        self.QUIT.pack({"side": "left"})
        
        self.status = Tkinter.Label(fg = "green", text="Keine Bewegung")
        self.status.pack()
        
        

    def checkStatus(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind(("", 50000))
        s.listen(1)

        try:
            while True:
                komm, addr = s.accept()
                while True:
                    data = komm.recv(1024)

                    if not data:
                        komm.close()
                        break
                    self.status["fg"] = "red"
                    self.status["text"] = "Bewegung erkannt!"
        finally:
            s.close()
    
    
root = Tkinter.Tk()
app = GUIMotionDetectionClient(master=root)
app.mainloop()
root.destroy()
BlackJack

@Bonzo1993: Du gibst dem Eventhandling ja auch gar keine Chance. Das startet wenn die `mainloop()`-Funktion aufgerufen wird. Das passiert bei Dir aber gar nicht weil die `checkStatus()`-Methode eine Endlosschleife enthält.

Man könnte die Netzwerkkommunikation in einen eigenen Thread auslagern (`threading`-Modul) und mit einen `Queue` mit dem Hauptthread kommunizieren, indem man mittels der `after()`-Methode auf Widgets regelmässig die Queue abfragt.
Bonzo1993
User
Beiträge: 28
Registriert: Freitag 28. August 2009, 22:03

also ich hab jetzt erstmal versucht das ganze auf zwei threads zu verteilen. Es soll einen Thread geben, der vom hauptprogramm gestartet wird und dann einfach die GUI startet. Ein zweiter Thread der dann vom Hauptprogramm gestartet wird ist der Netzwerkthread er soll die Nachricht des Servers empfangen und "Nachricht!" auf der Konsole ausgeben (später dann natürlich auf einem Label der GUI). Aber auch das funktioniert nicht. Entweder ich starte vom Hauptprogramm erst den GUI-Thread (dann funkzt die GUI und Netzwerkthread net) oder ich starte erst den netzwerthread und das ganze ist anders rum. Aber eig. sollten thread doch unabhängig nebeneinander parallel laufen können?

Code: Alles auswählen

'''
Created on 22.08.2010

@author: Christoph
'''

import socket
import Tkinter
from threading import Thread

class MotionDetectionClient(Thread):
    def __init__(self):
        Thread.__init__(self)
    
    def checkStatus(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind(("", 50000))
        s.listen(1)

        try:
            while True:
                komm, addr = s.accept()
                while True:
                    data = komm.recv(1024)

                    if not data:
                        komm.close()
                        break
                    print("Nachricht eingegangen!")
        finally:
            s.close()


class GUIMotionDetectionClient(Tkinter.Frame, Thread):
    def __init__(self, master = None):
        Thread.__init__(self)
        Tkinter.Frame.__init__(self, master)
        self.pack()
        self.createWidgets()

        
    def createWidgets(self):
        self.QUIT = Tkinter.Button(self)
        self.QUIT["text"] = "ENDE"
        self.QUIT["fg"]   = "red"
        self.QUIT["command"] =  self.quit

        self.QUIT.pack({"side": "left"})
        
        self.status = Tkinter.Label(fg = "green", text="Keine Nachricht!")
        self.status.pack()
    
    
client = MotionDetectionClient()
client.checkStatus()

root = Tkinter.Tk()
app = GUIMotionDetectionClient(master=root)
app.mainloop()
root.destroy()
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo.

Dann denke doch noch einmal über die run-Methode von Threads nach ;-) . Du kannst niche einfach irgend eine Methode aufrufen und hoffen, dass es dann irgendwie schon funktioniert. Die __init__-Methode von MotionDetectionClient ist übrigens überflüssig und für die GUI solltest du nicht noch einmal extra einen Thread starten.

Sebastian
Das Leben ist wie ein Tennisball.
Bonzo1993
User
Beiträge: 28
Registriert: Freitag 28. August 2009, 22:03

hi
Danke ;D ja da hätte ich dran denken können ^^

also so wie oben beschrieben klappts jetzt, aber die kommunikation zw. GUI und Netzwerkthread bekomm ich noch net ganz hin :( Weil ein direkter Zugriff is ja sowieso nicht möglich (also von dem netzwerkthread einfach den Text des Labels ändern). Ich weiß zwar, dass es die Möglichkeit gibt das ganze mit Queues zu machen, aber wie das hier konkret aussehen soll davon hab ich keinen Plan -,-

Code: Alles auswählen

'''
Created on 22.08.2010

@author: Christoph
'''

import socket
import Image
import Tkinter
from threading import Thread



class MotionDetectionClient(Thread): 
    def run(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind(("", 50000))
        s.listen(1)

        try:
            while True:
                komm, addr = s.accept()
                while True:
                    data = komm.recv(1024)

                    if not data:
                        komm.close()
                        break
                    print("Nachricht!")
        finally:
            s.close()


class GUIMotionDetectionClient(Tkinter.Frame):
    def __init__(self, master = None):
        Tkinter.Frame.__init__(self, master)
        self.pack()
        self.createWidgets()

    def createWidgets(self):
        self.QUIT = Tkinter.Button(self)
        self.QUIT["text"] = "ENDE"
        self.QUIT["fg"]   = "red"
        self.QUIT["command"] =  self.quit

        self.QUIT.pack({"side": "left"})
        
        self.status = Tkinter.Label(fg = "green", text="Keine Nachricht!")
        self.status.pack()
        
    
client = MotionDetectionClient()
client.start()

root = Tkinter.Tk()
app = GUIMotionDetectionClient(master=root)
app.mainloop()
root.destroy()
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Den Hinweis hat BlackJack doch schon gegeben: Du erzeugst eine Queue, in die du alle Ergebnisse des Netzwerk-Threads packst. Mittelst der after-Methode von Tkinter kannst du dann prüfen, ob in der Queue etwas vorliegt und den Inhalt dann entsprechend verarbeiten. Es gibt übrigens bereits ein queue-Modul, welches für solche Aufgaben gedacht ist.
Das Leben ist wie ein Tennisball.
Bonzo1993
User
Beiträge: 28
Registriert: Freitag 28. August 2009, 22:03

also ich hab das ganze jetzt so gelöst. Ich weiß natürlich nicht ob ihr euch, dass auch so vor gestellt habt. Immerhin habe ich eigentlich mit Paralleler Programmierung nichts am Hut und das dann auch noch in Verbindung mit GUIs -war ein bisschen zu viel für mich ;D

Code: Alles auswählen

'''
Created on 22.08.2010

@author: Christoph
'''

import socket
import Image
import Tkinter
from threading import Thread
import Queue

global schlange 
schlange = Queue.Queue()

class MotionDetectionClient(Thread): 
    def run(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind(("", 50000))
        s.listen(1)

        try:
            while True:
                komm, addr = s.accept()
                while True:
                    data = komm.recv(1024)

                    if not data:
                        komm.close()
                        break
                    
                    schlange.put(data)
        finally:
            s.close()


class GUIMotionDetectionClient(Tkinter.Frame):
    def __init__(self, master = None):
        Tkinter.Frame.__init__(self, master)
        self.pack()
        self.createWidgets()

    def createWidgets(self):
        self.status = Tkinter.Label(text="Keine Bewegung!")
        self.status.pack()
        
        self.QUIT = Tkinter.Button(self)
        self.QUIT["text"] = "ENDE"
        self.QUIT["fg"]   = "red"
        self.QUIT["command"] =  self.quit

        self.QUIT.pack({"side": "left"})
        
    
    def updateLabel(self):
        if schlange.empty():
            self.status["text"] = "Keine Bewegung!"
        else:
            self.status["text"] = schlange.get()
            
        self.master.after(1000,self.updateLabel)
        
        
client = MotionDetectionClient()
client.start()

root = Tkinter.Tk()
app = GUIMotionDetectionClient(master=root)
app.updateLabel()
app.mainloop()
root.destroy()
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo.

Wenn du jetzt noch das global entfernst und die Warteschlange dem Thread und der GUI als Attribute gibst, dann sieht es doch ganz gut aus. Außerdem solltest du den Code auf Modulebene, außer den Imports und den Klassen, noch in eine main-Methode packen und diese so aufrufen:

Code: Alles auswählen

def main():
    queue = ...
    client = ...

if __name__ == "__main__":
    main()
Dann kannst du dein Modul auch von anderen Modulen importieren lassen. Außerdem solltest du dich für deutsche oder englische Bezeichner entscheiden, beides durcheinander ist doch etwas wirr.

Sebastian
Das Leben ist wie ein Tennisball.
Bonzo1993
User
Beiträge: 28
Registriert: Freitag 28. August 2009, 22:03

hi ^^

vielen Dank :D

Könntest du dir diesen Thread vllt. mal anschauen der hat eig. auch viel mit Tkinter und GUI-Programmierung zu tun: http://python-forum.de/viewtopic.php?f=3&t=23949

edit:
Mir ist noch aufgefallen, dass wenn man auf den ENDE-Button klickt, nur die GUI nicht aber der Thread beendet wird. Wie kann ich das ändern? Soll ich dazu eine eigene quit-Methode anlegen (aber von der kann ich doch dann trotzdem den thread nicht beeden, oder)?


Christoph
BlackJack

@Bonzo1993: ``global`` auf Modulebene hat keinerlei Effekt.

Wenn der `MotionDetectionClient` auch in Zukunft nur die `run()`-Methode enthält, ist die Klasse IMHO overkill und eine Funktion täte es auch. Man kann auch Funktionen in einem eigenen Thread starten ohne eine Klasse schreiben zu müssen. Siehe `target`-Argument von `Thread.__init__()`.

Widgets sollten sich in der `__init__()` nicht selber layouten. Das machen die bereits vorhandenen ja auch nicht, und so nimmt man sich die Freiheit das Widget anderweitig zu verwenden.

Beim Status-Label in `createWidgets()` hast Du vergessen das parent/master-Widget anzugeben. Das geht hier zufällig gut, muss es aber nicht.

Durchgehend gross geschriebene Namen werden per Konvention für Konstanten verwendet. Das trifft auf den Quit-Button nicht zu. Der Button muss in diesem Fall auch nicht an das Objekt gebunden werden. Und die Konfiguration, sowie das `pack()`\en sehen "komisch" aus. Das geht einfacher:

Code: Alles auswählen

        tk.Button(self,
                  text='ENDE',
                  fg='red',
                  command=self.quit).pack(side=tk.LEFT)
(Setzt das gebräuchliche ``import Tkinter as tk`` voraus.)

Du mischst hier verschiedene `pack()`-Richtungen innerhalb eines Containerwidgets, denn Default ist 'top'. Das kann auch zu sehr komischen Ergebnissen führen.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo Bonzo1993,

mal ohne queue und threading.

server: http://paste.pocoo.org/show/255329/

client: http://paste.pocoo.org/show/255331/

Wir werden mal sehen, was die Profis dazu sagen ?

Gruß Frank
Antworten