PyQt4: connectSLotsByName funktioniert nicht

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
LukasHetzi
User
Beiträge: 14
Registriert: Donnerstag 25. Januar 2007, 12:01

hallo,

kann mir jemand mit PyQt4 helfen?
Ich möchte ein Signal von einer Klasse zu einer anderen schicken.
Weiß jemand, warum es mit connectSLotsByName nicht funktioniert?

Code: Alles auswählen

	import sys 
	from PyQt4.QtCore import * 
	 
	class helper(QObject): 
	    def __init__(self,  parent): 
	        QObject.__init__(self) 
	        parent.setObjectName("helper") 
	        QMetaObject.connectSlotsByName(parent) 
	 
	    def send(self): 
	        print "sending" 
	        self.emit(SIGNAL("test")) 
	 
	class main(helper,  QObject): 
	    def __init__(self,  parent=None): 
	        QObject.__init__(self) 
	 
	        self.send() 
	 
	    @pyqtSignature("") 
	    def on_helper_test(self): 
	        print "working" 
	 
	if __name__ == "__main__": 
	    app = QApplication(sys.argv) 
	    main = main() 
	    app.exec_() 
Aber mit einem normalen connect geht es problemlos:

Code: Alles auswählen

	import sys 
	from PyQt4.QtCore import * 
	from PyQt4.QtGui import * 
	 
	class helper(QObject): 
	    def __init__(self,  parent): 
	        QObject.__init__(self) 
	        parent.setObjectName("helper") 
	        #QMetaObject.connectSlotsByName(parent) 
	 
	    def send(self): 
	        print "sending" 
	        self.emit(SIGNAL("test()")) 
	 
	class main(helper,  QObject): 
	    def __init__(self,  parent=None): 
	        QObject.__init__(self) 
	        self.connect(self, SIGNAL("test()"), self.test_connect) 
	        self.send() 
	 
	    #@pyqtSignature("") 
	    #def on_helper_test(self): 
	    #    print "working" 
	 
	    def test_connect(self): 
	       print "working, connect" 
	 
	if __name__ == "__main__": 
	    app = QApplication(sys.argv) 
	    main = main() 
	    app.exec_() 
Ich bin für jede Antowrt dankbar.
lunar

Im Prinzip ist die Sache schon schräg, wenn in einer Vererbungshierarchie bidirektionale Nebenwirkungen auftreten, wenn also die abgeleitete Klasse eine Methode der Basis-Klasse aufruft, die indirekt wiederum eine Methode der abgeleiteten Klasse aufruft. Das ist meistens ein Zeichen dafür, dass die Aufgaben der einzelnen Klassen nicht ausreichend isoliert sind.

Ganz konkret wird die ``__init__``-Methode der Basisklasse nie aufgerufen. Ergo wird auch der Objektname von ``parent`` nie neu gesetzt, und ``connectSlotsByName`` steht da auch nur zu dekorativen Zwecken ;) Allerdings hätte sie auch keinen Effekt, wenn sie aufgerufen werden würde. ``connectSlotsByName`` durchsucht nur die Kind-Objekte des Qt-Objektbaums. Der hat allerdings nichts mit Vererbung zu tun (schon gar nicht in der Python-Domäne), sondern basiert auf explizit definierten Verhältnissen innerhalb von Container-Klassen. In deinem Beispiel hat dieser Objektbaum nur eine Ebene, also keine Kind-Klassen, ergo kann ``connectSlotsByName`` auch kein Kind-Objekt mit dem Namen "helper" finden.

Das ganze Beispiel macht überhaupt gar keinen Sinn, ist mehr als merkwürdig entworfen, und noch dazu funktioniert es nicht. Meiner Meinung nach musst du noch viel über Objekt-Orientiernung und Qt's Signal-Prinzip lernen.

Bevor ich dir dabei helfe, dein Beispiel zu korrigieren, wäre es vielleicht besser, du erzählst mal, was du überhaupt machen willst. Dann können wir das Problem vielleicht direkt bei der Wurzel packen, anstatt an den Symptomen rumzudoktoren.
LukasHetzi
User
Beiträge: 14
Registriert: Donnerstag 25. Januar 2007, 12:01

Hallo,

