2
votes

I have a command inherited from QUndoCommand:

class ImportEntityCommand : public QUndoCommand
{
    // ...

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

The redo method kicks off a QProcess:

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

Signal-slot connection is composed in command constructor:

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

The signal emitted by the process is handled like this:

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

But I'm receiving such error while compiling:

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 *,void **,QtPrivate::QSlotObjectBase *,Qt::ConnectionType,const int *,const QMetaObject *)': cannot convert argument 3 from 'const ImportEntityCommand *' to 'const QObject *'

The error point is that:

cannot convert argument 3 from 'const ImportEntityCommand *' to 'const QObject *'

I'm inhering my ImportEntityCommand class from QUndoCommand which in turn inherits from QObject, I guess.

Question

Therefore, for some reason, Qt won't allow me to handle signals inside a command inherited from QUndoCommand.

  • Is this limitation intentional?
  • What are my options to work around this limitation? What is the standard procedure?
2

2 Answers

2
votes

I'm inhering my ImportEntityCommand class from QUndoCommand which in turn inherits from QObject, I guess.

QUndoCommand does not inherit from QObject see:

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

Compared to a QWidget for example, that does inherit from QObject.

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

If you want your importer to handle signals and slots you can just inherit from QObject:

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

and you can connect to a lambda e.g.:

QObject::connect(m_importer
                 , &Importer::importFinished
                 [&]() { this->handleImportFinish() }
                 );
1
votes

Could you not just use composition to place a QObject derived class instance in the ImportEntityCommand? You could then connect the signal to the QObject derived class which would then call in to ImportEntityCommand to do the work.

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

Then in your ImportEntityCommand, you would create a SignalReceiver and connect it to the signal:

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

We can't get around from passing a raw pointer of ImportEntityCommand to SignalReceiver since ImportEntityCommand has to live in the undo stack. On the other hand, using a QScopedPointer for the receiver ensures that it is cleaned up properly when the undo stack deletes the ImportEntityCommand instance. Just remember to always specify the QScopedPointerDeleteLater when wrapping a QObject derivative -- especially one that is catching signals.