在回调函数中访问 QPushButton

Accessing the QPushButton in the callback function

我想在回调函数中启用按钮。我已尝试执行以下操作,但我得到了:

Runtime received SIGSEGV (address: 0x28 reason: address not mapped to object)
class MyWindow: public QDialog
{
    Q_OBJECT
public: 
   QPushButton *Btn;
   void Scan();
....
};
extern void StartScan(pfcallback);
void MyWindow::Scan()
{
 Btn->setEnabled(false);
 StartScan(Scanfinished);
}
void static Scanfinished()
{
  Btn->setEnabled(true);
}

如何在回调函数中访问按钮Scanfinished()

  1. 您正在尝试手动管理内存。如您所见,很容易使用悬空指针或犯下其他错误。相反,让编译器为你做。

  2. 你用错了static

如果让我做,我会做如下。析构函数由编译器生成,将正确释放所有资源并将m_instance重置为空值。

class MyWindow : public QDialog
{
   Q_OBJECT
   static QPointer<MyWindow> m_instance;
   QVBoxLayout m_layout{this};
   QPushButton m_button{"Scan"};
public:
   MyWindow(QWidget * parent = nullptr) : QDialog(parent) {
      Q_ASSERT(! m_instance);
      m_instance = this;
      m_layout.addWidget(&m_button);
   }
   void Scan();
   static void ScanFinished();
};

QPointer<MyWindow> MyWindow::m_instance;

void StartScan(void(*callback)());

void MyWindow::Scan()
{
   m_button.setEnabled(false);
   StartScan(ScanFinished);
}

void MyWindow::ScanFinished()
{
   m_instance->m_button.setEnabled(true);
}

此时很明显 StartScan 的 API 严重损坏,这种损坏迫使使用单例 MyWindow。在进行任何类型的回调时,您永远不会 使用单一的 C 函数指针。您 必须 接受一个带有 void* 的函数指针和一个 void* ,后者将用于携带函数需要工作的数据。这个是一个成语。如果你使用 C 风格的回调,你不能在不严重削弱你的 API.

的可用性的情况下使用这个习惯用法。

因此,这是一个完整的示例,适用于 Qt 4 和 Qt 5。您应该在您的问题中发布类似的东西 - 一个独立的测试用例。它编译并运行,您甚至可以从 github 获得完整的 Qt Creator 项目。它将在当前 Qt 支持的所有平台上编译和 运行。这不应该很难:毕竟这就是您使用 Qt 的原因。养成创建如此简洁的测试用例来证明您的问题的习惯将使您成为更好的开发人员,并使您的问题更容易回答。

// https://github.com/KubaO/Whosebugn/tree/master/questions/simple-callback-43094825
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#include <QtConcurrent>
#endif

class MyWindow: public QDialog
{
   Q_OBJECT
   QVBoxLayout m_layout{this};
   QPushButton m_button{"Scan"};
   Q_SIGNAL void ScanFinished();
public:
   MyWindow(QWidget * parent = nullptr) : QDialog(parent) {
      m_layout.addWidget(&m_button);
      connect(&m_button, SIGNAL(clicked(bool)), this, SLOT(Scan()));
      connect(this, SIGNAL(ScanFinished()), this, SLOT(OnScanFinished()));
   }
   Q_SLOT void Scan();
   static void ScanFinishedCallback(void* w);
   Q_SLOT void OnScanFinished();
};

void StartScan(void(*callback)(void*), void* data) {
   // Mockup of the scanning process: invoke the callback after a delay from
   // a worker thread.
   QtConcurrent::run([=]{
      struct Helper : QThread { using QThread::sleep; };
      Helper::sleep(2);
      callback(data);
   });
}

void MyWindow::Scan()
{
   m_button.setEnabled(false);
   StartScan(ScanFinishedCallback, static_cast<void*>(this));
}

void MyWindow::ScanFinishedCallback(void* data)
{
   emit static_cast<MyWindow*>(data)->ScanFinished();
}

void MyWindow::OnScanFinished()
{
   m_button.setEnabled(true);
}

int main(int argc, char ** argv) {
   QApplication app(argc, argv);
   MyWindow w;
   w.show();
   return app.exec();
}
#include "main.moc"

当然 StartScan 不能在调用它的线程中完成工作:它会阻塞 GUI 线程并使您的应用程序无响应。这是糟糕的用户体验的主要来源。相反,它应该生成一个并发作业,在扫描完成时通知调用者。

由于将从该并发线程调用回调,因此使用 MyWindow 的非线程安全方法是不安全的。唯一的线程安全方法是信号——因此我们可以发出一个信号,Qt 将安全地转发到 MyWindow 的线程并从正确的线程调用 OnScanFinished