PyQt4 - Signal/Slot im Thread

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
solar22
User
Beiträge: 27
Registriert: Donnerstag 14. Oktober 2010, 20:31

Hallo!

Ich bau grad für ein Schulprojekt einen kleinen Chat mit Server und Client.
Der Server funktioniert soweit. Der Client kann sich anmelden mit seiner IP und die Nachrichten werden auch brav an alle Clients verschickt.
Im Clienten sieht das so aus:
Die Hauptklasse hat die GUI (kann ja nur die bei Qt).
Und in einem Thread läuft ein kleiner Server der auf einem anderen Port die Nachrichten empfängt, welcher der eigentliche Server an alle Clients schickt.
Jetzt hab ich das so gemacht, das dieser "kleine Clientserver" ein Signal sendet ("newData()"), sobald was reinkommt und eine Variable self.newdaten mit dem Text beschreibt.
In der Hauptklasse ist ein Slot welcher dann in eine Funktion springt und den Text in eine Textarea appenden soll.
Ich lass zum Test self.newdaten auch in der Konsole ausgeben (da funktioniert es! Die Daten kommen also an...)
Aber die GUI macht nix...ich weiß das bei Qt nur die Hauptklasse GUI machen kann, aber ist durch das Signal/Slot gemache das nicht eh schon beachtet worden?
Hier mal der Code des Clienten:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: ISO-8859-1 -*-

'''
Import von notwendigen Bibliotheken und Designdateien
'''

import sys,os,time,urllib, webbrowser,math 	
from PyQt4 import QtGui, QtCore, QtWebKit 	
import threading			
import socket 

from PyQt4 import uic			
from PyQt4 import QtGui as qg		
from PyQt4 import QtCore as qc		
from PyQt4.QtTest import QTest as qt	

class ChatClient(QtGui.QMainWindow): 

    	def __init__(self, parent=None):
        	super(ChatClient, self).__init__(parent)
		uic.loadUi(os.path.dirname(os.path.abspath(__file__))+"/client.ui", self) #Designdatei 

		'''
		Aktueller Ordner in Variable speichern
		'''

		self.pfad = os.path.dirname(os.path.abspath(__file__))+"/"
	
		'''
		Slots und Signals setzten
		'''
	
		#self.connect(self.addWP,QtCore.SIGNAL("clicked()"),self.addCache)
		self.connect(self.sendText,QtCore.SIGNAL("clicked()"),self.sendTextToServer)
		self.connect(self, QtCore.SIGNAL('newData()'), self.newText) #Slot

		'''
		ip abfragen
		'''
	
		#self.ip, ok = QtGui.QInputDialog.getText(self, 'Serveradresse?', 'ServerIP:')
		#self.nickname, ok = QtGui.QInputDialog.getText(self, 'Nick?', 'Nickname:')
		#self.ip = int(self.ip)
		self.ip="localhost"
		self.nickname="devi"
		self.port = 50000

		'''
		connect
		'''

		
		self.loginOnServer()
	def openSocket(self):
		self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
		self.chatText.append("Socket open...")
		print threading.active_count()

	def sendTextToServer(self):
		self.openSocket()
		self.socket.sendto("["+self.nickname+"] "+self.userText.text(), (self.ip, self.port)) 
		self.chatText.append("Send text ["+self.nickname+"] "+self.userText.text())
		self.socket.close()

	def loginOnServer(self):
		self.openSocket()
		self.socket.sendto("/login", (self.ip, self.port))
		self.socket.close()
		self.chatText.append("Login on server "+str(self.ip)+":"+str(self.port)+"...done")
		self.chatText.append("Ready to chat...")
	def newText(self):   #Funktion die den Text appenden soll
		print "newdaten:"+self.newdaten #Funktioniert!
		self.chatText.append(self.newdaten) #Funktioniert nicht...
		print "newtext-ende"

	def run(self):
		s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

		try: 
		    s.bind(("", 50001)) 
		    while True: 
			daten, addr = s.recvfrom(1024)
			print "Empfange: %s" % (daten)  #Funktioniert!
			self.newdaten = daten
			self.emit(QtCore.SIGNAL('newData()')) #Signal
		finally: 
    			s.close()
app = QtGui.QApplication(sys.argv)        
dialog = ChatClient() 
dialog.show()

th = threading.Thread(target = ChatClient().run)
th.start()
sys.exit(app.exec_())
Hat da wer eine Idee, warum das nicht geht :K ?

Gruß!

Alex
BlackJack

@solar22: Ich weiss nicht ob ich's komplett durchschaut habe, aber Du erzeugst *zwei* `ChatClient`-Exemplare in zwei verschiedenen Threads -- und die enthalten beide GUI-Code. Das ist schon mal falsch und Zufall dass Dir das nicht härter um die Ohren fliegt.

Definierst Du den Slot `newData()` eigentlich auch irgendwo? Falls das irgendwie so funktioniert, dann hast Du den Slot des nicht sichtbaren Dialogs im zweiten Thread mit dem Signal verbunden. Kein Wunder dass man dass dann nicht sieht.