ich möchte eine Verbindung zu meinen Handy herstellen.

Dazu habe ich derzeit 3 Klassen:
  • ui_main : Mit pyuic generiertes GUI
  • libbluetooth : Zum Aufbau der Verbindung; sendet Signale bei bestimmten Events (z.B. neue SMS, Telefonanruf,...)
  • s60_Main : Main-Klasse; Initialisierung der GUI; in ihr werden alle Signale verwaltet (muss hier gemacht werden, da ich bei einem Signal von libbluetooth etwas an der GUI ändern muss)
  • settings: Für später geplant, zum Verwalten aller Einstellungen
Ich wollte alle Signale, die in den Klassen ui_main und libbluetooth entstehen in der main-Klasse abfangen.
Weisst du eine bessere Art, wie man dieses Problem lösen könnte?

Ich bin leider noch ein Neuling mit Python und Qt und finde nicht viele Beispiele die sich mit meinem Problem beschäftigen. Ich habe ein Buch über PyQt4, doch da findet man auch nicht viel über die Verwendung mehrerer Klassen.

mfg
Lukas
lunar

LukasHetzi hat geschrieben:Ich wollte alle Signale, die in den Klassen ui_main und libbluetooth entstehen in der main-Klasse abfangen.
Weisst du eine bessere Art, wie man dieses Problem lösen könnte?
Auf ganz normale Art und Weise eben: Entweder durch manuelle Verbindung mit ``QObject.connect`` oder durch ``QMetaObject.connectSlotsByName``. Bei letzterem musst du halt nur dafür sorgen, dass alle Objekte, deren Signale du abfangen willst, Kind-Objekte deines Hauptfenster-Objekts sind. Du musst also immer schön bei der Erzeugung einer Instanz das ``parent`` Argument übergeben, und dieses bei abgeleiteten Klassen an den Konstruktor der Basisklasse weitergeben.

Ich weiß, dass dir das jetzt wenig hilft, aber ich sehe leider dein Problem nicht... du musst doch einfach nur die Signale abfangen, ich verstehe nicht, warum dich das zu so einer seltsamen Vererbungshierarchie wie im Beispiel geführt hat. :?
LukasHetzi
User
Beiträge: 14
Registriert: Donnerstag 25. Januar 2007, 12:01

lunar hat geschrieben:... Bei letzterem musst du halt nur dafür sorgen, dass alle Objekte, deren Signale du abfangen willst, Kind-Objekte deines Hauptfenster-Objekts sind. Du musst also immer schön bei der Erzeugung einer Instanz das ``parent`` Argument übergeben, und dieses bei abgeleiteten Klassen an den Konstruktor der Basisklasse weitergeben.
Hallo,

ich habe das jetzt so versucht, es funktioniert aber noch immer nicht:

Code: Alles auswählen

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class helper(QObject):
    def __init__(self,  parent):
        super(helper, self).__init__(parent)
        parent.setObjectName("helper")

        print "self", self
        print "parent", parent

    def send(self):
        print "sending"
        self.emit(SIGNAL("test(QString)"), QString("hello"))

class main(QObject):
    def __init__(self,  parent=None):
        super(main, self).__init__(parent)
        self.helper = helper(self)
        self.helper.send()

    def on_helper_test(self):
        print "it works!"

if __name__ == "__main__":
    app = QApplication(sys.argv)
    main = main()
    app.exec_()

Es müsste aber das richtige Parent übergeben werden:

Code: Alles auswählen

[lukas@noname ~]$ python test.py
self <__main__.helper object at 0x2aaaaaafcb78>
parent <__main__.main object at 0x2aaaaaafcaf0>
sending
lunar

LukasHetzi hat geschrieben:ich habe das jetzt so versucht, es funktioniert aber noch immer nicht:
Wie auch? Du verbindest ja nirgendwo Signale mit Slots. Im Übrigen sind die Sternchen-Imports schlecht, weil sie den Namensraum zumüllen, und den Objektname des Vaterobjekts im Kind zu ändern, ist auch nicht wirklich guter Stil. Ein Objekt bestimmt selbst, wie es heißt, andernfalls werden die Nebenwirkungen der dauernden Namensänderungen schnell zum Problem.
LukasHetzi
User
Beiträge: 14
Registriert: Donnerstag 25. Januar 2007, 12:01

