Comment déclarer un signal Qt dans une classe/interface abstraite lorsque la classe d'implémentation est déjà dérivée de QObject/QWidget?
class IEmitSomething
{
public:
// this should be the signal known to others
virtual void someThingHappened() = 0;
}
class ImplementEmitterOfSomething : public QWidget, public IEmitSomething
{
// signal implementation should be generated here
signals: void someThingHappended();
}
Comme je l'ai découvert au cours des derniers jours ... la façon de procéder de Qt est la suivante:
class IEmitSomething
{
public:
virtual ~IEmitSomething(){} // do not forget this
signals: // <- ignored by moc and only serves as documentation aid
// The code will work exactly the same if signals: is absent.
virtual void someThingHappened() = 0;
}
Q_DECLARE_INTERFACE(IEmitSomething, "IEmitSomething") // define this out of namespace scope
class ImplementEmitterOfSomething : public QWidget, public IEmitSomething
{
Q_OBJECT
Q_INTERFACES(IEmitSomething)
signals:
void someThingHappended();
}
Vous pouvez maintenant vous connecter à ces signaux d'interface.
Si vous n'avez pas accès à l'implémentation lors de la connexion au signal, votre déclaration de connexion nécessitera un transtypage dynamique en QObject
:
IEmitSomething* es = ... // your implementation class
connect(dynamic_cast<QObject*>(es), SIGNAL(someThingHappended()), ...);
... et ainsi, vous n'êtes pas obligé d'exposer la classe d'implémentation aux abonnés et aux clients. Ouais!!!
Dans Qt, "signaux" est synonyme de "protégé". Mais cela aide MOC à générer le code nécessaire. Donc, si vous avez besoin d'une interface avec certains signaux, vous devez les déclarer en tant que méthodes protégées abstraites virtuelles. Tous les codes nécessaires seront générés par MOC - vous pourrez voir des détails, que "emit somesignal" sera remplacé par un appel virtuel de méthode protégée avec le même nom. Notez que le corps de with méthode est également généré par Qt.
UPDATE: Exemple de code:
MesInterfaces.h
#pragma once
struct MyInterface1
{
signals:
virtual void event1() = 0;
};
struct MyInterface2
{
signals:
virtual void event2() = 0;
};
MonImpl.h
#ifndef MYIMPL_H
#define MYIMPL_H
#include <QObject>
#include "MyInterfaces.h"
class MyImpl
: public QObject
, public MyInterface1
, public MyInterface2
{
Q_OBJECT
public:
MyImpl( QObject *parent );
~MyImpl();
void doWork();
signals:
void event1();
void event2();
};
class MyListner
: public QObject
{
Q_OBJECT
public:
MyListner( QObject *parent );
~MyListner();
public slots:
void on1();
void on2();
};
#endif // MYIMPL_H
MonImpl.cpp
#include "MyImpl.h"
#include <QDebug>
MyImpl::MyImpl(QObject *parent)
: QObject(parent)
{}
MyImpl::~MyImpl()
{}
void MyImpl::doWork()
{
emit event1();
emit event2();
}
MyListner::MyListner( QObject *parent )
{}
MyListner::~MyListner()
{}
void MyListner::on1()
{
qDebug() << "on1";
}
void MyListner::on2()
{
qDebug() << "on2";
}
main.cpp
#include <QCoreApplication>
#include "MyImpl.h"
int main( int argc, char *argv[] )
{
QCoreApplication a( argc, argv );
MyImpl *invoker = new MyImpl( NULL );
MyListner *listner = new MyListner( NULL );
MyInterface1 *i1 = invoker;
MyInterface2 *i2 = invoker;
// i1, i2 - not QObjects, but we are sure, that they will be.
QObject::connect( dynamic_cast< QObject * >( i1 ), SIGNAL( event1() ), listner, SLOT( on1() ) );
QObject::connect( dynamic_cast< QObject * >( i2 ), SIGNAL( event2() ), listner, SLOT( on2() ) );
invoker->doWork();
return a.exec();
}
La déclaration de signaux en tant que méthodes abstraites dans les interfaces pose deux problèmes:
Un signal est un signal du point de vue de Qt uniquement lorsqu'il est implémenté d'une manière particulière - à savoir, lorsque l'implémentation est générée par moc et est incluse dans les métadonnées de l'objet.
Il est généralement mauvais d’émettre des signaux directement de l’extérieur d’un objet.
En corollaire, l'interface étant abstraite, vous n'avez pas vraiment besoin de déclarer ses signaux. Elle n'a pas d'autre fonction que de documenter l'intention, puisque:
Si un signal est implémenté dans une classe dérivée de l'interface, vous pouvez utiliser le système métaobjet pour vérifier sa présence.
De toute façon, vous n'êtes pas censé appeler directement ces méthodes de signal.
Une fois que vous avez dynamisé l'interface non-objet en QObject
, il n'importe plus que l'implémentation dérive de l'interface.
Les seules raisons valables pour une telle gymnastique seraient les suivantes:
Coax doxygen ou un autre générateur de documentation à fournir une documentation pour votre code.
Forcer la classe concrète à implémenter une méthode portant le même nom. Cela ne garantit évidemment pas qu'il s'agisse d'un signal.
Nous voulons tous nous débarrasser définitivement du MOC, mais en attendant, je veux ajouter une alternative qui fonctionne sans inclure QObject.h et sans utiliser Q_OBJECT et Q_INTERFACE dans la classe d'interface.
Commencez par définir une fonction de connexion abstraite dans l'interface:
class I_Foo
{
public:
virtual void connectToSignalA(const QObject * receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection) = 0;
};
Maintenant, dans la classe dérivée, remplacez la fonction. Déclarez également le signal, ajoutez Q_OBJECT, etc.
class Bar : public QObject, public I_Foo
{
Q_OBJECT
public:
void connectToSignalA(const QObject * receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection);
signals:
void A();
};
Ensuite, à l'intérieur de cette classe .cpp, connectez-vous:
Bar::connectToSignalA(const QObject * receiver, const char *method, Qt::ConnectionType void type)
{
connect(this, SIGNAL(A()), receiver, method, type);
}
La mise en garde est que vous devez écrire la fonction connect dans chaque classe dérivée et que vous devez utiliser old-style-connect (ou peut-être utiliser une fonction template), mais c'est à peu près tout.