Du solltest vielleicht nicht alles in eine Klasse packen, sondern tatsächlich eine für die GUI und eine für den zweiten Thread erstellen (oder dafür vielleicht auch nur eine Funktion). Und vielleicht ist die `QThread`-Klasse auch einen Blick wert, denn AFAIK kommt die auch schon mit dem Signal/Slot-Mechanismus ausgestattet daher und fügt sich damit besser in das Qt-Rahmenwert ein.
solar22
User
Beiträge: 27
Registriert: Donnerstag 14. Oktober 2010, 20:31

Hallo!
Ich hab mir den QThread mal angeschaut anhand dieses Beispieles http://orangepalantir.org/topicspace/index.php?idnum=33 und es versucht zu implementieren.
Das sieht folgermaßen aus:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: ISO-8859-1 -*-

'''
Import von notwendigen Bibliotheken und Designdateien
'''

import sys,os,time,urllib, webbrowser,math 	
from PyQt4 import QtGui, QtCore, QtWebKit 	
import threading			
import socket 

from PyQt4 import uic			
from PyQt4 import QtGui as qg		
from PyQt4 import QtCore as qc		
from PyQt4.QtTest import QTest as qt	

class ChatClient(QtGui.QMainWindow): 

    	def __init__(self, parent=None):
        	super(ChatClient, self).__init__(parent)
		uic.loadUi(os.path.dirname(os.path.abspath(__file__))+"/client.ui", self) #Designdatei 

		'''
		Aktueller Ordner in Variable speichern
		'''

		self.pfad = os.path.dirname(os.path.abspath(__file__))+"/"
	
		'''
		Slots und Signals setzten
		'''
	
		self.DataCollector = clientserver(self)
		self.DataCollector.start()
		#self.connect(self.addWP,QtCore.SIGNAL("clicked()"),self.addCache)
		self.connect(self.sendText,QtCore.SIGNAL("clicked()"),self.sendTextToServer)
		#self.connect(self, QtCore.SIGNAL('newData()'), self.newText)
		self.connect(self.DataCollector,QtCore.SIGNAL("newData ( QString ) "), self.newText)

		'''
		ip abfragen
		'''
	
		#self.ip, ok = QtGui.QInputDialog.getText(self, 'Serveradresse?', 'ServerIP:')
		#self.nickname, ok = QtGui.QInputDialog.getText(self, 'Nick?', 'Nickname:')
		#self.ip = int(self.ip)
		self.ip="localhost"
		self.nickname="devi"
		self.port = 50000

		'''
		connect
		'''

		
		self.loginOnServer()




	def openSocket(self):
		self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
		self.chatText.append("Socket open...")
		#print threading.active_count()

	def sendTextToServer(self):
		self.openSocket()
		self.socket.sendto("["+self.nickname+"] "+self.userText.text(), (self.ip, self.port)) 
		self.chatText.append("Send text ["+self.nickname+"] "+self.userText.text())
		self.socket.close()

	def loginOnServer(self):
		self.openSocket()
		self.socket.sendto("/login", (self.ip, self.port))
		self.socket.close()
		self.chatText.append("Login on server "+str(self.ip)+":"+str(self.port)+"...done")
		self.chatText.append("Ready to chat...")
	def newText(self,newdaten):
		print "newdaten:"+newdaten
		self.chatText.append(newdaten)
		print "newtext-ende"

	
class clientserver(QtCore.QThread):
	def __init__(self,parent=None):
		QtCore.QThread.__init__(self,parent)

	def run(self):
		s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

		try: 
		    s.bind(("", 50009)) 
		    while True: 
			daten, addr = s.recvfrom(1024)
			print "Empfange: %s" % (daten)
			ChatClient().newdaten = daten
			#ChatClient().emit(QtCore.SIGNAL('newData()'))
			self.emit(QtCore.SIGNAL("NewData( QString )"),daten)
		finally: 
    			s.close()

app = QtGui.QApplication(sys.argv)        
dialog = ChatClient() 
dialog.show()

#th = threading.Thread(target = clientserver().run)
#th.start()
sys.exit(app.exec_())
Leider erzeugt das Programm einen Speicherzugriffsfehler (mir wird aber keine Zeile angezeigt wo) und die Funktion newText() wird nicht aufgerufen.
Hier die komplette Terminalausgabe:
alexander@alexander-laptop:/var/www/pychat$ python client.py
Empfange: [devi] dfds
QObject::setParent: Cannot set parent, new parent is in a different thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QThread: Destroyed while thread is still running
Speicherzugriffsfehler
alexander@alexander-laptop:/var/www/pychat$
solar22
User
Beiträge: 27
Registriert: Donnerstag 14. Oktober 2010, 20:31

Ok, die Situation hat sich geändert.
Die Zeile

Code: Alles auswählen

ChatClient().newdaten=daten
war schuld an dem Speicherzugriffsfehler.
Der Thread empfängt die Daten (Konsolenausgabe ist korrekt) gibt diese aber falsch an newText() weiter.
Denn er schickt von jeder Nachricht immer nur das erste Zeichen weiter.
Also zB [User] Text wird zu [
In Signal/Slot steht doch aber QString und Strings dürfen doch länger als ein Zeichen sein, oder?
Antworten