実装クラスがすでにQObject/QWidgetから派生している場合、抽象クラス/インターフェースでQtシグナルを宣言する方法は?
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();
}
私が最後の日に見つけたように...これを行うQtの方法は次のとおりです:
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();
}
これで、これらのインターフェース信号に接続できます。
信号に接続するときに実装にアクセスできない場合は、connectステートメントでQObject
への動的キャストが必要になります。
IEmitSomething* es = ... // your implementation class
connect(dynamic_cast<QObject*>(es), SIGNAL(someThingHappended()), ...);
...このようにして、実装クラスをサブスクライバーとクライアントに公開する必要がなくなります。うん!!!
Qtでは、「シグナル」は「保護」の同義語です。ただし、MOCが必要なコードを生成するのに役立ちます。そのため、いくつかの信号とのインターフェースが必要な場合は、それらを仮想抽象保護メソッドとして宣言する必要があります。必要なすべてのコードはMOCによって生成されます。「emit somesignal」が同じ名前の保護されたメソッドの仮想呼び出しに置き換えられる詳細が表示される場合があります。 Qtによって生成されたwithメソッドasloの本体に注意してください。
更新:サンプルコード:
MyInterfaces.h
#pragma once
struct MyInterface1
{
signals:
virtual void event1() = 0;
};
struct MyInterface2
{
signals:
virtual void event2() = 0;
};
MyImpl.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
MyImpl.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();
}
インターフェースでシグナルを抽象メソッドとして宣言するには、2つの問題があります。
シグナルはQtの視点からのみのシグナルです特定の方法で実装された場合-つまり、実装がmocによって生成され、オブジェクトのメタデータに含まれている場合。
通常、オブジェクトの外部から直接信号を発信するのは悪い設計です。
当然のことながら、インターフェースは抽象的であるため、シグナルを宣言する必要はまったくありません。これは、以下の理由から、意図を文書化すること以外に目的はありません。
インターフェースから派生するクラスに信号が実装されている場合、メタオブジェクトシステムを使用してその存在を確認できます。
とにかく、これらのシグナルメソッドを直接呼び出すことは想定されていません。
非オブジェクトインターフェイスをQObject
に動的にキャストすると、実装がインターフェイスから派生していたかどうかは問題ではなくなります。
そのような体操をするために残された唯一の有効な理由は次のようになります:
コードのドキュメントを提供するためにdoxygenまたは別のドキュメントジェネレータを同軸化します。
具象クラスに同じ名前のメソッドの実装を強制します。もちろん、これが実際に信号であることを保証するものではありません。
私たちは皆、MOCを完全に取り除くことを望んでいますが、それが発生するまでは、QObject.hを含めずに、インターフェイスクラスでQ_OBJECTおよびQ_INTERFACEを使用せずに機能する代替案を追加したいと思います。
まず、インターフェースで抽象接続関数を定義します。
class I_Foo
{
public:
virtual void connectToSignalA(const QObject * receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection) = 0;
};
派生クラスで、関数をオーバーライドします。また、信号を宣言し、Q_OBJECTなどを追加します。
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();
};
次に、そのクラス内で.cppが接続を行います。
Bar::connectToSignalA(const QObject * receiver, const char *method, Qt::ConnectionType void type)
{
connect(this, SIGNAL(A()), receiver, method, type);
}
注意点は、すべての派生クラスで接続関数を記述し、古いスタイルの接続を使用する(またはテンプレート関数を使用する)必要があることですが、それはそれだけです。