38

当实现类已经从 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();
}
4

4 回答 4

60

正如我在最后几天发现的那样...... 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();
}

现在您可以连接到这些接口信号。

如果您在连接到信号时无权访问实现,您的连接语句将需要动态转换为QObject

IEmitSomething* es = ... // your implementation class

connect(dynamic_cast<QObject*>(es), SIGNAL(someThingHappended()), ...);

...这样您就不必将实现类公开给订阅者和客户。是的!!!

于 2013-08-07T20:52:11.563 回答
17

在 Qt 中,“信号”是“受保护”的同义词。但它有助于 MOC 生成必要的代码。因此,如果您需要与某些信号的接口 - 您应该将它们声明为虚拟抽象受保护方法。所有必需的代码都将由 MOC 生成 - 您可能会看到详细信息,“发出一些信号”将被替换为具有相同名称的受保护方法的虚拟调用。请注意,with 方法的主体也是由 Qt 生成的。

更新:示例代码:

我的接口.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";
}

主文件

#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();
}
于 2013-07-30T09:49:49.877 回答
6

在接口中将信号声明为抽象方法有两个问题:

  1. 信号是从 Qt 的角度来看的信号,仅当以特定方式实现时 - 即,当实现由 moc 生成并包含在对象的元数据中时。

  2. 直接从物体外部发出信号通常是糟糕的设计。

作为推论,由于接口是抽象的,你根本不需要声明它的信号——它除了记录意图之外没有其他用途,因为:

  1. 如果信号是在派生自接口的类中实现的,则可以使用元对象系统来验证它的存在。

  2. 无论如何,您不应该直接调用这些信号方法。

  3. 一旦您将非对象接口动态转换为QObject,实现从接口派生就不再重要了。

做这种体操的唯一正当理由是:

  1. 哄 doxygen 或其他文档生成器为您的代码提供文档。

  2. 强制具体类具有同名方法的实现。这当然不能保证它实际上是一个信号。

于 2015-06-26T17:32:41.297 回答
2

我们都希望永远摆脱 MOC,但在此之前,我想添加一个替代方案,它可以在不包含 QObject.h 且不使用接口类中的 Q_OBJECT 和 Q_INTERFACE 的情况下工作。

首先在接口中定义一个抽象的connect函数:

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);
}

需要注意的是,您必须在每个派生类中编写连接函数,并且必须使用旧式连接(或者可能使用模板函数),但仅此而已。

于 2019-02-08T09:00:48.980 回答