Khaled
Khaled

Reputation: 59

Accessing the QPushButton in the callback function

I want to enable a pushbutton in a callback function. I have tried to do the following but I have got:

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

How to access the button in the callback function Scanfinished() ?

Upvotes: 0

Views: 843

Answers (1)

  1. You're attempting to manually manage memory. As you can see, it's very easy to use a dangling pointer or commit other blunders. Instead, let the compiler do it for you.

  2. You use static incorrectly.

If I were to do it, I'd do as follows. The destructor is generated by the compiler and will correctly release all resources and reset m_instance to a null value.

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

At this point it's rather obvious that the API of StartScan is horribly broken, and this brokenness forces the use of a singleton MyWindow. When doing any kind of callbacks, you never use a sole C function pointer. You must accept both a function pointer that takes a void* and a void* that will be used to carry the data the function needs to work. This is an idiom. If you use C-style callbacks, you cannot not use the idiom without severely crippling the usability of your API.

Thus, this is a complete example and works in both Qt 4 and Qt 5. You should have posted something like it - a self-contained test case - in your question. It compiles and it works and you can even get the complete Qt Creator project from github. It will compile and run on all platforms supported by current Qt. It's not supposed to be hard: that's why you're using Qt, after all. Getting in the habit of creating such concise test cases to demonstrate your issues will make you a better developer, and make your questions much easier to answer.

// 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"

Of course StartScan cannot do the work in the thread it was called from: it'd block the GUI thread and make your application unresponsive. That's the prime source of bad user experience. Instead, it should spawn a concurrent job that will notify the caller when the scanning is done.

Since the callback will be called from that concurrent thread, it's not safe to use MyWindow's non-thread-safe methods. The only thread-safe methods are signals - thus we can emit a signal that Qt will the safely forward to MyWindow's thread and invoke OnScanFinished from the right thread.

Upvotes: 3

Related Questions