对异步运行并发出信号的命令使用撤消堆栈 问题

问题描述

我有一个QUndoCommand 继承的命令:

class ImportEntityCommand : public QUndoCommand
{
    // ...

private:
    QString m_importedEntityName;
    Importer *m_importer;
    // ...
}

redo 方法启动 QProcess:

void ImportEntityCommand::redo()
{
    if (/* Import is already done before */) {
        // ...
    } else {
        // Import finish is handled by a slot
        m_importer->import(m_url);
    }
}

信号槽连接由命令构造函数组成:

ImportEntityCommand::ImportEntityCommand(EditorSceneItemmodel *sceneModel,const QUrl &url) :,m_importer(new Importer(/* ... */))
{

    // Importer would start a QProcess which runs asynchronously and emits a signal
    // that's why I have to handle the signal by a slot
    QObject::connect(m_importer,&Importer::importFinished,this,&ImportEntityCommand::handleImportFinish
                     );
}

进程发出的信号是这样处理的:

void ImportEntityCommand::handleImportFinish(const QString name)
{
    m_importedEntityName = name;
}

但是我在编译时收到这样的错误

C:\Qt\qt5.12.9\5.12.9\msvc2017_64\include\QtCore\qobject.h:250: error: C2664: 'QMetaObject::Connection QObject::connectImpl(const QObject *,void **,const QObject *,QtPrivate::QSlotObjectBase *,Qt::ConnectionType,const int *,const QMetaObject *)':无法将参数 3 从 'const ImportEntityCommand *' 转换为 'const QObject *'

错误点在于:

无法将参数 3 从“const ImportEntityCommand *”转换为“const QObject *”

我的 ImportEntityCommand 类继承自 QUndoCommand ,我猜它又继承自 QObject

问题

因此,出于某种原因,Qt 不允许我处理继承自 QUndoCommand 的命令内的信号。

  • 这个限制是故意的吗?
  • 我有哪些选择可以解决此限制?标准程序是什么?

解决方法

我从 QUndoCommand 继承了我的 ImportEntityCommand 类,其中 我猜是从 QObject 继承的。

QUndoCommand 不继承自 QObject 参见:

https://doc.qt.io/qt-5/qundocommand.html

例如与 QWidget 相比,它确实继承自 QObject。

https://doc.qt.io/qt-5/qwidget.html

如果你想让你的导入器处理信号和槽,你可以从 QObject 继承:

getResult({  getValue("Hello") })

并且您可以连接到 lambda,例如:

class importer: public QObject
{
    Q_OBJECT
public:
    ...
private:
    ...
}
,

难道你不能只使用组合在ImportEntityCommand 中放置一个QObject 派生类实例吗?然后,您可以将信号连接到 QObject 派生类,然后该派生类将调用 ImportEntityCommand 来完成工作。

class SignalReceiver : public QObject
{
    Q_OBJECT
public:
    SignalReciever(ImportEntityCommand *iec) : QObject(),_iec(iec)
    {}
public slot:
    void handleImportFinished(QString url)
    {
        if (this->_iec) this->_iec->handleImportFinished(url);
    }
private:
    ImportEntityCommand *_iec{nullptr};
};

然后在您的 ImportEntityCommand 中,您将创建一个 SignalReceiver 并将其连接到信号:

class ImportEntityCommand : public QUndoCommand
{
    // ...same as before,then
private:
    QScopedPointer<SignalReceiver,QScopedPointerDeleteLater> _signalReceiver;
};
// in the ImportEntityCommand constructor
    this->_signalReceiver.reset(new SignalReceiver(this));
    QObject::connect(this->importer,&Importer::importFinished,this->_signalReciver.data(),&SignalReceiver::handleImportFinished);

我们无法避免将 ImportEntityCommand 的原始指针传递给 SignalReceiver,因为 ImportEntityCommand 必须位于撤消堆栈中。另一方面,对接收器使用 QScopedPointer 可确保在撤消堆栈删除 ImportEntityCommand 实例时正确清理它。只需记住在包装 QScopedPointerDeleteLater 派生时总是指定 QObject - 尤其是捕捉信号的派生。