Использование стека отмены для команды, которая выполняется асинхронно и излучает сигналы

#c #qt #signals #signals-slots #qprocess

#c #qt #сигналы #сигналы-слоты #qprocess

Вопрос:

У меня есть команда, унаследованная от 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 amp;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
                     , amp;Importer::importFinished
                     , this
                     , amp;ImportEntityCommand::handleImportFinish
                     );
}
 

Сигнал, передаваемый процессом, обрабатывается следующим образом:

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

Но я получаю такую ошибку при компиляции:

C:QtQt5.12.95.12.9msvc2017_64includeQtCoreqobject.h:250 : ошибка: C2664: ‘QMetaObject::Connection QObject::connectImpl(const QObject *,void **,const QObject *,void **,QtPrivate::QSlotObjectBase *,Qt::ConnectionType,const int *,const QMetaObject *)’: не удается преобразовать аргумент 3 из ‘constImportEntityCommand *’ в ‘const QObject *’

Точка ошибки заключается в том, что:

не удается преобразовать аргумент 3 из ‘const ImportEntityCommand *’ в ‘const QObject *’

Я использую свой ImportEntityCommand класс, от QUndoCommand которого, в свою очередь, наследуется QObject , я полагаю.

Вопрос

Поэтому по какой-то причине Qt не позволяет мне обрабатывать сигналы внутри команды, унаследованной от QUndoCommand .

  • Является ли это ограничение преднамеренным?
  • Какие у меня есть варианты обойти это ограничение? Какова стандартная процедура?

Ответ №1:

Я использую свой класс ImportEntityCommand из QUndoCommand, который, в свою очередь, наследуется от QObject, я полагаю.

QUndoCommand не наследуется от QObject см.:

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

По сравнению с QWidget, например, это наследуется от QObject.

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

Если вы хотите, чтобы ваш импортер обрабатывал сигналы и слоты, вы можете просто наследовать от QObject:

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

и вы можете подключиться к лямбде, например:

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

Комментарии:

1. Мой Importer класс уже наследует от QObject . На самом деле, мой ImportEntityCommand класс является причиной проблемы, которая наследуется от QUndoCommand , а не QObject .

2. Вы можете подключиться к лямбде, что означает ImportEntityCommand , что это не обязательно должен быть QObject, но тогда вам придется обрабатывать область действия ImportEntityCommand и при необходимости отключать соединение.

3. Правильно, лямбда-подход разрешил ошибку компиляции. Однако мое приложение застревает во время работы, что, вероятно, связано с какой-то другой проблемой. Спасибо =)

Ответ №2:

Не могли бы вы просто использовать composition для размещения экземпляра производного класса QObject в ImportEntityCommand ? Затем вы можете подключить сигнал к производному классу 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,
                     amp;Importer::importFinished,
                     this->_signalReciver.data(),
                     amp;SignalReceiver::handleImportFinished);
 

Мы не можем обойти передачу необработанного указателя ImportEntityCommand на SignalReceiver , поскольку ImportEntityCommand он должен находиться в стеке отмены. С другой стороны, использование a QScopedPointer для получателя гарантирует, что он будет правильно очищен, когда стек отмены удалит ImportEntityCommand экземпляр. Просто не забывайте всегда указывать QScopedPointerDeleteLater при переносе QObject производной, особенно той, которая улавливает сигналы.

Комментарии:

1. В конце концов, я использовал ваш подход, чтобы иметь возможность использовать сигналы / слоты с моими QUndoCommand экземплярами. Спасибо =)