我想在回调函数中启用按钮。我试过以下但我得到了:
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()
中的按钮?
答案 0 :(得分:3)
您正在尝试手动管理内存。正如您所看到的,使用悬空指针或犯下其他错误非常容易。相反,让编译器为您完成。
您错误地使用了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
。