使用Clion开发QT

使用clion 新建工程

使用clion 创建一个QT工程,注意其中Qt CMake前缀路径的选择:

img

新建好的工程如下:

img

这时,如果直接编译会报一个错误:

img

经过谷歌查询,这个错误是由于CMake指定了一个Debug版本的QT,但是我并没有找到所谓的Debug版本的下载方式,所以这里CMake我们需要自己写,来规避一些错误。

修改cmake

打开CMakeLists.txt,修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
cmake_minimum_required(VERSION 3.21) #cmake最低版本
project(QtWindowsHost) #工程名

set(CMAKE_CXX_STANDARD 14) #C++标准
# 开启QT用于预处理的组件
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
# 设置cmake模块的查询目录,注意这里的路径,到mingw路径即可
set(CMAKE_PREFIX_PATH C:/Qt/5.14.2/mingw73_64)
# 查找QT的模块
find_package(Qt5 COMPONENTS
Core
Gui
Widgets
REQUIRED)
# 添加源文件
add_executable(QtWindowsHost
main.cpp
)
# 添加模块
target_link_libraries(QtWindowsHost
Qt5::Core
Qt5::Gui
Qt5::Widgets
)

以上模板仅供参考。

img

这时候我们再去编译,就可以看到编译成功了,生成了一个默认的窗口:

img

项目设置

这里我们一切从简,只要一个简单的接收发送界面即可,也就是之前文章中所提到的Demo。

重构目录

在这里,为了目录结构的清晰,我们简单重构一下我们的项目目录。重构后如下:

img

  • 我们新建了一个Sources文件夹用于保存所有的QT源文件,在Sources下,Forms和Headers分别存放UI文件和.h头文件,所有的cpp源文件直接放在Sources目录下,这里在新建好文件夹之后,只需要把main.cpp直接拖拽到Sources下即可,clion 会自动帮我们处理cmake文件中路径的问题。
  • lib目录存放我们之后需要的MQTT第三方库的 .dll文件和.a文件
  • include目录存放第三方库的头文件

新建界面UI类

对着左侧项目根目录右键,选择新建,新建一个QT Ui类:

img

然后给新建的UI类起一个名字,这里就叫做MainWindow,基类我们选择Qwidget,此时我们可以看到,clion 会自动帮我们把新生成的文件添加到cmake中:

img

最后我们自己把对应的文件拖拽到对应的目录中即可:

img

在cmake中添加头文件目录

这时候我们修改了头文件的目录,所以CMake是无法自动寻找到Headers目录的,需要我们手动指定,在cmake中添加如下内容:

1
2
3
4
include_directories(
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/Sources/Headers
)

img

修改mainwindow.cpp

此时我们直接编译会报错,所以需要自己修改一下mainwindow.cpp,将最上方头文件的引入的地址进行修改:

img

这里需要说明的是,QT会把UI文件预处理成对应的.h文件,然后在CPP文件中引用,生成的.h文件与UI文件同目录,所以我们想要引用的话需要正确设置文件的位置,正如上面所说一样,UI文件都在Forms目录下,所以我们引入的时候也需要指定Forms目录

这时候编译,就可以看到原来报错找不到定义或头文件的地方,都不会再报错了,因为编译过后,ui_MainWindow.h文件就会生成。

img

修改main.cpp

然后我们将main.cpp修改为以下内容:

1
2
3
4
5
6
7
8
9
10
#include <QApplication>
#include "mainwindow.h"

int main (int argc, char *argv[])
{
QApplication a (argc, argv);
MainWindow w;
w.show ();
return QApplication::exec ();
}

编译运行,就能看到我们的结果啦:

img

在clion 中添加外部工具

在设置中的外部工具中,添加一个新的外部工具,指向designer.exe:

img

然后我们就能对着UI文件右键,选择外部工具,最后使用QtDesigner打开它:

img

添加MQTT库

我们的demo是基于MQTT通信的,所以我们要添加一个第三方的动态库,也就是第三方的MQTT库。

有关MQTT库的编译,可以看我之前的一篇文章(QT 安装与编译 MQTT),里面详细介绍了如何自己编译MQTT的库并在Qt Creator下部署。

添加文件

首先确保我们的工程目录结构如下:

然后在lib目录中添加MQTT库:

在include目录中添加头文件:

修改cmake

打开cmake文件,我们需要进行如下修改:

首先在 find_package中添加Network模块:

然后添加第三方库文件目录:

最后在target_link_libraries链接Network模块和MQTT库:

完整的cmake文件如下:

设计界面和逻辑

界面设计和逻辑就不多说了,直接上代码,UI文件可以使用TXT、Vscode等工具打开,然后就能复制粘贴了。

