我有一个按钮,它的一个插槽连接到它点击的()信号。
插槽是一项耗时的任务,耗时数秒。
当尝试在插槽结束前重新点击按钮时,我做不到。
当运行连接到其点击()信号的插槽时,如何使按钮响应?
发布于 2016-08-31 16:05:03
在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);
});
}
如果您需要转换已经到位的数据,一些常见的方法是:
data
为null时显示一个“繁忙”指示符。
Q_SLOT void transformData2() { MyDataPtr data = this->data;this->data.reset();QtConcurrent::run(={ data->MyDataPtr();MyDataPtr hasData(data);});}如果MyData
很容易移动,并且默认值的含义为“空”/“空”,则可以按值保留它:
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();
当然,以事件为基础的魔法酱是可以被考虑出来的。
发布于 2016-08-31 14:31:51
你有两个高级别的选择:
QtConcurrent::run()
,在GUI方面,您需要在执行操作时适当地处理所有的GUI事件。QApplication::processEvents()
,使GUI保持响应性(例如,在循环内部):如果耗时的操作是第三方代码,则这将无法工作,而且在执行长期运行的任务时,仍然需要解决如何处理GUI操作。你会发现,到目前为止,第一种选择比第二种更好,FWIW。
https://stackoverflow.com/questions/39252150
复制相似问题