lunar hat geschrieben:Wie auch? Du verbindest ja nirgendwo Signale mit Slots.
Hast du ein kleines Beispiel wie das funktionieren könnte?
lunar hat geschrieben:und den Objektname des Vaterobjekts im Kind zu ändern...
Das kann ich weglassen, ich habe es nur verwendet weil es auch im von pyuic erzeugten Code vorkommt.
lunar

LukasHetzi hat geschrieben:
lunar hat geschrieben:Wie auch? Du verbindest ja nirgendwo Signale mit Slots.
Hast du ein kleines Beispiel wie das funktionieren könnte?
Ersetze mal ``parent.setObjectName("helper")`` durch ``self.setObjectName("helper")``. Du willst ja wohl, dass das Helper-Objekt "Helper" heißt ;)

Dann rufst du in der __init__-Methode des ``main`` Objekts ``QMetaObject.connectSlotsByName(self)`` auf. Dann sollte das Signal mit dem Slot verbunden werden.

Im Übrigen solltest du im Bezug auf Klassennamen PEP 8 einhalten, sie also groß schreiben.
lunar hat geschrieben:und den Objektname des Vaterobjekts im Kind zu ändern...
Das kann ich weglassen, ich habe es nur verwendet weil es auch im von pyuic erzeugten Code vorkommt.[/quote]
Du solltest dir automatisch generierten Code niemals zum Vorbild nehmen. Dieser Code wird nie von Menschen bearbeiten, ergo kann er richtig schlecht sein, ohne dass es jemanden interessieren muss.
LukasHetzi
User
Beiträge: 14
Registriert: Donnerstag 25. Januar 2007, 12:01

Es funktioniert leider noch immer nicht.
Ich weiß nicht, was ich noch falsch gemacht habe.

Code: Alles auswählen

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class Helper(QObject):
    def __init__(self,  parent):
        super(Helper, self).__init__(parent)
        self.setObjectName("helper")

    def send(self):
        print "sending"
        self.emit(SIGNAL("test(QString)"), QString("hello"))

class Main(QObject):
    def __init__(self,  parent=None):
        super(Main, self).__init__(parent)
        self.helper = Helper(self)

        QMetaObject.connectSlotsByName(self)  # connectSlotsByName(self.helper) funktioniert auch nicht

        self.helper.send()

    def on_helper_test(self):
        print "it works!"

if __name__ == "__main__":
    app = QApplication(sys.argv)
    main = Main()
    app.exec_()
LukasHetzi
User
Beiträge: 14
Registriert: Donnerstag 25. Januar 2007, 12:01

lunar hat geschrieben:... oder durch ``QMetaObject.connectSlotsByName``. Bei letzterem musst du halt nur dafür sorgen, dass alle Objekte, deren Signale du abfangen willst, Kind-Objekte deines Hauptfenster-Objekts sind.
Ich habe das mal in der interaktiven Konsole überprüft. Das müsste doch stimmen, aber warum funktioniert es bei mir noch nicht?

Code: Alles auswählen

>>> main.children()
[<__main__.Helper object at 0x2aaaaaafcaf0>, <__main__.Helper object at 0x2aaaaaafcaf0>]
>>> main.helper.parent()
<__main__.Main object at 0x2aaaaaafca68>
lunar

Nach allem, was ich so in der Zwischenzeit gelesen habe, scheint es so, als würde PyQt4 Signale beim ersten Auslösen dynamisch deklarieren. Da QMetaObject allerdings eine C++-Klasse, erkennst sie nur statisch deklarierte Signale, kann also mit Python-Signalen nichts anfangen. Irgendwo macht das auch Sinn, immerhin sind die Metainformationen (dazu gehören auch Signale) in Qt4 zur Kompilierzeit festgelegt.

Du wirst dir deine Signale selbst verbinden müssen. Allerdings ist es auch nicht allzu schwer, eine anständige Python-Version von ``QMetaObject.connectSlotsByName`` zu implementieren. Das überlasse ich dir, dabei lernst du sicher viel über den Qt4-Objektbaum ;)
Antworten