Qt如何使一个按钮响应时,插槽连接到它的click()信号正在运行
Qt how to make a button responsive when the slot connected to its clicked() signal is running?
我有一个连接到它的click()信号的插槽按钮。
槽是一个耗时的任务,需要花费很多秒。
当尝试在插槽结束之前重新点击按钮时,我不能。
如何使按钮响应时运行插槽连接到其click()信号?
在GUI中运行的任何东西都不能阻塞,也不能花费太多时间来完成。不要在GUI线程中执行长时间运行的操作。这将解决你的问题。
现在你可能会问:那么我怎么做长时间运行的事情呢?您可以在其他线程中执行它们,但这并不意味着必须看到QThread
的实例。
你必须隔离数据和对数据的操作。假设我们有如下的类:
class MyData {
public:
MyData() {}
/// Loads and pre-processes the data
bool load(const QString & file) { QThread::sleep(5); return true; }
/// Transforms the data
void transform() { QThread::sleep(2); }
};
using MyDataPtr = std::shared_ptr<MyData>;
Q_DECLARE_METATYPE(MyDataPtr)
int main(...) { qRegisterMetaType<MyDataPtr>(); ... }
为了使它更普遍,我们假设复制和移动MyData
是昂贵的。
如果操作是"生成新数据"的类型,比如读取文件,处理数据和构建数据结构,您可以在工作线程中实例化数据并将其传递给GUI线程:
class GuiClass : public QWidget {
Q_OBJECT
MyDataPtr data;
Q_SIGNAL void hasData(const MyDataPtr &);
public:
GuiClass() {
connect(this, &GuiClass::hasData,
this, [this](const MyDataPtr & newData){ data = newData; });
}
Q_SLOT void makeData(const QString & file) {
QtConcurrent::run([=]{
auto data = std::make_shared<MyData>();
if (data->load(file))
emit hasData(data);
});
}
如果您需要转换已经存在的数据,一些常见的方法是:
复制数据,在工作线程中转换,然后在转换后交换。
Q_SLOT void transformData1() { MyDataPtr copy = std::make_shared<MyData>(*data); QtConcurrent::run([=]{ copy->transform(); emit hasData(copy); }); }
暂时删除数据,转换数据,然后再放回去。UI将不得不处理丢失的数据,例如,当
data
为null时显示"忙"指示灯。Q_SLOT void transformData2() { MyDataPtr data = this->data; this->data.reset(); QtConcurrent::run([=]{ data->transform(); emit hasData(data); }); }
如果MyData
很容易移动,并且默认值具有"null"/"empty"的含义,则可以通过值来保留它:
class GuiClassVal : public QWidget {
Q_OBJECT
MyData data;
struct DataEvent : public QEvent {
static const QEvent::Type type;
MyData data;
DataEvent(const MyData & data) : QEvent{type}, data{data} {}
DataEvent(MyData && data) : QEvent{type}, data{std::move(data)} {}
};
/// This method is thread-safe.
void setData(MyData && data) {
QCoreApplication::postEvent(this, new DataEvent{std::move(data)});
}
/// This method is thread-safe.
void setData(const MyData & data) {
QCoreApplication::postEvent(this, new DataEvent{data});
}
bool event(QEvent *event) override {
if (event->type() == DataEvent::type) {
data.~MyData();
new (&data) MyData{std::move(static_cast<DataEvent*>(event)->data)};
return true;
}
return QWidget::event(event);
}
void transformInEvent(DataEvent * ev){
ev->data.transform();
QCoreApplication::postEvent(this, ev);
};
public:
Q_SLOT void makeData(const QString & file) {
QtConcurrent::run([=]{
MyData data;
if (data.load(file)) setData(std::move(data));
});
}
Q_SLOT void transformData1() {
QtConcurrent::run(this, &GuiClassVal::transformInEvent, new DataEvent{data});
}
Q_SLOT void transformData2() {
QtConcurrent::run(this, &GuiClassVal::transformInEvent, new DataEvent{std::move(data)});
}
};
const QEvent::Type GuiClassVal::DataEvent::type = (QEvent::Type)QEvent::registerEventType();
基于事件的魔力酱当然可以被分解。
您有两个高级选择:
- 将耗时的选择生成到一个线程中:最简单的方法是使用
QtConcurrent::run()
,在GUI端,您需要担心在操作执行时适当地处理所有GUI事件。 - 在你耗时的代码中,经常调用
QApplication::processEvents()
,使GUI保持响应(例如,在循环内部):如果耗时的操作是你不能跳出的第三方代码,这将不起作用,并且你仍然需要弄清楚如何处理GUI操作,而你的长时间运行的任务正在执行。
你会发现,到目前为止,第一个选择比第二个选择要好得多。
相关文章:
- Qt VTK交互风格的信号到小部件
- 获取日期异步信号安全吗?如果在信号处理程序中使用,它会导致死锁吗
- 如何将点击的信号和插槽添加到qt中的自定义按钮中
- 如何在没有信号的情况下从C++执行QML插槽
- 线程之间的布尔停止信号
- 如何在信号处理程序和普通函数中对全局变量进行互斥读写操作
- 当用户在qtablewidget中输入单元格时,如何获得信号?C++
- 有可能在信号处理程序中设置promise吗
- 代码在我的计算机上运行良好,但是在将其提交给coursera时遇到未知的信号11问题
- 在条件变量中触发错误信号的频率是多少
- 在信号处理程序中捕获C++未处理的异常并恢复应用程序
- 调试和自由执行中的信号处理
- 升压信号2将插槽传递到成员功能以断开连接
- C++函数包装器来捕获某些信号
- 如何在qt中将信号和插槽与另一个对象连接 --解决了
- 中止信号来自 C++ 中的中止(3) (SIGABRT)
- Qt将信号与另一个类方法连接
- C++ 信号和插槽不工作:插槽不响应事件
- Coursera :自动评分器的未知信号 11
- Qt如何使一个按钮响应时,插槽连接到它的click()信号正在运行