@@ -1005,3 +1005,102 @@ void task(){
1005
1005

1006
1006
1007
1007
在启动进度条后,能够正常点击“**测试**”按钮然后触发弹窗,就是没有问题,你也可以尝试不创建线程,那么界面将会卡主,你点击不了“**测试**”按钮,也移动不了这个窗口。
1008
+
1009
+ 我们的项目是使用 visual studio 编写的,你可以直接安装 Qt 插件后使用它打开。此项目很小,为了简洁,所有的的界面与设置均是代码控制,而没有进行别的 ui 操作,也就是说,你只需要注意 `async_progress_bar.h`、`async_progress_bar.cpp`、`main.cpp` 这三个文件即可。它们存放在仓库以及目录 **`code`** 文件夹中。
1010
+
1011
+ ```cpp
1012
+ class async_progress_bar : public QMainWindow {
1013
+ Q_OBJECT
1014
+
1015
+ public:
1016
+ async_progress_bar(QWidget * parent = nullptr);
1017
+ ~ async_progress_bar();
1018
+
1019
+ void task(){
1020
+ future = std::async(std::launch::async, [=] {
1021
+ QMetaObject::invokeMethod (this, [ this] {
1022
+ // 这里显示的线程 ID 就是主线程,代表这些任务就是在主线程,即 UI 线程执行
1023
+ QMessageBox::information(nullptr, "线程ID", std::to_string(_ Thrd_id()).c_str());
1024
+ button->setEnabled(false);
1025
+ progress_bar->setRange(0, 1000);
1026
+ button->setText("正在执行...");
1027
+ });
1028
+ for (int i = 0; i <= 1000; ++i) {
1029
+ std::this_thread::sleep_for(10ms);
1030
+ QMetaObject::invokeMethod(this, [ this, i] {
1031
+ progress_bar->setValue(i);
1032
+ });
1033
+ }
1034
+ QMetaObject::invokeMethod(this, [ this] {
1035
+ button->setText("start");
1036
+ button->setEnabled(true);
1037
+ });
1038
+ // 不在 invokeMethod 中获取线程 ID,这里显示的是子线程的ID
1039
+ auto s = std::to_string(_ Thrd_id());
1040
+ QMetaObject::invokeMethod(this, [ =] {
1041
+ QMessageBox::information(nullptr, "线程ID", s.c_str());
1042
+ });
1043
+ });
1044
+ }
1045
+ private:
1046
+ QString progress_bar_style =
1047
+ "QProgressBar {"
1048
+ " border: 2px solid grey;"
1049
+ " border-radius: 5px;"
1050
+ " background-color: lightgrey;"
1051
+ " text-align: center;" // 文本居中
1052
+ " color: #000000 ;" // 文本颜色
1053
+ "}"
1054
+ "QProgressBar::chunk {"
1055
+ " background-color: #7FFF00;"
1056
+ " width: 10px;" // 设置每个进度块的宽度
1057
+ " font: bold 14px;" // 设置进度条文本字体
1058
+ "}";
1059
+ QString button_style =
1060
+ "QPushButton {"
1061
+ " text-align: center;" // 文本居中
1062
+ "}";
1063
+ QProgressBar* progress_bar{};
1064
+ QPushButton* button{};
1065
+ QPushButton* button2{};
1066
+ Ui::async_progress_barClass ui{};
1067
+ std::future<void >future;
1068
+ };
1069
+ // 创建控件 设置布局、样式 连接信号
1070
+ async_progress_bar::async_progress_bar(QWidget * parent)
1071
+ : QMainWindow{ parent }, progress_bar{ new QProgressBar(this) },
1072
+ button{ new QPushButton("start",this) },button2{ new QPushButton("测试",this) } {
1073
+ ui.setupUi(this);
1074
+
1075
+ progress_bar->setStyleSheet(progress_bar_style);
1076
+ progress_bar->setRange(0, 1000);
1077
+
1078
+ button->setMinimumSize(100, 50);
1079
+ button->setMaximumWidth(100);
1080
+ button->setStyleSheet(button_style);
1081
+ button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
1082
+
1083
+ button2->setMinimumSize(100, 50);
1084
+ button2->setMaximumWidth(100);
1085
+ button2->setStyleSheet(button_style);
1086
+ button2->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
1087
+
1088
+ QVBoxLayout* layout = new QVBoxLayout;
1089
+ layout->addWidget(progress_bar);
1090
+ layout->addWidget(button, 0, Qt::AlignHCenter);
1091
+ layout->addWidget(button2, 0, Qt::AlignHCenter);
1092
+ // 设置窗口布局为垂直布局管理器
1093
+ centralWidget()->setLayout(layout);
1094
+
1095
+ connect(button, &QPushButton::clicked, this, &async_progress_bar::task);
1096
+ connect(button2, &QPushButton::clicked, []{
1097
+ QMessageBox::information(nullptr, "测试", "没有卡界面!");
1098
+ });
1099
+ }
1100
+ ```
1101
+
1102
+ 为了展示 `QMetaObject::invokeMethod` 中的 lambda 是在主线程运行,我们打印了线程 ID,因为 C++11 的 `std::this_thread::get_id()` 返回的那个内部类型没办法直接转换为 `unsigned int`,我们就直接使用了 win32 的 API *`_Thrd_id()`* 了。如果您是 Linux 之类的环境,使用 [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/) 接口 [*`pthread_self()`*](https://pubs.opengroup.org/onlinepubs/009696699/functions/pthread_self.html)。
1103
+
1104
+ 这个例子其实很好的展示了多线程异步的作用,因为有 UI,所以很直观,毕竟如果你不用线程,那么不就卡界面了,用了就没事。
1105
+
1106
+ 建议打开此项目自己编译运行,并尝试修改,如有必要,建议自己也写一遍,代码较为简单,就不再介绍了。
0 commit comments