【问题标题】:Accessing the QPushButton in the callback function在回调函数中访问 QPushButton
【发布时间】:2017-03-29 13:23:48
【问题描述】:

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

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()中的按钮?

【问题讨论】:

  • 你忘了Btn = new QPushButton;吗?
  • 您的代码甚至不应该编译...您正试图通过全局函数访问您的类的成员而不定义实例。您必须学习如何使用信号和插槽。
  • 我不使用静态时的情况相同。我还使用了以下 connect(this, SIGNAL(EnableBtn()), Btn, SLOT(setEnabled(true)));但我也一样
  • 在 Stackoverflow 上发布代码之前,您应该让您的代码进行编译。您的代码无效。还有一条建议:学习如何在 Qt 中使用信号和槽。不要在回调函数上浪费时间。
  • "我只发布了部分代码。"您非常接近发布一个可以编译和工作的实际示例。你为什么不这样做?它实际上会多几行!

标签: c++ qt callback qt4 qpushbutton


【解决方案1】:
  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/stackoverflown/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

【讨论】:

  • 如果我在 ScanFinishedCallback() 中编写以下内容,我唯一可以发出信号: MyWindow* Dlg=new MyWindow;发出 Dlg->ScanFinished();,但不幸的是我得到了:QPixmap:在 GUI 线程之外使用像素图是不安全的
  • 修复损坏的StartScan 代码以获取必要的void* 参数使用静态m_instance 指针使实例在回调中可用,如答案的第一部分。您不能在回调中创建窗口:它不会在任何地方连接,请记住连接是每个对象,而不是每个类。 我可以发出信号的唯一原因 那只是因为您拒绝理解答案并修复您的代码以让您正确发出信号。如果有什么不清楚的,请询问。您可以从这里开始工作,可编译的代码。
猜你喜欢
  • 2016-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-20
相关资源
最近更新 更多