mainwindow.ui

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QWidget" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>452</width>
<height>384</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QRadioButton" name="rb_status">
<property name="text">
<string>连接状态</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pb_connect">
<property name="text">
<string>Connect</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Topic:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="le_pb_topic"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Payload:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="le_pu_payload"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Publish</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Topic:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="le_sub_topic"/>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Subscribe</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_3">
<property name="text">
<string>UnSubscribe</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>LOG:</string>
</property>
</widget>
</item>
<item>
<widget class="QTextBrowser" name="te_log"/>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

mainwindow.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//
// Created by XinMouRen on 2022/1/25.
//

#ifndef _MAINWINDOW_H_
#define _MAINWINDOW_H_

#include <QWidget>
#include "qmqtt.h"
QT_BEGIN_NAMESPACE
namespace Ui
{
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QWidget {
Q_OBJECT

public:
explicit MainWindow (QWidget *parent = nullptr);
~MainWindow () override;
QMQTT::Client *client;
void on_pb_connect_clicked();
void on_pushButton_clicked();
void on_pushButton_2_clicked();
void on_pushButton_3_clicked();
void doConnected(); //MQTT 连接成功
void doDisconnected();//MQTT连接断开
void doDataReceived(QMQTT::Message);//MQTT收到数据
private:
Ui::MainWindow *ui;
};

#endif //_MAINWINDOW_H

mainwindow.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//
// Created by XinMouRen on 2022/1/25.
//

// You may need to build the project (run Qt uic code generator) to get "ui_MainWindow.h" resolved

#include "Headers/mainwindow.h"
// #include "ui_MainWindow.h" 这是原来的
#include "Forms/ui_MainWindow.h"

MainWindow::MainWindow (QWidget *parent) :
QWidget (parent), ui (new Ui::MainWindow)
{
ui->setupUi (this);
client = new QMQTT::Client ();
client->setHostName ("127.0.0.1");
client->setPort (1883);
client->setClientId ("clientid");
client->setUsername ("user");
client->setPassword ("password");
ui->pushButton->setEnabled (false);
ui->pushButton_2->setEnabled (false);
connect (this->client, &QMQTT::Client::connected, this, &MainWindow::doConnected);
connect (this->client, &QMQTT::Client::disconnected, this, &MainWindow::doDisconnected);
connect (this->client, &QMQTT::Client::received, this, &MainWindow::doDataReceived);
}
void MainWindow::on_pb_connect_clicked ()
{

if (!client->isConnectedToHost ())
{
client->connectToHost ();
}
else
{
client->disconnectFromHost ();
}

}

void MainWindow::doConnected ()
{
ui->rb_status->setChecked (true);
ui->pb_connect->setText ("Disconnect");
ui->pushButton->setEnabled (true);
ui->pushButton_2->setEnabled (true);
}

void MainWindow::doDisconnected ()
{
ui->rb_status->setChecked (false);
ui->pb_connect->setText ("Connect");
ui->pushButton->setEnabled (false);
ui->pushButton_2->setEnabled (false);
}

void MainWindow::doDataReceived (const QMQTT::Message &message)
{
QString mes =
QString (message.id ()) + " " + QString (message.qos ()) + " " + message.topic () + " " + message.payload ()
+ "\n";
ui->te_log->append (mes);
}

void MainWindow::on_pushButton_clicked ()
{
QString topic = ui->le_pb_topic->text ().trimmed ();
QString payload = ui->le_pu_payload->text ().trimmed ();
if (topic.isEmpty () || payload.isEmpty ())
{
qDebug () << "pub topic and payload is empty!";
return;
}
QMQTT::Message message (136, topic, payload.toUtf8 ());

client->publish (message);
}

void MainWindow::on_pushButton_2_clicked ()
{
QString topic = ui->le_sub_topic->text ().trimmed ();
if (topic.isEmpty ())
{
qDebug () << "sub topic and payload is empty!";
return;
}
qDebug () << topic;
client->subscribe (topic);
}

void MainWindow::on_pushButton_3_clicked ()
{
QString topic = ui->le_sub_topic->text ().trimmed ();
if (topic.isEmpty ())
{
qDebug () << "sub topic and payload is empty!";
return;
}
client->unsubscribe (topic);

}

MainWindow::~MainWindow ()
{
delete ui;
}

运行

将上述代码编译运行后,可以看到如下界面:

经测试,我们的MQTT通信也是正常的。

至此,我们从clion 下创建工程并添加了第三方库的过程已经结束,项目代码已经上传到了GitHub,如果大家有需要可以直接下载下来,如果你的QT版本是5.14.2的话,应该是可以直接运行的。

代码仓库在我的博客里可以找到。

最后,MQTT客户端的代码基本上是来自某个大佬的仓库,但是时间久远忘记是哪个了,这个大佬用的是QT4,MQTT那里是在初始化的时候就填入地址,这会导致所填的地址只能是IP的形式,不能以域名的形式进行MQTT通信,有关这方面的内容可以看我的上一篇文章。