├── PidAssistant.pro
├── PidAssistant.pro.user
├── PidAssistant.user
├── aboutdialog.cpp
├── aboutdialog.h
├── aboutdialog.ui
├── chart.cpp
├── chart.h
├── common.cpp
├── common.h
├── image.qrc
├── image
├── MyMessageBox
│ ├── MyMessageBox.css
│ ├── WindowIcon.png
│ ├── information.png
│ ├── question.png
│ └── warnning.png
├── MyTitle
│ ├── MyTitle.css
│ ├── close.png
│ ├── close11.png
│ ├── max.png
│ ├── min.png
│ └── restore.png
├── down_arrow_16.png
└── logo.png
├── main.cpp
├── mainwindow.cpp
├── mainwindow.h
├── mainwindow.ui
├── mymessagebox.cpp
├── mymessagebox.h
├── mymessagebox.ui
├── mytitlebar.cpp
├── mytitlebar.h
├── qcustomplot.cpp
├── qcustomplot.h
├── serial.cpp
├── serial.h
└── zhiLanIcon.ico
/PidAssistant.pro:
--------------------------------------------------------------------------------
1 | #-------------------------------------------------
2 | #
3 | # Project created by QtCreator 2019-08-20T14:16:45
4 | #
5 | #-------------------------------------------------
6 |
7 | QT += core gui serialport printsupport
8 |
9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
10 |
11 | TARGET = PidAssistant
12 | TEMPLATE = app
13 |
14 | # The following define makes your compiler emit warnings if you use
15 | # any feature of Qt which has been marked as deprecated (the exact warnings
16 | # depend on your compiler). Please consult the documentation of the
17 | # deprecated API in order to know how to port your code away from it.
18 | DEFINES += QT_DEPRECATED_WARNINGS
19 |
20 | # You can also make your code fail to compile if you use deprecated APIs.
21 | # In order to do so, uncomment the following line.
22 | # You can also select to disable deprecated APIs only up to a certain version of Qt.
23 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
24 |
25 | CONFIG += c++11
26 |
27 | SOURCES += \
28 | main.cpp \
29 | mainwindow.cpp \
30 | serial.cpp \
31 | common.cpp \
32 | qcustomplot.cpp \
33 | aboutdialog.cpp \
34 | mytitlebar.cpp \
35 | mymessagebox.cpp \
36 | chart.cpp
37 |
38 | HEADERS += \
39 | mainwindow.h \
40 | serial.h \
41 | common.h \
42 | qcustomplot.h \
43 | aboutdialog.h \
44 | mytitlebar.h \
45 | mymessagebox.h \
46 | chart.h
47 |
48 | FORMS += \
49 | mainwindow.ui \
50 | aboutdialog.ui \
51 | mymessagebox.ui
52 |
53 | RC_ICONS = zhiLanIcon.ico
54 |
55 | # Default rules for deployment.
56 | qnx: target.path = /tmp/$${TARGET}/bin
57 | else: unix:!android: target.path = /opt/$${TARGET}/bin
58 | !isEmpty(target.path): INSTALLS += target
59 |
60 | RESOURCES += \
61 | image.qrc
62 |
--------------------------------------------------------------------------------
/PidAssistant.pro.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | EnvironmentId
7 | {b08eecf7-d0b0-4170-84dc-c7c92c55c347}
8 |
9 |
10 | ProjectExplorer.Project.ActiveTarget
11 | 0
12 |
13 |
14 | ProjectExplorer.Project.EditorSettings
15 |
16 | true
17 | false
18 | true
19 |
20 | Cpp
21 |
22 | CppGlobal
23 |
24 |
25 |
26 | QmlJS
27 |
28 | QmlJSGlobal
29 |
30 |
31 | 2
32 | UTF-8
33 | false
34 | 4
35 | false
36 | 80
37 | true
38 | true
39 | 1
40 | true
41 | false
42 | 0
43 | true
44 | true
45 | 0
46 | 8
47 | true
48 | 1
49 | true
50 | true
51 | true
52 | false
53 |
54 |
55 |
56 | ProjectExplorer.Project.PluginSettings
57 |
58 |
59 | -fno-delayed-template-parsing
60 |
61 | true
62 |
63 |
64 |
65 | ProjectExplorer.Project.Target.0
66 |
67 | Desktop Qt 5.9.7 MinGW 32bit
68 | Desktop Qt 5.9.7 MinGW 32bit
69 | qt.qt5.597.win32_mingw53_kit
70 | 0
71 | 0
72 | 0
73 |
74 | F:/Project/C++/Qt/build-PidAssistant-Desktop_Qt_5_9_7_MinGW_32bit-Debug
75 |
76 |
77 | true
78 | qmake
79 |
80 | QtProjectManager.QMakeBuildStep
81 | true
82 |
83 | false
84 | false
85 | false
86 |
87 |
88 | true
89 | Make
90 |
91 | Qt4ProjectManager.MakeStep
92 |
93 | false
94 |
95 |
96 |
97 | 2
98 | Build
99 |
100 | ProjectExplorer.BuildSteps.Build
101 |
102 |
103 |
104 | true
105 | Make
106 |
107 | Qt4ProjectManager.MakeStep
108 |
109 | true
110 | clean
111 |
112 |
113 | 1
114 | Clean
115 |
116 | ProjectExplorer.BuildSteps.Clean
117 |
118 | 2
119 | false
120 |
121 | Debug
122 | Debug
123 | Qt4ProjectManager.Qt4BuildConfiguration
124 | 2
125 | true
126 |
127 |
128 | F:/Project/C++/Qt/build-PidAssistant-Desktop_Qt_5_9_7_MinGW_32bit-Release
129 |
130 |
131 | true
132 | qmake
133 |
134 | QtProjectManager.QMakeBuildStep
135 | false
136 |
137 | false
138 | false
139 | false
140 |
141 |
142 | true
143 | Make
144 |
145 | Qt4ProjectManager.MakeStep
146 |
147 | false
148 |
149 |
150 |
151 | 2
152 | Build
153 |
154 | ProjectExplorer.BuildSteps.Build
155 |
156 |
157 |
158 | true
159 | Make
160 |
161 | Qt4ProjectManager.MakeStep
162 |
163 | true
164 | clean
165 |
166 |
167 | 1
168 | Clean
169 |
170 | ProjectExplorer.BuildSteps.Clean
171 |
172 | 2
173 | false
174 |
175 | Release
176 | Release
177 | Qt4ProjectManager.Qt4BuildConfiguration
178 | 0
179 | true
180 |
181 |
182 | F:/Project/C++/Qt/build-PidAssistant-Desktop_Qt_5_9_7_MinGW_32bit-Profile
183 |
184 |
185 | true
186 | qmake
187 |
188 | QtProjectManager.QMakeBuildStep
189 | true
190 |
191 | false
192 | true
193 | false
194 |
195 |
196 | true
197 | Make
198 |
199 | Qt4ProjectManager.MakeStep
200 |
201 | false
202 |
203 |
204 |
205 | 2
206 | Build
207 |
208 | ProjectExplorer.BuildSteps.Build
209 |
210 |
211 |
212 | true
213 | Make
214 |
215 | Qt4ProjectManager.MakeStep
216 |
217 | true
218 | clean
219 |
220 |
221 | 1
222 | Clean
223 |
224 | ProjectExplorer.BuildSteps.Clean
225 |
226 | 2
227 | false
228 |
229 | Profile
230 | Profile
231 | Qt4ProjectManager.Qt4BuildConfiguration
232 | 0
233 | true
234 |
235 | 3
236 |
237 |
238 | 0
239 | 部署
240 |
241 | ProjectExplorer.BuildSteps.Deploy
242 |
243 | 1
244 | Deploy Configuration
245 |
246 | ProjectExplorer.DefaultDeployConfiguration
247 |
248 | 1
249 |
250 |
251 | false
252 | false
253 | 1000
254 |
255 | true
256 |
257 | false
258 | false
259 | false
260 | false
261 | true
262 | 0.01
263 | 10
264 | true
265 | 1
266 | 25
267 |
268 | 1
269 | true
270 | false
271 | true
272 | valgrind
273 |
274 | 0
275 | 1
276 | 2
277 | 3
278 | 4
279 | 5
280 | 6
281 | 7
282 | 8
283 | 9
284 | 10
285 | 11
286 | 12
287 | 13
288 | 14
289 |
290 | 2
291 |
292 |
293 |
294 |
295 |
296 | Custom Executable
297 |
298 | ProjectExplorer.CustomExecutableRunConfiguration
299 | 3768
300 | false
301 | true
302 | false
303 | false
304 | true
305 |
306 | 1
307 |
308 |
309 |
310 | ProjectExplorer.Project.TargetCount
311 | 1
312 |
313 |
314 | ProjectExplorer.Project.Updater.FileVersion
315 | 18
316 |
317 |
318 | Version
319 | 18
320 |
321 |
322 |
--------------------------------------------------------------------------------
/PidAssistant.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | EnvironmentId
7 | {b08eecf7-d0b0-4170-84dc-c7c92c55c347}
8 |
9 |
10 | ProjectExplorer.Project.ActiveTarget
11 | 0
12 |
13 |
14 | ProjectExplorer.Project.EditorSettings
15 |
16 | true
17 | false
18 | true
19 |
20 | Cpp
21 |
22 | CppGlobal
23 |
24 |
25 |
26 | QmlJS
27 |
28 | QmlJSGlobal
29 |
30 |
31 | 2
32 | UTF-8
33 | false
34 | 4
35 | false
36 | 80
37 | true
38 | true
39 | 1
40 | true
41 | false
42 | 0
43 | true
44 | true
45 | 0
46 | 8
47 | true
48 | 1
49 | true
50 | true
51 | true
52 | false
53 |
54 |
55 |
56 | ProjectExplorer.Project.PluginSettings
57 |
58 |
59 | -fno-delayed-template-parsing
60 |
61 | true
62 |
63 |
64 |
65 | ProjectExplorer.Project.Target.0
66 |
67 | Desktop Qt 5.9.7 MinGW 32bit
68 | Desktop Qt 5.9.7 MinGW 32bit
69 | qt.qt5.597.win32_mingw53_kit
70 | 1
71 | 0
72 | 0
73 |
74 | F:/Project/C++/Qt/build-PidDebugTool-Desktop_Qt_5_9_7_MinGW_32bit-Debug
75 |
76 |
77 | true
78 | qmake
79 |
80 | QtProjectManager.QMakeBuildStep
81 | true
82 |
83 | false
84 | false
85 | false
86 |
87 |
88 | true
89 | Make
90 |
91 | Qt4ProjectManager.MakeStep
92 |
93 | false
94 |
95 |
96 |
97 | 2
98 | Build
99 |
100 | ProjectExplorer.BuildSteps.Build
101 |
102 |
103 |
104 | true
105 | Make
106 |
107 | Qt4ProjectManager.MakeStep
108 |
109 | true
110 | clean
111 |
112 |
113 | 1
114 | Clean
115 |
116 | ProjectExplorer.BuildSteps.Clean
117 |
118 | 2
119 | false
120 |
121 | Debug
122 | Debug
123 | Qt4ProjectManager.Qt4BuildConfiguration
124 | 2
125 | true
126 |
127 |
128 | F:/Project/C++/Qt/build-PidDebugTool-Desktop_Qt_5_9_7_MinGW_32bit-Release
129 |
130 |
131 | true
132 | qmake
133 |
134 | QtProjectManager.QMakeBuildStep
135 | false
136 |
137 | false
138 | false
139 | false
140 |
141 |
142 | true
143 | Make
144 |
145 | Qt4ProjectManager.MakeStep
146 |
147 | false
148 |
149 |
150 |
151 | 2
152 | Build
153 |
154 | ProjectExplorer.BuildSteps.Build
155 |
156 |
157 |
158 | true
159 | Make
160 |
161 | Qt4ProjectManager.MakeStep
162 |
163 | true
164 | clean
165 |
166 |
167 | 1
168 | Clean
169 |
170 | ProjectExplorer.BuildSteps.Clean
171 |
172 | 2
173 | false
174 |
175 | Release
176 | Release
177 | Qt4ProjectManager.Qt4BuildConfiguration
178 | 0
179 | true
180 |
181 |
182 | F:/Project/C++/Qt/build-PidDebugTool-Desktop_Qt_5_9_7_MinGW_32bit-Profile
183 |
184 |
185 | true
186 | qmake
187 |
188 | QtProjectManager.QMakeBuildStep
189 | true
190 |
191 | false
192 | true
193 | false
194 |
195 |
196 | true
197 | Make
198 |
199 | Qt4ProjectManager.MakeStep
200 |
201 | false
202 |
203 |
204 |
205 | 2
206 | Build
207 |
208 | ProjectExplorer.BuildSteps.Build
209 |
210 |
211 |
212 | true
213 | Make
214 |
215 | Qt4ProjectManager.MakeStep
216 |
217 | true
218 | clean
219 |
220 |
221 | 1
222 | Clean
223 |
224 | ProjectExplorer.BuildSteps.Clean
225 |
226 | 2
227 | false
228 |
229 | Profile
230 | Profile
231 | Qt4ProjectManager.Qt4BuildConfiguration
232 | 0
233 | true
234 |
235 | 3
236 |
237 |
238 | 0
239 | 部署
240 |
241 | ProjectExplorer.BuildSteps.Deploy
242 |
243 | 1
244 | Deploy Configuration
245 |
246 | ProjectExplorer.DefaultDeployConfiguration
247 |
248 | 1
249 |
250 |
251 | false
252 | false
253 | 1000
254 |
255 | true
256 |
257 | false
258 | false
259 | false
260 | false
261 | true
262 | 0.01
263 | 10
264 | true
265 | 1
266 | 25
267 |
268 | 1
269 | true
270 | false
271 | true
272 | valgrind
273 |
274 | 0
275 | 1
276 | 2
277 | 3
278 | 4
279 | 5
280 | 6
281 | 7
282 | 8
283 | 9
284 | 10
285 | 11
286 | 12
287 | 13
288 | 14
289 |
290 | 2
291 |
292 | PidDebugTool
293 |
294 | Qt4ProjectManager.Qt4RunConfiguration:F:/Project/C++/Qt/PidDebugTool/PidDebugTool.pro
295 | true
296 |
297 | PidDebugTool.pro
298 |
299 | F:/Project/C++/Qt/build-PidDebugTool-Desktop_Qt_5_9_7_MinGW_32bit-Release
300 | 3768
301 | false
302 | true
303 | false
304 | false
305 | true
306 |
307 | 1
308 |
309 |
310 |
311 | ProjectExplorer.Project.TargetCount
312 | 1
313 |
314 |
315 | ProjectExplorer.Project.Updater.FileVersion
316 | 18
317 |
318 |
319 | Version
320 | 18
321 |
322 |
323 |
--------------------------------------------------------------------------------
/aboutdialog.cpp:
--------------------------------------------------------------------------------
1 | #include "aboutdialog.h"
2 | #include "ui_aboutdialog.h"
3 |
4 | AboutDialog::AboutDialog(QWidget *parent) :
5 | QDialog(parent),
6 | ui(new Ui::AboutDialog)
7 | {
8 | ui->setupUi(this);
9 |
10 | // 让窗口居中显示 | 隐藏标题栏
11 | this->setWindowFlags(Qt::Window |Qt::FramelessWindowHint);
12 | // 设置为模态对话框
13 | this->setModal(true);
14 | // label安装事件过滤器,实现鼠标点击事件
15 | ui->webLabel->installEventFilter(this);
16 |
17 | // 初始化标题栏
18 | m_titleBar = new MyTitleBar(this);
19 | // 设置窗口边框宽度
20 | m_titleBar->setWindowBorderWidth(0);
21 | // 设置标题栏颜色
22 | m_titleBar->setBackgroundColor(191, 191, 191);
23 | // 设置标题栏上按钮类型
24 | m_titleBar->setButtonType(ONLY_CLOSE_BUTTON);
25 | // 设置标题栏长度
26 | m_titleBar->setTitleWidth(this->width());
27 | // // 设置标题颜色
28 | // m_titleBar->setTitleColor(0, 0, 0);
29 | // 设置标题栏图标
30 | m_titleBar->setTitleIcon("://image/MyMessageBox/WindowIcon.png", QSize(16, 16));
31 | // 设置标题内容
32 | m_titleBar->setTitleContent("关于", 12);
33 | }
34 |
35 | AboutDialog::~AboutDialog()
36 | {
37 | delete ui;
38 | }
39 |
40 | // 事件过滤器:只实现点击label打开网页的功能
41 | bool AboutDialog::eventFilter(QObject *obj, QEvent *event)
42 | {
43 | if(obj==ui->webLabel && event->type()==QEvent::MouseButtonPress)
44 | {
45 | QDesktopServices::openUrl(QUrl(QLatin1String("http://sealandrobo.com/")));
46 | }
47 |
48 | return QWidget::eventFilter(obj,event);
49 | }
50 |
--------------------------------------------------------------------------------
/aboutdialog.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019,浙江智澜科技有限公司
3 | * All rights reserved.
4 | *
5 | * 摘 要:关于对话框类,显示关于信息
6 | *
7 | * 当前版本:1.5
8 | * 作 者:聂咸丰
9 | * 完成日期:2019 年 9 月 1 日
10 | */
11 | #ifndef ABOUTDIALOG_H
12 | #define ABOUTDIALOG_H
13 |
14 | #include
15 | #include
16 | #include
17 | #include "mytitlebar.h"
18 |
19 | namespace Ui {
20 | class AboutDialog;
21 | }
22 |
23 | class AboutDialog : public QDialog
24 | {
25 | Q_OBJECT
26 |
27 | public:
28 | explicit AboutDialog(QWidget *parent = nullptr);
29 | ~AboutDialog();
30 |
31 | protected:
32 | bool eventFilter(QObject *obj, QEvent *event); // 事件过滤器:只实现点击label打开网页的功能
33 |
34 | private:
35 | Ui::AboutDialog *ui;
36 |
37 | MyTitleBar* m_titleBar; // 自定义标题栏
38 | };
39 |
40 | #endif // ABOUTDIALOG_H
41 |
--------------------------------------------------------------------------------
/aboutdialog.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | AboutDialog
4 |
5 |
6 |
7 | 0
8 | 0
9 | 360
10 | 180
11 |
12 |
13 |
14 | Dialog
15 |
16 |
17 |
18 |
19 | 20
20 | 65
21 | 80
22 | 80
23 |
24 |
25 |
26 | border-image: url(:/image/logo.png);
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 120
36 | 140
37 | 220
38 | 31
39 |
40 |
41 |
42 |
43 | 宋体
44 | 10
45 |
46 |
47 |
48 | 版权所有 2019 智澜科技 保留所有权
49 |
50 |
51 |
52 |
53 |
54 | 120
55 | 40
56 | 121
57 | 31
58 |
59 |
60 |
61 |
62 | 宋体
63 | 16
64 |
65 |
66 |
67 | PID调试助手
68 |
69 |
70 |
71 |
72 |
73 | 120
74 | 90
75 | 91
76 | 31
77 |
78 |
79 |
80 |
81 | 宋体
82 | 12
83 |
84 |
85 |
86 | version 1.5
87 |
88 |
89 |
90 |
91 |
92 | 220
93 | 100
94 | 54
95 | 12
96 |
97 |
98 |
99 |
100 | true
101 |
102 |
103 |
104 | PointingHandCursor
105 |
106 |
107 | color: rgb(110, 148, 204);
108 |
109 |
110 | 官方网站
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/chart.cpp:
--------------------------------------------------------------------------------
1 | #include "chart.h"
2 |
3 | Chart::Chart(QMainWindow *parent) : QMainWindow(parent)
4 | {
5 | // 初始化成员变量
6 | m_pauseTime = 0;
7 | m_vecPidLastData.resize(5); // 存放5个通道的PID上一次结果值
8 | m_ismoving = false;
9 | }
10 |
11 | void Chart::initChart(QCustomPlot *ChartWidget)
12 | {
13 | // 初始化图表实例指针
14 | m_customPlot = ChartWidget;
15 | // 将customPlot的mouseMove信号连接到mouseCoordConver函数
16 | connect(m_customPlot, &QCustomPlot::mouseMove, this, &Chart::mouseCoordConver);
17 | // 安装事件过滤器
18 | m_customPlot->installEventFilter(this);
19 | // 设置背景色
20 | m_customPlot->setBackground(QBrush(QColor(0, 0, 0)));
21 | // 设置x/y轴刻度标签颜色
22 | m_customPlot->yAxis->setTickLabelColor(QColor(255, 255, 255));
23 | m_customPlot->xAxis->setTickLabelColor(QColor(255, 255, 255));
24 | // 支持鼠标拖拽轴的范围、滚动缩放轴的范围,左键点选图层(每条曲线独占一个图层)
25 | m_customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
26 |
27 | // 添加通道1曲线
28 | m_customPlot->addGraph(); // 向绘图区域QCustomPlot添加一条曲线
29 | m_customPlot->graph(0)->setPen(QPen(Qt::red, 3)); // 设置曲线颜色
30 | // 添加通道2曲线
31 | m_customPlot->addGraph();
32 | m_customPlot->graph(1)->setPen(QPen(Qt::magenta, 3));
33 | // 添加通道3曲线
34 | m_customPlot->addGraph();
35 | m_customPlot->graph(2)->setPen(QPen(Qt::green, 3));
36 | // 添加通道4曲线
37 | m_customPlot->addGraph();
38 | m_customPlot->graph(3)->setPen(QPen(Qt::blue, 3));
39 | // 添加通道5曲线
40 | m_customPlot->addGraph();//添加graph等价于添加新曲线
41 | m_customPlot->graph(4)->setPen(QPen(Qt::cyan, 3));
42 |
43 | // 最开始设置Y轴的范围为0-1000
44 | m_customPlot->yAxis->setRange(0, 1000);
45 |
46 | // 创建时间坐标轴
47 | QSharedPointer timeTicker(new QCPAxisTickerTime);
48 | timeTicker->setTimeFormat("%s");
49 | timeTicker->setTickCount(10); // 设置越大,同屏显示的时间栅格越多
50 | m_customPlot->xAxis->setTicker(timeTicker);
51 |
52 | for(int i=0; igraph(i)->rescaleAxes(true);
56 | // 解决点击曲线,曲线变蓝色的问题
57 | m_customPlot->graph(i)->setSelectable(QCP::stNone);
58 | }
59 |
60 | // 立即刷新图表
61 | m_customPlot->replot();
62 | }
63 |
64 | // 更新图表
65 | void Chart::updataChart(char channel, QVector vecPidData)
66 | {
67 | // 自串口打开以来经过的时间-暂停时间,即x轴的时间值,以s为单位
68 | double XAxisTime = (m_serialTime.elapsed() - this->m_pauseTime)/1000.0;
69 |
70 | // 关闭通道后停止向graph里面添加数据
71 | if((channel&0x01) == 0x01)
72 | {
73 | if(m_vecPidLastData[0] != vecPidData[0]) //值改变才添加数据到graph中
74 | {
75 | // 将数据添加到曲线
76 | m_customPlot->graph(0)->addData(XAxisTime, vecPidData[0]);
77 | // 添加到本地数据vector,方便保存和重新加载
78 | m_mapChannel[0].insert(XAxisTime, vecPidData[0]);
79 | // 获得上一次的数据(曲线不光滑的原因是好几次的Y值都一样,导致横线[时间过长]过长)
80 | m_vecPidLastData[0] = vecPidData[0];
81 | }
82 | }
83 | if((channel&0x02) == 0x02)
84 | {
85 | if(m_vecPidLastData[1] != vecPidData[1])
86 | {
87 | m_customPlot->graph(1)->addData(XAxisTime, vecPidData[1]);
88 | m_mapChannel[1].insert(XAxisTime, vecPidData[1]);
89 | m_vecPidLastData[1] = vecPidData[1];
90 | }
91 | }
92 | if((channel&0x04) == 0x04)
93 | {
94 | if(m_vecPidLastData[2] != vecPidData[2])
95 | {
96 | m_customPlot->graph(2)->addData(XAxisTime, vecPidData[2]);
97 | m_mapChannel[2].insert(XAxisTime, vecPidData[2]);
98 | m_vecPidLastData[2] = vecPidData[2];
99 | }
100 | }
101 | if((channel&0x08) == 0x08)
102 | {
103 | if(m_vecPidLastData[3] != vecPidData[3])
104 | {
105 | m_customPlot->graph(3)->addData(XAxisTime, vecPidData[3]);
106 | m_mapChannel[3].insert(XAxisTime, vecPidData[3]);
107 | m_vecPidLastData[3] = vecPidData[3];
108 | }
109 | }
110 | if((channel&0x10) == 0x10)
111 | {
112 | if(m_vecPidLastData[4] != vecPidData[4])
113 | {
114 | m_customPlot->graph(4)->addData(XAxisTime, vecPidData[4]);
115 | m_mapChannel[4].insert(XAxisTime, vecPidData[4]);
116 | m_vecPidLastData[4] = vecPidData[4];
117 | }
118 | }
119 |
120 | // 自适应设置X和Y轴的范围
121 | if(!m_ismoving)
122 | {
123 | // 自定义x轴值的显示范围(起始值为elapsedTime,终止值为TIMER_COUNT)
124 | m_customPlot->xAxis->setRange(XAxisTime, 8, Qt::AlignRight);
125 |
126 | for(int i=0; igraph(i)->rescaleValueAxis(true);
128 | }
129 |
130 | // 立即刷新图像
131 | m_customPlot->replot();
132 | }
133 |
134 | // 事件过滤器
135 | bool Chart::eventFilter(QObject *obj, QEvent *event)
136 | {
137 | // 检测是否使用鼠标
138 | if (obj == m_customPlot && event->type()==QEvent::MouseButtonPress)
139 | {
140 | QMouseEvent *event_ = static_cast(event);
141 | if(m_customPlot->rect().contains(event_->pos()) && event_->button() == Qt::LeftButton)
142 | {
143 | // 鼠标左键移动,则将m_ismoving设置为true
144 | m_ismoving = true;
145 | }
146 | }
147 | else if (event->type()==QEvent::MouseButtonRelease)
148 | {
149 | // 鼠标左键释放,则将m_ismoving设置为false
150 | m_ismoving = false;
151 | }
152 | // 检测是否使用滚轮
153 | if(event->type()==QEvent::Wheel)
154 | {
155 | m_ismoving = true;
156 | }
157 |
158 | return QWidget::eventFilter(obj,event);
159 | }
160 |
161 | // 启动串口计时器
162 | void Chart::startTimer()
163 | {
164 | m_serialTime.start();
165 | }
166 |
167 | // 清零暂停时间
168 | void Chart::clearPauseTime()
169 | {
170 | m_pauseTime = 0;
171 | }
172 |
173 | // 往通道曲线中添加数据
174 | void Chart::addChannelData(int channel, double x, int y)
175 | {
176 | m_customPlot->graph(channel)->addData(x, y);
177 | }
178 |
179 | // 清除通道对应曲线
180 | void Chart::clearChannel(int channel)
181 | {
182 | // 写入空的vector即可清除曲线
183 | m_customPlot->graph(channel)->setData({}, {});
184 | }
185 |
186 | // 清除图表
187 | void Chart::clearChart()
188 | {
189 | for(int i=0; igraph(i)->setData({}, {});
191 | }
192 |
193 | // 刷新图表
194 | void Chart::replotChart()
195 | {
196 | m_customPlot->replot();
197 | }
198 |
199 | // 清空本地各通道的数据
200 | void Chart::clearChannelData()
201 | {
202 | for(int i=0; i* Chart::getMapData()
214 | {
215 | return m_mapChannel;
216 | }
217 |
218 | // 自适应设置所有通道的X和Y轴范围
219 | void Chart::adaptiveAll()
220 | {
221 | // 自适应设置X和Y轴的范围
222 | for(int i=0; igraph(i)->rescaleAxes(true);
224 | }
225 |
226 | // 鼠标坐标点转换函数
227 | void Chart::mouseCoordConver(QMouseEvent *event)
228 | {
229 | // 把鼠标坐标点 转换为 QCustomPlot 内部坐标值 (pixelToCoord 函数)
230 | double x_val = m_customPlot->xAxis->pixelToCoord(event->pos().x());
231 | double y_val = m_customPlot->yAxis->pixelToCoord(event->pos().y());
232 |
233 | QString xValStr = QString::number(x_val, 'f', 2);
234 | QString yValStr = QString::number(y_val, 'f', 2);
235 | // 在图表区域中鼠标移动,则将内部鼠标坐标点发送给下方状态栏中的X和Y标签
236 | emit sendCoordToMain(xValStr.toFloat(), yValStr.toFloat());
237 | }
238 |
239 | // 控制是否暂停更新
240 | void Chart::controlUpdata(bool pauseFlag)
241 | {
242 | if(!pauseFlag)
243 | m_pauseTime += this->m_pauseTimer.elapsed(); // 计算总暂停时间
244 | else
245 | m_pauseTimer.restart(); // 暂停更新后开始计时
246 | }
247 |
--------------------------------------------------------------------------------
/chart.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019,浙江智澜科技有限公司
3 | * All rights reserved.
4 | *
5 | * 摘 要:图表类,主要显示5个通道的曲线
6 | *
7 | * 当前版本:1.5
8 | * 作 者:聂咸丰
9 | * 完成日期:2019 年 9 月 10 日
10 | */
11 | #ifndef CHART_H
12 | #define CHART_H
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include "common.h"
21 | #include "serial.h"
22 | #include "mymessagebox.h"
23 | #include "aboutdialog.h"
24 | #include "qcustomplot.h"
25 |
26 | #define CHANNEL_COUNT 5 // 通道数量
27 |
28 | class Chart : public QMainWindow
29 | {
30 | Q_OBJECT
31 |
32 | public:
33 | Chart(QMainWindow *parent = nullptr);
34 |
35 | void initChart(QCustomPlot *ChartWidget); // 初始化图表
36 | void updataChart(char channel, QVector vecPidData); // 更新图表
37 | bool eventFilter(QObject *obj, QEvent *event); // 事件过滤器
38 | void startTimer(); // 启动串口计时器
39 | void clearPauseTime();
40 | void addChannelData(int channel, double x, int y); // 往通道曲线中添加数据
41 | void clearChannel(int channel); // 清除通道对应曲线
42 | void clearChart(); // 清除图表
43 | void replotChart(); // 刷新图表
44 | void clearChannelData(); // 清空本地各通道的数据
45 | void saveChannelData(int channel, double x, int y); // 本地保存通道数据
46 | QMap* getMapData(); // 获得本地保存的各通道数据
47 | void adaptiveAll(); // 自适应设置所有通道的X和Y轴范围
48 | void mouseCoordConver(QMouseEvent *event); // 鼠标坐标点转换函数
49 | void controlUpdata(bool flag); // 控制是否暂停更新
50 |
51 | signals:
52 | void sendCoordToMain(float, float); // 发送鼠标坐标点给主窗口的信号
53 |
54 | private:
55 | QCustomPlot *m_customPlot; // 图表实例指针
56 | QVector m_vecPidLastData; // 存放5个通道的PID上一次结果值
57 | QMap m_mapChannel[5]; // 本地保存各通道的数据
58 | bool m_ismoving; // 鼠标左键是否移动和滚轮是否使用的标志位
59 |
60 | QTime m_pauseTimer; // 暂停时间计时器
61 | int m_pauseTime; // 暂停时间
62 | QTime m_serialTime; // 串口打开后的计时器
63 | };
64 |
65 | #endif // CHART_H
66 |
--------------------------------------------------------------------------------
/common.cpp:
--------------------------------------------------------------------------------
1 | #include "common.h"
2 |
3 | common::common()
4 | {
5 |
6 | }
7 |
8 | // 延时函数
9 | void common::delayMSec(int msec)
10 | {
11 | QEventLoop loop;//定义一个新的事件循环
12 | QTimer::singleShot(msec, &loop, SLOT(quit()));//创建单次定时器,槽函数为事件循环的退出函数
13 | loop.exec();//事件循环开始执行,程序会卡在这里,直到定时时间到,本循环被退出
14 | }
15 |
16 | // 获得有效通道序号
17 | int common::getChannelIndex(char channel)
18 | {
19 | // 若channel=0x08,则返回4
20 | int validChIndex = 0;
21 | for(int i=0; i<5;i++)
22 | {
23 | validChIndex++;
24 | if(channel==0x01)
25 | break;
26 | channel >>= 1;
27 | }
28 |
29 | return validChIndex;
30 | }
31 |
32 | // 获得浮点数的小数部分
33 | int common::getDecimal(float value)
34 | {
35 | QString str = QString::number(static_cast(value));
36 | QStringList strList = str.split(".");
37 |
38 | return strList.at(1).toInt();
39 | }
40 |
--------------------------------------------------------------------------------
/common.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019,浙江智澜科技有限公司
3 | * All rights reserved.
4 | *
5 | * 摘 要:common.h/cpp,存放一些公用的宏、枚举、类
6 | *
7 | * 当前版本:1.5
8 | * 作 者:聂咸丰
9 | * 完成日期:2019 年 8 月 28 日
10 | */
11 | #ifndef COMMON_H
12 | #define COMMON_H
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | /******************************协议帧******************************/
20 | #define FRAME_HEAD 0xC33C // 帧头
21 | #define FRAME_LENGTH 0x15 // 帧长度(接受5通道的帧长度为0x13)
22 | #define FRAME_TAIL 0x0D0A // 帧头
23 | #define RECV_DATA_CMD 0x01 // 命令:接受下位机上传数据【通道1-5(2byte),共18个字节】
24 | #define SEND_PID_CMD 0x02 // 命令:上位机发送PID给下位机【通道(1byte)、KP-KI-KD(4byte),共21个字节】
25 | #define RECV_PID_CMD 0x03 // 命令:上位机读取PID值,下位机回复格式也一样【通道(1byte)、KP-KI-KD(4byte)】
26 | #define RECV_DEFAULT_PID_CMD 0x04 // 命令:上位机读取并设置默认PID值,下位机回复格式也一样【通道(1byte)、KP-KI-KD(4byte)】
27 | #define SEND_DEFAULT_PID_CMD 0x05 // 命令:上位机发送PID值给下位机,重新系统默认PID值【通道(1byte)、KP-KI-KD(4byte)】
28 | #define LIMIT_LENGTH 10 // 避免串口有时候只接受到几个字节,后面调用QByteArray.at(i)导致崩溃
29 |
30 | typedef union _Temp
31 | {
32 | qint16 int16Data[11];
33 | char cData[22];
34 | }TempAll;
35 |
36 | typedef union _TempPid
37 | {
38 | float fData[3];
39 | char cData[12];
40 | }TempPid;
41 |
42 | typedef union _Temp1
43 | {
44 | qint16 int16Data[5];
45 | char cData[10];
46 | }Temp1;
47 |
48 | class common
49 | {
50 | public:
51 | common();
52 |
53 | static int getChannelIndex(char channel); // 获得有效通道序号
54 | static int getDecimal(float value); // 获得浮点数的小数部分
55 | static void delayMSec(int msec); // 延时函数
56 | };
57 |
58 | #endif // COMMON_H
59 |
--------------------------------------------------------------------------------
/image.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | image/back_board.png
4 | image/logo.png
5 | image/close.png
6 | image/close1.png
7 | image/MyMessageBox/MyMessageBox.css
8 | image/MyTitle/MyTitle.css
9 | image/MyTitle/close.png
10 | image/MyTitle/close11.png
11 | image/MyTitle/max.png
12 | image/MyTitle/min.png
13 | image/MyTitle/restore.png
14 | image/MyMessageBox/information.png
15 | image/MyMessageBox/question.png
16 | image/MyMessageBox/warnning.png
17 | image/MyMessageBox/WindowIcon.png
18 | image/down_arrow_16.png
19 |
20 |
21 |
--------------------------------------------------------------------------------
/image/MyMessageBox/MyMessageBox.css:
--------------------------------------------------------------------------------
1 | *{font-family:Microsoft YaHei;}
2 |
3 | QLabel#MessageContent
4 | {
5 | font-size:14px;
6 | }
7 |
8 | QPushButton#okButton
9 | {
10 | border-radius:3px;
11 | background-color:rgb(244 , 244 , 244);
12 | border: 1px solid rgb(160 , 150 , 152);
13 | }
14 |
15 | QPushButton#okButton:hover
16 | {
17 | background-color:rgb(190 , 231 , 253);
18 | border: 1px solid rgb(79 , 173 , 216);
19 | }
20 |
21 | QPushButton#okButton:pressed
22 | {
23 | background-color:rgb(235 , 236 , 237);
24 | border: 1px solid rgb(79 , 173 , 216);
25 | padding-left:2px;
26 | padding-top:2px;
27 | }
28 |
29 | QPushButton#cancelButton
30 | {
31 | border-radius:3px;
32 | background-color:rgb(244 , 244 , 244);
33 | border: 1px solid rgb(160 , 150 , 152);
34 | }
35 |
36 | QPushButton#cancelButton:hover
37 | {
38 | background-color:rgb(190 , 231 , 253);
39 | border: 1px solid rgb(79 , 173 , 216);
40 | }
41 |
42 | QPushButton#cancelButton:pressed
43 | {
44 | background-color:rgb(235 , 236 , 237);
45 | border: 1px solid rgb(79 , 173 , 216);
46 | padding-left:2px;
47 | padding-top:2px;
48 | }
49 |
50 | QLineEdit#inputContent
51 | {
52 | border: 1px solid rgb(195 , 195 , 195);
53 | }
54 |
55 | QLineEdit#inputContent:hover
56 | {
57 | border: 1px solid rgb(1 , 186 , 255 );
58 | }
--------------------------------------------------------------------------------
/image/MyMessageBox/WindowIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/confidentFeng/PidAssistant/6c7a3993695673b22121a4357f879167aae58d1e/image/MyMessageBox/WindowIcon.png
--------------------------------------------------------------------------------
/image/MyMessageBox/information.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/confidentFeng/PidAssistant/6c7a3993695673b22121a4357f879167aae58d1e/image/MyMessageBox/information.png
--------------------------------------------------------------------------------
/image/MyMessageBox/question.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/confidentFeng/PidAssistant/6c7a3993695673b22121a4357f879167aae58d1e/image/MyMessageBox/question.png
--------------------------------------------------------------------------------
/image/MyMessageBox/warnning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/confidentFeng/PidAssistant/6c7a3993695673b22121a4357f879167aae58d1e/image/MyMessageBox/warnning.png
--------------------------------------------------------------------------------
/image/MyTitle/MyTitle.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/confidentFeng/PidAssistant/6c7a3993695673b22121a4357f879167aae58d1e/image/MyTitle/MyTitle.css
--------------------------------------------------------------------------------
/image/MyTitle/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/confidentFeng/PidAssistant/6c7a3993695673b22121a4357f879167aae58d1e/image/MyTitle/close.png
--------------------------------------------------------------------------------
/image/MyTitle/close11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/confidentFeng/PidAssistant/6c7a3993695673b22121a4357f879167aae58d1e/image/MyTitle/close11.png
--------------------------------------------------------------------------------
/image/MyTitle/max.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/confidentFeng/PidAssistant/6c7a3993695673b22121a4357f879167aae58d1e/image/MyTitle/max.png
--------------------------------------------------------------------------------
/image/MyTitle/min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/confidentFeng/PidAssistant/6c7a3993695673b22121a4357f879167aae58d1e/image/MyTitle/min.png
--------------------------------------------------------------------------------
/image/MyTitle/restore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/confidentFeng/PidAssistant/6c7a3993695673b22121a4357f879167aae58d1e/image/MyTitle/restore.png
--------------------------------------------------------------------------------
/image/down_arrow_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/confidentFeng/PidAssistant/6c7a3993695673b22121a4357f879167aae58d1e/image/down_arrow_16.png
--------------------------------------------------------------------------------
/image/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/confidentFeng/PidAssistant/6c7a3993695673b22121a4357f879167aae58d1e/image/logo.png
--------------------------------------------------------------------------------
/main.cpp:
--------------------------------------------------------------------------------
1 | #include "mainwindow.h"
2 | #include
3 |
4 | int main(int argc, char *argv[])
5 | {
6 | QApplication a(argc, argv);
7 | MainWindow w;
8 | w.show();
9 |
10 | return a.exec();
11 | }
12 |
--------------------------------------------------------------------------------
/mainwindow.cpp:
--------------------------------------------------------------------------------
1 | #include "mainwindow.h"
2 | #include "ui_mainwindow.h"
3 |
4 | MainWindow::MainWindow(QWidget *parent) :
5 | QMainWindow(parent),
6 | ui(new Ui::MainWindow)
7 | {
8 | ui->setupUi(this);
9 |
10 | // 设置窗体标题
11 | this->setWindowTitle("PidAssistant");
12 | // 禁止最大化按钮
13 | this->setWindowFlags(windowFlags() &~ Qt::WindowMaximizeButtonHint);
14 | // 禁止拖动窗口大小
15 | this->setFixedSize(this->width(),this->height());
16 | // 默认不使能暂停更新按钮
17 | ui->pauseUpdateButton->setEnabled(false);
18 | // 默认设置按钮样式
19 | ui->pauseUpdateButton->setStyleSheet("background-color: rgb(255, 255, 255); border-radius:8px;");
20 | ui->windowTopButton->setStyleSheet("background-color: rgb(255, 255, 255); border-radius:8px;");
21 | // 限制输入栏只能输入数字和小数点
22 | // 通过正则表达式设置三行"密码输入栏",只能输入6位数字
23 | QValidator *accountValidator = new QRegExpValidator(QRegExp("^(-?[0]|-?[1-9][0-9]{0,5})(?:\\.\\d{1,4})?$|(^\\t?$)"));
24 | ui->pLineEdit->setValidator(accountValidator);
25 | ui->dLineEdit->setValidator(accountValidator);
26 | ui->iLineEdit->setValidator(accountValidator);
27 |
28 | // 初始化成员变量
29 | m_vecPidData.resize(5); // 存放5个通道的PID结果值
30 | m_channel = 0x00; // 有效通道
31 |
32 | // 默认打开通道1
33 | ui->ch1Frame->setStyleSheet("color: rgba(255, 0, 0, 255);");
34 | m_channel |= 0x01;
35 | // 为ch[1~5]Frame添加事件过滤器
36 | ui->ch1Frame->installEventFilter(this);
37 | ui->ch2Frame->installEventFilter(this);
38 | ui->ch3Frame->installEventFilter(this);
39 | ui->ch4Frame->installEventFilter(this);
40 | ui->ch5Frame->installEventFilter(this);
41 | // label安装事件过滤器,实现鼠标点击事件
42 | ui->taobaoLabel->installEventFilter(this);
43 | ui->chartWidget->installEventFilter(this);
44 |
45 | // 初始化串口
46 | m_serial = new Serial;
47 | // 寻找可用串口
48 | QStringList serialStrList;
49 | serialStrList = m_serial->scanSerial();
50 | for (int i=0; iportComboBox->addItem(serialStrList[i]); // 在comboBox那添加串口号
53 | }
54 | // 默认设置波特率为115200(第5项)
55 | ui->baudComboBox->setCurrentIndex(5);
56 | // 当下位机中有数据发送过来时就会响应这个槽函数
57 | connect(m_serial, SIGNAL(readOneFrame()), this, SLOT(readFromSerial()));
58 | // 处理串口错误
59 | connect(m_serial, SIGNAL(errorSignal()), this, SLOT(handleSerialError()));
60 | // 连接onNewSerialPort信号槽,定时获取串口列表
61 | connect(m_serial, SIGNAL(onNewSerialPort(QStringList)),this, SLOT(onNewPortList(QStringList)));
62 |
63 | // 初始化右键菜单(下个版本添加)
64 | // initRightClickMenu();
65 |
66 | // 初始化图表实例指针
67 | m_chart = new Chart(this);
68 | // 初始化图表
69 | m_chart->initChart(ui->chartWidget);
70 | // 读取到一帧数据,则更新一次图表
71 | connect(this, &MainWindow::updataSignal, this, &MainWindow::runUpdataChart);
72 | // 在下方状态栏上显示鼠标所在位置
73 | connect(m_chart, SIGNAL(sendCoordToMain(float, float)), this, SLOT(showCoor(float, float)));
74 |
75 | QStyle* style = QApplication::style();
76 | // 添加Qt内置的"加载"图标
77 | QIcon icon = style->standardIcon(QStyle::SP_DialogOpenButton);
78 | ui->loadAction->setIcon(icon);
79 | // 添加Qt内置的"保存"图标
80 | icon = style->standardIcon(QStyle::SP_DialogSaveButton);
81 | ui->saveAction->setIcon(icon);
82 | }
83 |
84 | MainWindow::~MainWindow()
85 | {
86 | delete ui;
87 | }
88 |
89 | // 运行更新绘图函数的函数
90 | void MainWindow::runUpdataChart()
91 | {
92 | // 在右侧的数码管上显示各个通道的PID输出值
93 | ui->ch1LcdNumber->display(QString("%1").arg(m_vecPidData[0]));
94 | ui->ch2LcdNumber->display(QString("%1").arg(m_vecPidData[1]));
95 | ui->ch3LcdNumber->display(QString("%1").arg(m_vecPidData[2]));
96 | ui->ch4LcdNumber->display(QString("%1").arg(m_vecPidData[3]));
97 | ui->ch5LcdNumber->display(QString("%1").arg(m_vecPidData[4]));
98 |
99 | // 更新绘图
100 | m_chart->updataChart(m_channel, m_vecPidData);
101 | }
102 |
103 | // 事件过滤器
104 | bool MainWindow::eventFilter(QObject *obj, QEvent *event)
105 | {
106 | // 通道标签选择是否高亮
107 | if(event->type()==QEvent::MouseButtonPress)
108 | {
109 | QMouseEvent *mouseEvent = static_cast(event);
110 |
111 | if(obj==ui->ch1Frame && mouseEvent->buttons()==Qt::LeftButton)
112 | {
113 | if(ui->ch1Frame->styleSheet() == "color: rgba(255, 0, 0, 70);")
114 | {
115 | ui->ch1Frame->setStyleSheet("color: rgba(255, 0, 0, 255);");
116 | m_channel |= 0x01;
117 | }
118 | else
119 | {
120 | ui->ch1Frame->setStyleSheet("color: rgba(255, 0, 0, 70);");
121 | m_channel &= 0xFE;
122 | m_chart->clearChannel(0); // 清空数据
123 | }
124 | }
125 | else if(obj==ui->ch2Frame && mouseEvent->buttons()==Qt::LeftButton)
126 | {
127 | if(ui->ch2Frame->styleSheet() == "color: rgba(255, 0, 255, 70);")
128 | {
129 | ui->ch2Frame->setStyleSheet("color: rgba(255, 0, 255, 255);");
130 | m_channel |= 0x02;
131 | }
132 | else
133 | {
134 | ui->ch2Frame->setStyleSheet("color: rgba(255, 0, 255, 70);");
135 | m_channel &= 0xFD;
136 | m_chart->clearChannel(1);
137 | }
138 | }
139 | else if(obj==ui->ch3Frame && mouseEvent->buttons()==Qt::LeftButton)
140 | {
141 | if(ui->ch3Frame->styleSheet() == "color: rgba(0, 128, 0, 70);")
142 | {
143 | ui->ch3Frame->setStyleSheet("color: rgba(0, 128, 0, 255);");
144 | m_channel |= 0x04;
145 | }
146 | else
147 | {
148 | ui->ch3Frame->setStyleSheet("color: rgba(0, 128, 0, 70);");
149 | m_channel &= 0xFB;
150 | m_chart->clearChannel(2);
151 | }
152 | }
153 | else if(obj==ui->ch4Frame && mouseEvent->buttons()==Qt::LeftButton)
154 | {
155 | if(ui->ch4Frame->styleSheet() == "color: rgba(0, 0, 255, 70);")
156 | {
157 | ui->ch4Frame->setStyleSheet("color: rgba(0, 0, 255, 255);");
158 | m_channel |= 0x08;
159 | }
160 | else
161 | {
162 | ui->ch4Frame->setStyleSheet("color: rgba(0, 0, 255, 70);");
163 | m_channel &= 0xF7;
164 | m_chart->clearChannel(3);
165 | }
166 | }
167 | else if(obj==ui->ch5Frame && mouseEvent->buttons()==Qt::LeftButton)
168 | {
169 | if(ui->ch5Frame->styleSheet() == "color: rgba(0, 255, 255, 70);")
170 | {
171 | ui->ch5Frame->setStyleSheet("color: rgba(0, 255, 255, 255);");
172 | m_channel |= 0x10;
173 | }
174 | else
175 | {
176 | ui->ch5Frame->setStyleSheet("color: rgba(0, 255, 255, 70);");
177 | m_channel &= 0xEF;
178 | m_chart->clearChannel(4);
179 | }
180 | }
181 | }
182 |
183 | // 只有单个通道有效时,读写按钮才能使用
184 | if(m_channel==0x01 || m_channel==0x02 || m_channel==0x04 || m_channel==0x08 || m_channel==0x10)
185 | {
186 | ui->readPidButton->setEnabled(true);
187 | ui->writePidButton->setEnabled(true);
188 | ui->resButton->setEnabled(true);
189 | ui->setButton->setEnabled(true);
190 | }
191 | else
192 | {
193 | ui->readPidButton->setEnabled(false);
194 | ui->writePidButton->setEnabled(false);
195 | ui->resButton->setEnabled(false);
196 | ui->setButton->setEnabled(false);
197 | }
198 |
199 | // 按下"官方淘宝商铺"Label,打开公司淘宝网页
200 | if(obj==ui->taobaoLabel && event->type()==QEvent::MouseButtonPress)
201 | {
202 | QDesktopServices::openUrl(QUrl(QLatin1String("https://shop360222626.taobao.com/index.htm?"
203 | "spm=2013.1.w5002-21081213145.2.4a80f31bmzWRiF")));
204 | }
205 |
206 | return QWidget::eventFilter(obj,event);
207 | }
208 |
209 | // 初始化右键菜单
210 | void MainWindow::initRightClickMenu()
211 | { // 初始化动作
212 | QAction *action1 = new QAction("新建",this);
213 | // 初始化右键菜单
214 | QMenu *rightClickMenu = new QMenu(this);
215 | // 动作添加到右键菜单
216 | rightClickMenu->addAction(action1);
217 | rightClickMenu->addAction(ui->newAction);
218 | rightClickMenu->addSeparator();
219 | rightClickMenu->addAction(ui->exitAction);
220 | // 给动作设置信号槽
221 | //connect(exitAction, &QAction::triggered, this, &MainWindow::on_exitAction_triggered);
222 |
223 | // 给控件设置上下文菜单策略:鼠标右键点击控件时会发送一个void QWidget::customContextMenuRequested(const QPoint &pos)信号
224 | ui->chartWidget->setContextMenuPolicy(Qt::CustomContextMenu);
225 | // 给信号设置相应的槽函数
226 | connect(ui->chartWidget, &QCustomPlot::customContextMenuRequested, [=](const QPoint &pos)
227 | {
228 | Q_UNUSED(pos);
229 | rightClickMenu->exec(QCursor::pos());
230 | });
231 | }
232 |
233 | // 打开串口按钮-点击槽函数
234 | void MainWindow::on_openSerialButton_clicked()
235 | {
236 | if(ui->openSerialButton->text() == tr("打开串口"))
237 | {
238 | // 打开串口
239 | if(m_serial->open(ui->portComboBox->currentText(), ui->baudComboBox->currentText().toInt()))
240 | {
241 | // 打开串口前清除图表,避免加载数据产生曲线和串口接受曲线混杂
242 | m_chart->clearChart();
243 | // 清空本地各通道的数据(避免加载数据中本地保存通道数据和打开串口后接受通道数据混淆)
244 | m_chart->clearChannelData();
245 | // 暂停时间清零
246 | m_chart->clearPauseTime();
247 | // 启动串口计时器,以获得串口打开后的经过时间
248 | m_chart->startTimer();
249 |
250 | // 关闭下拉列表使能
251 | ui->portComboBox->setEnabled(false);
252 | ui->baudComboBox->setEnabled(false);
253 | // 使能暂停更新按钮
254 | ui->pauseUpdateButton->setEnabled(true);
255 | // 修改按钮名称
256 | ui->openSerialButton->setText(tr("关闭串口"));
257 | // 修改串口提示标签的文本,且颜色修改为蓝色
258 | ui->serialTipLabel->setText(tr("串口已开启"));
259 | ui->serialTipLabel->setStyleSheet("color:blue");
260 | }
261 | }
262 | else
263 | {
264 | // 避免当暂停更新(会disconnect)后关闭串口,再打开串口不接受数据的情况
265 | m_isPause = false;
266 | emit ui->pauseUpdateButton->clicked();
267 |
268 | // 关闭串口
269 | m_serial->close();
270 | // 重新开启下拉列表使能
271 | ui->portComboBox->setEnabled(true);
272 | ui->baudComboBox->setEnabled(true);
273 | // 设置不使能暂停更新按钮
274 | ui->pauseUpdateButton->setEnabled(false);
275 | // 恢复按钮名称
276 | ui->openSerialButton->setText(tr("打开串口"));
277 | // 恢复串口提示标签的文本及颜色
278 | ui->serialTipLabel->setText(tr("串口未开启"));
279 | ui->serialTipLabel->setStyleSheet("color:red");
280 | }
281 | }
282 |
283 | // 读取来自串口类的数据
284 | void MainWindow::readFromSerial()
285 | {
286 | QByteArray readBuf = m_serial->getReadBuf();
287 | //qDebug() << "mainRecv:" << readBuf;
288 |
289 | // 校验帧头
290 | TempAll tempAll;
291 | tempAll.cData[0] = readBuf.at(0);
292 | tempAll.cData[1] = readBuf.at(1);
293 | if(tempAll.int16Data[0] != static_cast(FRAME_HEAD))
294 | {
295 | qDebug() << "校验帧头错误";
296 | m_serial->clearReadBuf();
297 | return;
298 | }
299 |
300 | // 检验校验位 【注意所有帧的长度不一致】
301 | qint16 checkSum = 0;
302 | for(int i=0; i(readBuf.at(i));
305 | }
306 | char checkSumH = static_cast((checkSum & 0xff00) >> 8);
307 | char checkSumL = static_cast((checkSum & 0x00ff));
308 | if(checkSumH!=readBuf.at(readBuf.at(2)-3) || checkSumL!=readBuf.at(readBuf.at(2)-4))
309 | {
310 | qDebug() << "校验位错误";
311 | m_serial->clearReadBuf();
312 | return;
313 | }
314 |
315 | // 校验命令
316 | switch (readBuf.at(3))
317 | {
318 | case RECV_DATA_CMD: // 命令:接受下位机上传数据
319 | {
320 | // 通道1-5(2byte)
321 | Temp1 temp1;
322 | for(int i=0; i<10; i++)
323 | {
324 | temp1.cData[i] = readBuf[4+i];
325 | }
326 |
327 | // 更新图表数据
328 | for(int i=0; ipLineEdit->setText(QString("%1").arg(static_cast(tempPid.fData[0])));
346 | ui->iLineEdit->setText(QString("%1").arg(static_cast(tempPid.fData[1])));
347 | ui->dLineEdit->setText(QString("%1").arg(static_cast(tempPid.fData[2])));
348 | }
349 | break;
350 | case RECV_DEFAULT_PID_CMD: // 命令:上位机读取并设置默认PID值,下位机回复格式也一样
351 | {
352 | //qDebug() << "读到出厂PID";
353 | // 通道(1byte)、KP-KI-KD(4byte)
354 | TempPid tempPid;
355 | for(int i=0; i<12; i++)
356 | {
357 | tempPid.cData[i] = readBuf[5+i];
358 | }
359 |
360 | //显示默认PID值
361 | ui->pLineEdit->setText(QString("%1").arg(static_cast(tempPid.fData[0])));
362 | ui->iLineEdit->setText(QString("%1").arg(static_cast(tempPid.fData[1])));
363 | ui->dLineEdit->setText(QString("%1").arg(static_cast(tempPid.fData[2])));
364 |
365 | // 恢复出厂后,还要将读出的出厂PID写入到相应通道
366 | m_serial->sendDataFrame(SEND_PID_CMD, m_channel, tempPid.fData[0], tempPid.fData[1], tempPid.fData[2]);
367 | }
368 | break;
369 | }
370 |
371 | // 清除读取数据缓冲区
372 | m_serial->clearReadBuf();
373 | }
374 |
375 | // 处理串口错误
376 | void MainWindow::handleSerialError()
377 | {
378 | MyMessageBox *msgBox = new MyMessageBox();
379 | msgBox->showMyMessageBox(nullptr, tr("错误提示"), tr("串口连接中断,请检查是否正确连接!"), MESSAGE_WARNNING,
380 | BUTTON_OK, true);
381 |
382 | // 再次触发"打开串口"按钮槽函数,以关闭串口
383 | ui->openSerialButton->setText(tr("关闭串口"));
384 | emit ui->openSerialButton->clicked();
385 | }
386 |
387 | // 用于响应SerialPortList信号,周期获取串口列表
388 | void MainWindow::onNewPortList(QStringList portName)
389 | {
390 | ui->portComboBox->clear();
391 | ui->portComboBox->addItems(portName);
392 | }
393 |
394 | // 读取PID按钮-点击槽函数
395 | void MainWindow::on_readPidButton_clicked()
396 | {
397 | MyMessageBox *msgBox = new MyMessageBox();
398 | // 判断串口是否打开
399 | if(!m_serial->isOpen())
400 | {
401 | msgBox->showMyMessageBox(this, tr("错误提示"), tr("串口未打开,请先打开串口!"), MESSAGE_WARNNING, \
402 | BUTTON_OK, true);
403 | return;
404 | }
405 |
406 | // 只有单个通道有效时,才能读取PID,达到读取单个通道PID的功能
407 | if(m_channel==0x01 || m_channel==0x02 || m_channel==0x04 || m_channel==0x08 || m_channel==0x10)
408 | {
409 | // 发送全零的PID值给下位机,下位机收到后则上发有效通道的PID
410 | float pValue = 0;
411 | float iValue = 0;
412 | float dValue = 0;
413 |
414 | // 读取PID成功后,给用户提示
415 | MyMessageBox *msgBox = new MyMessageBox();
416 | msgBox->showMyMessageBox(this, tr("读取PID"), tr("成功读取通道 %1 的PID!").arg(common::getChannelIndex(m_channel)), \
417 | MESSAGE_INFORMATION, BUTTON_OK, true);
418 |
419 | //qDebug() << "发送读取PID值帧";
420 | for(int i=0; i<7; i++)
421 | {
422 | m_serial->sendDataFrame(RECV_PID_CMD, m_channel, pValue, iValue, dValue);
423 | common::delayMSec(100);
424 | }
425 | }
426 | }
427 |
428 | // 写入PID按钮-点击槽函数
429 | void MainWindow::on_writePidButton_clicked()
430 | {
431 | MyMessageBox *msgBox = new MyMessageBox();
432 | // 判断串口是否打开
433 | if(!m_serial->isOpen())
434 | {
435 | msgBox->showMyMessageBox(this, tr("错误提示"), tr("串口未打开,请先打开串口!"), MESSAGE_WARNNING, \
436 | BUTTON_OK, true);
437 | return;
438 | }
439 |
440 | if(ui->pLineEdit->text().isEmpty() || ui->iLineEdit->text().isEmpty() || ui->dLineEdit->text().isEmpty())
441 | {
442 | MyMessageBox *msgBox = new MyMessageBox();
443 | msgBox->showMyMessageBox(this, tr("写入PID"), tr("P、I、D值都需要输入!"), MESSAGE_WARNNING, \
444 | BUTTON_OK, true);
445 | return;
446 | }
447 |
448 | float kp = ui->pLineEdit->text().toFloat();
449 | float ki = ui->iLineEdit->text().toFloat();
450 | float kd = ui->dLineEdit->text().toFloat();
451 | m_serial->sendDataFrame(SEND_PID_CMD, m_channel, kp, ki, kd);
452 |
453 | // 写入PID成功后,给用户提示
454 | msgBox->showMyMessageBox(this, tr("写入PID"), tr("成功写入通道 %1 的PID!").arg(common::getChannelIndex(m_channel)), \
455 | MESSAGE_INFORMATION, BUTTON_OK, true);
456 | }
457 |
458 | // 恢复出厂按钮-点击槽函数
459 | void MainWindow::on_resButton_clicked()
460 | {
461 |
462 | MyMessageBox *msgBox = new MyMessageBox();
463 | // 判断串口是否打开
464 | if(!m_serial->isOpen())
465 | {
466 | msgBox->showMyMessageBox(this, tr("错误提示"), tr("串口未打开,请先打开串口!"), MESSAGE_WARNNING, \
467 | BUTTON_OK, true);
468 | return;
469 | }
470 |
471 | if(msgBox->showMyMessageBox(this, tr("恢复出厂"), tr("确定恢复出厂PID吗?"), MESSAGE_QUESTION, \
472 | BUTTON_OK_AND_CANCEL, true) == ID_OK)
473 | {
474 | //qDebug() << "恢复出厂-确定按钮";
475 | // 只有单个通道有效时,才能恢复出厂PID
476 | if(m_channel==0x01 || m_channel==0x02 || m_channel==0x04 || m_channel==0x08 || m_channel==0x10)
477 | {
478 | for(int i=0; i<7; i++)
479 | {
480 | m_serial->sendDataFrame(RECV_DEFAULT_PID_CMD, m_channel, 0, 0, 0);
481 | common::delayMSec(100);
482 | }
483 | }
484 | }
485 | }
486 |
487 | // 设置出厂按钮-点击槽函数
488 | void MainWindow::on_setButton_clicked()
489 | {
490 | MyMessageBox *msgBox = new MyMessageBox();
491 | // 判断串口是否打开
492 | if(!m_serial->isOpen())
493 | {
494 | msgBox->showMyMessageBox(this, tr("错误提示"), tr("串口未打开,请先打开串口!"), MESSAGE_WARNNING, \
495 | BUTTON_OK_AND_CANCEL, true);
496 | return;
497 | }
498 | // 判断P、I、D值是否输入
499 | if(ui->pLineEdit->text().isEmpty() || ui->iLineEdit->text().isEmpty() || ui->dLineEdit->text().isEmpty())
500 | {
501 | msgBox->showMyMessageBox(this, tr("设置出厂"), tr("出厂P、I、D值都需要输入!"), MESSAGE_WARNNING, \
502 | BUTTON_OK, true);
503 | return;
504 | }
505 |
506 | if(msgBox->showMyMessageBox(this, tr("设置出厂"), tr("确定重新设置出厂PID吗?"), MESSAGE_QUESTION, \
507 | BUTTON_OK_AND_CANCEL, true) == ID_OK)
508 | {
509 | //qDebug() << "设置出厂-确定按钮";
510 | // 只有单个通道有效时,才能重新设置出厂PID
511 | if(m_channel==0x01 || m_channel==0x02 || m_channel==0x04 || m_channel==0x08 || m_channel==0x10)
512 | {
513 | float kp = ui->pLineEdit->text().toFloat();
514 | float ki = ui->iLineEdit->text().toFloat();
515 | float kd = ui->dLineEdit->text().toFloat();
516 |
517 | m_serial->sendDataFrame(SEND_DEFAULT_PID_CMD, m_channel, kp, ki, kd);
518 | common::delayMSec(100);
519 | // 设置默认PID之后,还要写入当前PID
520 | m_serial->sendDataFrame(SEND_PID_CMD, m_channel, kp, ki, kd);
521 | }
522 | }
523 | }
524 |
525 | // 暂停更新按钮-点击槽函数
526 | void MainWindow::on_pauseUpdateButton_clicked()
527 | {
528 | if(m_isPause)
529 | {
530 | m_isPause = false;
531 | m_chart->controlUpdata(true);
532 | disconnect(this, &MainWindow::updataSignal, this, &MainWindow::runUpdataChart);
533 | ui->pauseUpdateButton->setStyleSheet("background-color:rgb(162, 188, 228); border-radius:8px;");
534 | }
535 | else
536 | {
537 | m_isPause = true;
538 | m_chart->controlUpdata(false);
539 | connect(this, &MainWindow::updataSignal, this, &MainWindow::runUpdataChart);
540 | ui->pauseUpdateButton->setStyleSheet("background-color: rgb(255, 255, 255); border-radius:8px;");
541 | }
542 | }
543 |
544 | // 窗口置顶按钮-点击槽函数
545 | void MainWindow::on_windowTopButton_clicked()
546 | {
547 | if (!m_windowFlags)
548 | {
549 | m_windowFlags = windowFlags();
550 | setWindowFlags(m_windowFlags | Qt::WindowStaysOnTopHint);
551 | this->show();
552 | ui->windowTopButton->setStyleSheet("background-color:rgb(162, 188, 228); border-radius:8px;");
553 | }
554 | else
555 | {
556 | m_windowFlags = nullptr;
557 | setWindowFlags(m_windowFlags);
558 | this->show();
559 | ui->windowTopButton->setStyleSheet("background-color: rgb(255, 255, 255); border-radius:8px;");
560 | }
561 | }
562 |
563 | // 在下方状态栏上显示鼠标所在位置
564 | void MainWindow::showCoor(float x_val, float y_val)
565 | {
566 | ui->xLabel->setText(QString("X: %1").arg(static_cast(x_val)));
567 | ui->yLabel->setText(QString("Y: %1").arg(static_cast(y_val)));
568 | }
569 |
570 | // 保存数据-触发槽函数
571 | void MainWindow::on_saveAction_triggered()
572 | {
573 | // 保存数据前先暂停更新
574 | if(m_serial->isOpen()) // 串口打开的前提下
575 | {
576 | m_isPause = false;
577 | m_chart->controlUpdata(m_isPause);
578 | disconnect(m_serial, SIGNAL(readOneFrame()), this, SLOT(readFromSerial()));
579 | }
580 |
581 | QFileDialog fileDialog;
582 | QString fileName = fileDialog.getSaveFileName(this, "保存数据", "", "Text File(*.txt)");
583 | if(fileName == "")
584 | {
585 | return;
586 | }
587 | QFile file(fileName);
588 | if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
589 | {
590 | MyMessageBox *msgBox = new MyMessageBox();
591 | msgBox->showMyMessageBox(this, tr("错误提示"), tr("文件打开失败!"), MESSAGE_WARNNING, BUTTON_OK, true);
592 | return;
593 | }
594 | else
595 | {
596 | // 获得各通道数据
597 | QMap *mapData;
598 | mapData = m_chart->getMapData();
599 |
600 | // 本地保存各通道的数据
601 | QTextStream textStream(&file);
602 | for(int i=0; i::iterator it = mapData[i].begin();
606 | for (it = mapData[i].begin(); it != mapData[i].end(); it++)
607 | {
608 | textStream << QString::number(it.key(), 10, 2) + " " + QString::number(it.value()) + "\r\n";
609 | }
610 | }
611 |
612 | file.close();
613 | }
614 |
615 | // 保存数据后再恢复更新
616 | if(m_serial->isOpen())
617 | {
618 | m_isPause = true;
619 | m_chart->controlUpdata(m_isPause);
620 | connect(m_serial, SIGNAL(readOneFrame()), this, SLOT(readFromSerial()));
621 | }
622 | }
623 |
624 | // 载入数据-触发槽函数
625 | void MainWindow::on_loadAction_triggered()
626 | {
627 | MyMessageBox *msgBox = new MyMessageBox();
628 | // 载入数据前,必须关闭串口
629 | if(m_serial->isOpen())
630 | {
631 | msgBox->showMyMessageBox(this, tr("错误提示"), tr("载入数据前,必须关闭串口!"), MESSAGE_WARNNING, BUTTON_OK, true);
632 | return;
633 | }
634 |
635 | // 载入数据前清除图表,避免加载数据产生曲线和串口接受曲线混杂
636 | m_chart->clearChart();
637 |
638 | // 载入数据
639 | QString fileName;
640 | fileName = QFileDialog::getOpenFileName(this, "载入数据", "", "Text File(*.txt)");
641 | if(fileName == "")
642 | {
643 | return ;
644 | }
645 | else
646 | {
647 | QFile file(fileName);
648 | if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
649 | {
650 | msgBox->showMyMessageBox(this, tr("错误提示"), tr("文件打开失败!"), MESSAGE_WARNNING, BUTTON_OK, true);
651 | return;
652 | }
653 | else
654 | {
655 | if(!file.isReadable())
656 | msgBox->showMyMessageBox(this, tr("错误提示"), tr("文件不可读!"), MESSAGE_WARNNING, BUTTON_OK, true);
657 | else
658 | {
659 | QTextStream textStream(&file);
660 | int index = 0; // 通道序号
661 | while(!textStream.atEnd())
662 | {
663 | QString line = textStream.readLine();
664 | if(line.contains("ch")) // 获得通道序号
665 | {
666 | index = QString(line.split("ch").at(1)).toInt();
667 | continue;
668 | }
669 | QStringList strList = line.split(" ");
670 | // 将数据添加到曲线
671 | m_chart->addChannelData(index-1, strList.at(0).toDouble(), strList.at(1).toInt());
672 | // 同时本地保存数据(适用于打开软件后,先加载数据再保存数据的情况)
673 | m_chart->saveChannelData(index-1, strList.at(0).toDouble(), strList.at(1).toInt());
674 | }
675 | file.close();
676 | }
677 | }
678 | }
679 |
680 | // 自适应设置所有通道的X和Y轴范围
681 | m_chart->adaptiveAll();
682 |
683 | // 刷新图表
684 | m_chart->replotChart();
685 | }
686 |
687 | // 截屏动作-触发槽函数
688 | void MainWindow::on_screenAction_triggered()
689 | {
690 | MyMessageBox *msgBox = new MyMessageBox();
691 |
692 | // 检查截图目录是否存在,若不存在则新建
693 | QString strDir = QCoreApplication::applicationDirPath() + "/screenshot";
694 | QDir dir;
695 | if (!dir.exists(strDir))
696 | {
697 | if(!dir.mkpath(strDir))
698 | msgBox->showMyMessageBox(this, tr("错误提示"), tr("新建截图目录失败!"), MESSAGE_WARNNING, \
699 | BUTTON_OK, true);
700 | }
701 |
702 | QPixmap pix = ui->chartWidget->grab(QRect(0,0,ui->chartWidget->width(),ui->chartWidget->height()));
703 | QString fileName= QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss") + ".png";//通过时间命名文件
704 | if(!pix.save(QCoreApplication::applicationDirPath() + "/screenshot/" + fileName, "png"))
705 | {
706 | msgBox->showMyMessageBox(this, tr("错误提示"), tr("截图失败!"), MESSAGE_WARNNING, \
707 | BUTTON_OK, true);
708 | }
709 | else
710 | {
711 | msgBox->showMyMessageBox(this, tr("提示"), tr("截图已保存在:安装目录\\Screenshot目录下!"), MESSAGE_INFORMATION, \
712 | BUTTON_OK, true);
713 | }
714 | }
715 |
716 | // 关于动作-触发槽函数
717 | void MainWindow::on_aboutAction_triggered()
718 | {
719 | AboutDialog *aboutW = new AboutDialog(this);
720 | aboutW->show();
721 | }
722 |
723 | // 退出动作-触发槽函数
724 | void MainWindow::on_exitAction_triggered()
725 | {
726 | this->close();
727 | }
728 |
--------------------------------------------------------------------------------
/mainwindow.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019,浙江智澜科技有限公司
3 | * All rights reserved.
4 | *
5 | * 摘 要:实现主窗口的基本功能
6 | *
7 | * 当前版本:1.5
8 | * 作 者:聂咸丰
9 | * 完成日期:2019 年 8 月 28 日
10 | */
11 | #ifndef MAINWINDOW_H
12 | #define MAINWINDOW_H
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include "qcustomplot.h"
21 | #include "common.h"
22 | #include "serial.h"
23 | #include "chart.h"
24 | #include "mymessagebox.h"
25 | #include "aboutdialog.h"
26 | #include
27 |
28 | namespace Ui {
29 | class MainWindow;
30 | }
31 |
32 | class MainWindow : public QMainWindow
33 | {
34 | Q_OBJECT
35 |
36 | public:
37 | explicit MainWindow(QWidget *parent = nullptr);
38 | ~MainWindow();
39 |
40 | protected:
41 | void runUpdataChart(); // 运行更新绘图函数的函数
42 | bool eventFilter(QObject *obj, QEvent *event); // 事件过滤器
43 | void initRightClickMenu(); // 初始化右键菜单
44 |
45 | private slots:
46 | void readFromSerial(); // 读取来自串口类的数据
47 | void handleSerialError(); // 处理串口错误
48 | void onNewPortList(QStringList portName); // 用于响应SerialPortList信号,周期获取串口列表
49 | void on_openSerialButton_clicked(); // 打开串口按钮-点击槽函数
50 | void on_readPidButton_clicked(); // 读取PID按钮-点击槽函数
51 | void on_writePidButton_clicked(); // 写入PID按钮-点击槽函数
52 | void on_resButton_clicked(); // 恢复出厂按钮-点击槽函数
53 | void on_setButton_clicked(); // 设置出厂按钮-点击槽函数
54 | void on_pauseUpdateButton_clicked(); // 暂停更新按钮-点击槽函数
55 | void on_windowTopButton_clicked(); // 窗口置顶按钮-点击槽函数
56 | void showCoor(float x_val, float y_val); // 在下方状态栏上显示鼠标所在位置
57 |
58 | void on_saveAction_triggered(); // 保存数据-触发槽函数
59 | void on_loadAction_triggered(); // 载入数据-触发槽函数
60 | void on_screenAction_triggered(); // 截屏动作-触发槽函数
61 | void on_aboutAction_triggered(); // 关于动作-触发槽函数
62 | void on_exitAction_triggered(); // 退出动作-触发槽函数
63 |
64 | signals:
65 | void updataSignal(); // 更新图表信号
66 |
67 | private:
68 | Ui::MainWindow *ui;
69 |
70 | Serial *m_serial; // 自定义串口类实例指针
71 | Chart *m_chart; // 图表实例指针
72 | QVector m_vecPidData; // 存放5个通道的PID结果值
73 | char m_channel; // 有效通道
74 | bool m_isPause = true; // 是否暂停更新的标志位
75 | int m_currIndex;
76 | Qt::WindowFlags m_windowFlags; // 窗口置顶标志
77 | };
78 |
79 | #endif // MAINWINDOW_H
80 |
--------------------------------------------------------------------------------
/mainwindow.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 1200
10 | 580
11 |
12 |
13 |
14 | MainWindow
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 10
24 | 0
25 | 1000
26 | 512
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | 10
37 | 520
38 | 71
39 | 31
40 |
41 |
42 |
43 |
44 | 宋体
45 | 10
46 |
47 |
48 |
49 | color: rgb(255, 0, 0);
50 |
51 |
52 | 串口未开启
53 |
54 |
55 |
56 |
57 |
58 | 990
59 | 520
60 | 71
61 | 20
62 |
63 |
64 |
65 |
66 | 宋体
67 | 12
68 | 75
69 | true
70 |
71 |
72 |
73 | color: rgb(0, 102, 204);
74 |
75 |
76 | 智澜科技
77 |
78 |
79 |
80 |
81 |
82 | 1080
83 | 520
84 | 101
85 | 20
86 |
87 |
88 |
89 |
90 | 宋体
91 | 12
92 | 75
93 | true
94 |
95 |
96 |
97 | PointingHandCursor
98 |
99 |
100 | color: rgb(0, 102, 204);
101 |
102 |
103 | 官方淘宝商铺
104 |
105 |
106 |
107 |
108 |
109 | 490
110 | 522
111 | 64
112 | 22
113 |
114 |
115 |
116 | /*按钮普通态*/
117 | QPushButton
118 | {
119 | /*背景颜色*/
120 | background-color:rgb(255 , 255 , 255);
121 | /*边框圆角半径为8像素*/
122 | border-radius:8px;
123 | }
124 |
125 |
126 | 暂停更新
127 |
128 |
129 |
130 |
131 |
132 | 810
133 | 522
134 | 64
135 | 22
136 |
137 |
138 |
139 | /*按钮普通态*/
140 | QPushButton
141 | {
142 | /*背景颜色*/
143 | background-color:rgb(255 , 255 , 255);
144 | /*边框圆角半径为8像素*/
145 | border-radius:8px;
146 | }
147 |
148 |
149 | 窗口置顶
150 |
151 |
152 |
153 |
154 |
155 | 1020
156 | 10
157 | 161
158 | 51
159 |
160 |
161 |
162 | color: rgba(255, 0, 0, 70);
163 |
164 |
165 | QFrame::StyledPanel
166 |
167 |
168 | QFrame::Raised
169 |
170 |
171 |
172 |
173 | 0
174 | 2
175 | 51
176 | 51
177 |
178 |
179 |
180 |
181 | 宋体
182 | 32
183 | 75
184 | true
185 |
186 |
187 |
188 |
189 |
190 |
191 | +
192 |
193 |
194 |
195 |
196 |
197 | 50
198 | 0
199 | 111
200 | 51
201 |
202 |
203 |
204 |
205 | 宋体
206 | 24
207 | 75
208 | true
209 |
210 |
211 |
212 |
213 |
214 |
215 | 0
216 |
217 |
218 | false
219 |
220 |
221 | 4
222 |
223 |
224 | 0.000000000000000
225 |
226 |
227 | 0
228 |
229 |
230 |
231 |
232 |
233 |
234 | 1020
235 | 70
236 | 161
237 | 51
238 |
239 |
240 |
241 | color: rgba(255, 0, 255, 70);
242 |
243 |
244 | QFrame::StyledPanel
245 |
246 |
247 | QFrame::Raised
248 |
249 |
250 |
251 |
252 | 0
253 | 2
254 | 51
255 | 51
256 |
257 |
258 |
259 |
260 | 宋体
261 | 32
262 | 75
263 | true
264 |
265 |
266 |
267 |
268 |
269 |
270 | +
271 |
272 |
273 |
274 |
275 |
276 | 50
277 | 0
278 | 111
279 | 51
280 |
281 |
282 |
283 |
284 | 宋体
285 | 24
286 | 75
287 | true
288 |
289 |
290 |
291 |
292 |
293 |
294 | 0
295 |
296 |
297 | false
298 |
299 |
300 | 4
301 |
302 |
303 | 0.000000000000000
304 |
305 |
306 | 0
307 |
308 |
309 |
310 |
311 |
312 |
313 | 1020
314 | 130
315 | 161
316 | 51
317 |
318 |
319 |
320 | color: rgba(0, 128, 0, 70);
321 |
322 |
323 | QFrame::StyledPanel
324 |
325 |
326 | QFrame::Raised
327 |
328 |
329 |
330 |
331 | 0
332 | 2
333 | 51
334 | 51
335 |
336 |
337 |
338 |
339 | 宋体
340 | 32
341 | 75
342 | true
343 |
344 |
345 |
346 |
347 |
348 |
349 | +
350 |
351 |
352 |
353 |
354 |
355 | 50
356 | 0
357 | 111
358 | 51
359 |
360 |
361 |
362 |
363 | 宋体
364 | 24
365 | 75
366 | true
367 |
368 |
369 |
370 |
371 |
372 |
373 | 0
374 |
375 |
376 | false
377 |
378 |
379 | 4
380 |
381 |
382 | 0.000000000000000
383 |
384 |
385 | 0
386 |
387 |
388 |
389 |
390 |
391 |
392 | 1020
393 | 190
394 | 161
395 | 51
396 |
397 |
398 |
399 | color: rgba(0, 0, 255, 70);
400 |
401 |
402 | QFrame::StyledPanel
403 |
404 |
405 | QFrame::Raised
406 |
407 |
408 |
409 |
410 | 0
411 | 2
412 | 51
413 | 51
414 |
415 |
416 |
417 |
418 | 宋体
419 | 32
420 | 75
421 | true
422 |
423 |
424 |
425 |
426 |
427 |
428 | +
429 |
430 |
431 |
432 |
433 |
434 | 50
435 | 0
436 | 111
437 | 51
438 |
439 |
440 |
441 |
442 | 宋体
443 | 24
444 | 75
445 | true
446 |
447 |
448 |
449 |
450 |
451 |
452 | 0
453 |
454 |
455 | false
456 |
457 |
458 | 4
459 |
460 |
461 | 0.000000000000000
462 |
463 |
464 | 0
465 |
466 |
467 |
468 |
469 |
470 |
471 | 1020
472 | 250
473 | 161
474 | 51
475 |
476 |
477 |
478 | color: rgba(0, 255, 255, 70);
479 |
480 |
481 | QFrame::StyledPanel
482 |
483 |
484 | QFrame::Raised
485 |
486 |
487 |
488 |
489 | 0
490 | 2
491 | 51
492 | 51
493 |
494 |
495 |
496 |
497 | 宋体
498 | 32
499 | 75
500 | true
501 |
502 |
503 |
504 |
505 |
506 |
507 | +
508 |
509 |
510 |
511 |
512 |
513 | 50
514 | 0
515 | 111
516 | 51
517 |
518 |
519 |
520 |
521 | 宋体
522 | 24
523 | 75
524 | true
525 |
526 |
527 |
528 |
529 |
530 |
531 | 0
532 |
533 |
534 | false
535 |
536 |
537 | 4
538 |
539 |
540 | 0.000000000000000
541 |
542 |
543 | 0
544 |
545 |
546 |
547 |
548 |
549 |
550 | 1020
551 | 320
552 | 161
553 | 31
554 |
555 |
556 |
557 | -
558 |
559 |
560 |
561 | 48
562 | 20
563 |
564 |
565 |
566 | QLineEdit {
567 | /* 边框1px宽,颜色为深紫色 */
568 | border: 1px solid rgb(41, 57, 85);
569 | /* 给定3px边框圆角 */
570 | border-radius: 3px;
571 | /* 背景色定为白色吧 */
572 | background: white;
573 | /* 文本的大小 */
574 | font-size: 14px ;
575 | }
576 |
577 | QLineEdit:hover {
578 | /* 鼠标悬停时,我们将编辑框的边框设置为蓝色 */
579 | border: 1px solid blue;
580 | }
581 |
582 |
583 |
584 | -
585 |
586 |
587 |
588 | 105
589 | 20
590 |
591 |
592 |
593 |
594 | 宋体
595 | 14
596 | 50
597 | false
598 |
599 |
600 |
601 | 比例(P):
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 | 1020
611 | 360
612 | 161
613 | 31
614 |
615 |
616 |
617 | -
618 |
619 |
620 |
621 | 48
622 | 20
623 |
624 |
625 |
626 | QLineEdit {
627 | /* 边框1px宽,颜色为深紫色 */
628 | border: 1px solid rgb(41, 57, 85);
629 | /* 给定3px边框圆角 */
630 | border-radius: 3px;
631 | /* 背景色定为白色吧 */
632 | background: white;
633 | /* 文本的大小 */
634 | font-size: 14px ;
635 | }
636 |
637 | QLineEdit:hover {
638 | /* 鼠标悬停时,我们将编辑框的边框设置为蓝色 */
639 | border: 1px solid blue;
640 | }
641 |
642 |
643 |
644 | -
645 |
646 |
647 |
648 | 105
649 | 20
650 |
651 |
652 |
653 |
654 | 宋体
655 | 14
656 | 50
657 | false
658 |
659 |
660 |
661 | 积分(I):
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 | 1020
671 | 400
672 | 161
673 | 22
674 |
675 |
676 |
677 | -
678 |
679 |
680 |
681 | 48
682 | 20
683 |
684 |
685 |
686 | QLineEdit {
687 | /* 边框1px宽,颜色为深紫色 */
688 | border: 1px solid rgb(41, 57, 85);
689 | /* 给定3px边框圆角 */
690 | border-radius: 3px;
691 | /* 背景色定为白色吧 */
692 | background: white;
693 | /* 文本的大小 */
694 | font-size: 14px ;
695 | }
696 |
697 | QLineEdit:hover {
698 | /* 鼠标悬停时,我们将编辑框的边框设置为蓝色 */
699 | border: 1px solid blue;
700 | }
701 |
702 |
703 |
704 | -
705 |
706 |
707 |
708 | 105
709 | 20
710 |
711 |
712 |
713 |
714 | 宋体
715 | 14
716 | 50
717 | false
718 |
719 |
720 |
721 | 微分(D):
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 | 600
731 | 520
732 | 161
733 | 31
734 |
735 |
736 |
737 | -
738 |
739 |
740 |
741 | 宋体
742 | 10
743 | 75
744 | true
745 |
746 |
747 |
748 | X: 0.00
749 |
750 |
751 |
752 | -
753 |
754 |
755 |
756 | 宋体
757 | 10
758 | 75
759 | true
760 |
761 |
762 |
763 | Y: 0.00
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 | 1020
773 | 451
774 | 158
775 | 54
776 |
777 |
778 |
779 | -
780 |
781 |
782 | 读取PID
783 |
784 |
785 |
786 | -
787 |
788 |
789 | 写入PID
790 |
791 |
792 |
793 | -
794 |
795 |
796 | 恢复出厂
797 |
798 |
799 |
800 | -
801 |
802 |
803 | 设置出厂
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 | 390
813 | 522
814 | 64
815 | 22
816 |
817 |
818 |
819 | /*按钮普通态*/
820 | QPushButton
821 | {
822 | /*背景颜色*/
823 | background-color:rgb(255 , 255 , 255);
824 | /*边框圆角半径为8像素*/
825 | border-radius:8px;
826 | }
827 |
828 | /*按钮停留态*/
829 | QPushButton:hover
830 | {
831 | /*背景颜色*/
832 | background-color:rgb(197, 212, 244);
833 | }
834 |
835 | /*按钮按下态*/
836 | QPushButton:pressed
837 | {
838 | /*背景颜色*/
839 | background-color: rgb(162, 188, 228);
840 | /*左内边距为3像素,让按下时字向右移动3像素*/
841 | padding-left:3px;
842 | /*上内边距为3像素,让按下时字向下移动3像素*/
843 | padding-top:3px;
844 | }
845 |
846 |
847 | 打开串口
848 |
849 |
850 |
851 |
852 |
853 | 92
854 | 523
855 | 287
856 | 22
857 |
858 |
859 |
860 | -
861 |
862 |
863 | QComboBox {
864 | border: 1px solid gray;
865 | border-radius: 3px;
866 | padding: 1px 2px 1px 2px;
867 | max-width: 6em;
868 | }
869 |
870 | QComboBox::drop-down {
871 | subcontrol-origin: padding;
872 | subcontrol-position: top right;
873 | width: 20px;
874 | border-left-width: 1px;
875 | border-left-color: darkgray;
876 | border-left-style: solid;
877 | border-top-right-radius: 3px;
878 | border-bottom-right-radius: 3px;
879 | }
880 |
881 | QComboBox::down-arrow {
882 | image: url(://image/down_arrow_16.png);
883 | }
884 |
885 |
886 |
-
887 |
888 | 4800
889 |
890 |
891 | -
892 |
893 | 9600
894 |
895 |
896 | -
897 |
898 | 19200
899 |
900 |
901 | -
902 |
903 | 38400
904 |
905 |
906 | -
907 |
908 | 57600
909 |
910 |
911 | -
912 |
913 | 115200
914 |
915 |
916 | -
917 |
918 | 128000
919 |
920 |
921 | -
922 |
923 | 256000
924 |
925 |
926 |
927 |
928 | -
929 |
930 |
931 | QComboBox {
932 | border: 1px solid gray;
933 | border-radius: 3px;
934 | padding: 1px 2px 1px 2px;
935 | max-width: 6em;
936 | }
937 |
938 | QComboBox::drop-down {
939 | subcontrol-origin: padding;
940 | subcontrol-position: top right;
941 | width: 20px;
942 | border-left-width: 1px;
943 | border-left-color: darkgray;
944 | border-left-style: solid;
945 | border-top-right-radius: 3px;
946 | border-bottom-right-radius: 3px;
947 | }
948 |
949 | QComboBox::down-arrow {
950 | image: url(://image/down_arrow_16.png);
951 | }
952 |
953 |
954 |
955 |
956 | -
957 |
958 |
959 |
960 | 宋体
961 | 9
962 |
963 |
964 |
965 | 选择串口:
966 |
967 |
968 |
969 | -
970 |
971 |
972 |
973 | 宋体
974 | 9
975 |
976 |
977 |
978 | 选择波特率:
979 |
980 |
981 |
982 |
983 |
984 |
985 |
1010 |
1011 |
1012 | 关于
1013 |
1014 |
1015 |
1016 |
1017 | 退出
1018 |
1019 |
1020 | Ctrl+Q
1021 |
1022 |
1023 |
1024 |
1025 | true
1026 |
1027 |
1028 | 新建
1029 |
1030 |
1031 | 新建动作
1032 |
1033 |
1034 |
1035 |
1036 | 保存数据
1037 |
1038 |
1039 | 保存数据
1040 |
1041 |
1042 | Ctrl+S
1043 |
1044 |
1045 |
1046 |
1047 | 载入数据
1048 |
1049 |
1050 | Ctrl+O
1051 |
1052 |
1053 |
1054 |
1055 | 截图
1056 |
1057 |
1058 | Ctrl+F1
1059 |
1060 |
1061 |
1062 |
1063 |
1064 |
1065 | QCustomPlot
1066 | QWidget
1067 |
1068 | 1
1069 |
1070 |
1071 |
1072 |
1073 |
1074 |
--------------------------------------------------------------------------------
/mymessagebox.cpp:
--------------------------------------------------------------------------------
1 | #include "mymessagebox.h"
2 | #include "ui_mymessagebox.h"
3 |
4 | MyMessageBox::MyMessageBox(QWidget *parent) :
5 | QWidget(parent),
6 | ui(new Ui::MyMessageBox)
7 | {
8 | ui->setupUi(this);
9 |
10 | // 让窗口居中显示 | 隐藏标题栏
11 | this->setWindowFlags(Qt::Window |Qt::FramelessWindowHint);
12 |
13 | // 初始化成员变量
14 | m_isModalFlag = false;
15 |
16 | // 初始化标题栏
17 | initTitleBar();
18 |
19 | //使用调色板设置窗口的背景色
20 | QPalette pal(palette());
21 | pal.setColor(QPalette::Background, QColor(255, 255, 255));
22 | setAutoFillBackground(true);
23 | setPalette(pal);
24 | }
25 |
26 | MyMessageBox::~MyMessageBox()
27 | {
28 | delete ui;
29 | }
30 |
31 | // 初始化标题栏
32 | void MyMessageBox::initTitleBar()
33 | {
34 | m_titleBar = new MyTitleBar(this);
35 |
36 | // 设置窗口边框宽度
37 | m_titleBar->setWindowBorderWidth(0);
38 | // 设置标题栏颜色
39 | m_titleBar->setBackgroundColor(185, 92, 125);
40 | // 设置标题栏上按钮类型
41 | m_titleBar->setButtonType(ONLY_CLOSE_BUTTON);
42 | // 设置标题栏长度
43 | m_titleBar->setTitleWidth(this->width());
44 | // 设置标题颜色
45 | m_titleBar->setTitleColor(255, 255, 255);
46 | // 设置标题栏图标
47 | m_titleBar->setTitleIcon("://image/MyMessageBox/WindowIcon.png", QSize(16, 16));
48 |
49 | // 默认隐藏输入栏
50 | ui->inputContent->setVisible(false);
51 | }
52 |
53 | // 设置标题内容
54 | void MyMessageBox::setWindowTitle(QString title , int titleFontSize)
55 | {
56 | m_titleBar->setTitleContent(title, titleFontSize);
57 | }
58 |
59 | // 设置提示内容
60 | void MyMessageBox::setContentText(QString contentText)
61 | {
62 | ui->MessageContent->setText(contentText);
63 | }
64 |
65 | // 设置对话框类型
66 | void MyMessageBox::setMessageType(MessageType messageType)
67 | {
68 | switch (messageType)
69 | {
70 | case MESSAGE_INFORMATION:
71 | ui->MessageIcon->setPixmap(QPixmap("://image/MyMessageBox/information.png"));
72 | break;
73 | case MESSAGE_WARNNING:
74 | ui->MessageIcon->setPixmap(QPixmap("://image/MyMessageBox/warnning.png"));
75 | break;
76 | case MESSAGE_QUESTION:
77 | ui->MessageIcon->setPixmap(QPixmap("://image/MyMessageBox/question.png"));
78 | break;
79 | case MESSAGE_INPUT:
80 | ui->MessageIcon->setVisible(false);
81 | ui->inputContent->setVisible(true);
82 | break;
83 | }
84 | }
85 |
86 | // 设置按钮类型
87 | void MyMessageBox::setButtonType(MessageButtonType buttonType)
88 | {
89 | switch (buttonType)
90 | {
91 | case BUTTON_OK:
92 | {
93 | ui->okButton->setText("确定");
94 | ui->cancelButton->setVisible(false);
95 | }
96 | break;
97 | case BUTTON_OK_AND_CANCEL:
98 | {
99 | ui->okButton->setText("确定");
100 | ui->cancelButton->setText("取消");
101 | }
102 | break;
103 | default:
104 | break;
105 | }
106 | }
107 |
108 | // 加载本地样式文件:可以将样式直接写在文件中,程序运行时直接加载进来
109 | void MyMessageBox::loadStyleSheet(const QString &sheetName)
110 | {
111 | QFile file("://image/MyMessageBox/" + sheetName + ".css");
112 | file.open(QFile::ReadOnly);
113 | if (file.isOpen())
114 | {
115 | QString styleSheet = this->styleSheet();
116 | styleSheet += QLatin1String(file.readAll());
117 | this->setStyleSheet(styleSheet);
118 | }
119 | }
120 |
121 | // 自定义显示函数
122 | int MyMessageBox::showMyMessageBox(QWidget* parent, const QString &title, const QString &contentText,\
123 | MessageType messageType, MessageButtonType messageButtonType, bool isModelWindow)
124 | {
125 | MyMessageBox * myMessage = new MyMessageBox(parent);
126 | // 设置标题内容
127 | myMessage->setWindowTitle(title, 10);
128 | // 设置提示内容
129 | myMessage->setContentText(contentText);
130 | // 设置对话框类型
131 | myMessage->setMessageType(messageType);
132 | // 设置按钮类型
133 | myMessage->setButtonType(messageButtonType);
134 | // 加载样式表
135 | myMessage->loadStyleSheet("MyMessageBox");
136 | // 设置是否为模态窗口
137 | m_isModalFlag = isModelWindow;
138 | if (m_isModalFlag)
139 | {
140 | // 设置为模态窗口时,参数parent必须设置父窗口指针,否则模态设置无效
141 | // 因为 Qt::WindowModal 参数只对父窗口有效,如果想要模态对全局窗口都有效可以设置 Qt::ApplicationModal
142 | return myMessage->exec();
143 | }
144 | else
145 | {
146 | myMessage->show();
147 | }
148 |
149 | return -1;
150 | }
151 |
152 | // 关闭事件:在exec()方法中返回选择结果
153 | void MyMessageBox::closeEvent(QCloseEvent *event)
154 | {
155 | // 关闭窗口时结束事件循环,在exec()方法中返回选择结果,且非模态窗口,使用exit()方法会出异常
156 | if (!m_isModalFlag && m_eventLoop != nullptr)
157 | {
158 | m_eventLoop->exit();
159 | }
160 | event->accept();
161 | }
162 |
163 | // 因为QWidget没有exec()方法,所以需要自己定义来完成exec()方法
164 | int MyMessageBox::exec()
165 | {
166 | // exec()方法就是直接设置窗口显示为模态,并且窗口关闭结束后返回用户选择结果(按了确定还是取消按钮)
167 | // 这里也可以继承QDialog类,QDialog有自己的exec()方法,根据返回 Accepted, Rejected 来决定是否按了确定按钮
168 | this->setWindowModality(Qt::WindowModal);
169 | this->show();
170 | // 使用事件循环QEventLoop ,不让exec()方法结束,在用户选择确定或者取消后,关闭窗口结束事件循环,并返回最后用户选择的结果
171 | // 根据返回结果得到用户按下了确定还是取消,采取相应的操作。从而模拟出QDialog类的exec()方法
172 | m_eventLoop = new QEventLoop(this);
173 | m_eventLoop->exec();
174 |
175 | return m_chooseResult;
176 | }
177 |
178 | // 确定按钮-点击信号槽
179 | void MyMessageBox::on_okButton_clicked()
180 | {
181 | m_chooseResult = ID_OK;
182 | this->close();
183 | }
184 |
185 | // 取消按钮-点击槽函数
186 | void MyMessageBox::on_cancelButton_clicked()
187 | {
188 | m_chooseResult = ID_CANCEL;
189 | this->close();
190 | }
191 |
--------------------------------------------------------------------------------
/mymessagebox.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019,浙江智澜科技有限公司
3 | * All rights reserved.
4 | *
5 | * 摘 要:实现消息对话框
6 | *
7 | * 当前版本:1.5
8 | * 作 者:聂咸丰
9 | * 完成日期:2019 年 9 月 4 日
10 | */
11 | #ifndef MYMESSAGEBOX_H
12 | #define MYMESSAGEBOX_H
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include "mytitlebar.h"
19 |
20 | enum ChosseResult
21 | {
22 | ID_OK = 0, // 确定;
23 | ID_CANCEL // 取消;
24 | };
25 |
26 | enum MessageType
27 | {
28 | MESSAGE_INFORMATION = 0, // 提示信息;
29 | MESSAGE_WARNNING, // 提示警告;
30 | MESSAGE_QUESTION, // 提示询问;
31 | MESSAGE_INPUT // 提示输入框;
32 | };
33 |
34 | enum MessageButtonType
35 | {
36 | BUTTON_OK = 0, // 只有确定按钮;
37 | BUTTON_OK_AND_CANCEL, // 确定、取消按钮;
38 | BUTTON_CLOSE // 关闭按钮;
39 | };
40 |
41 | namespace Ui {
42 | class MyMessageBox;
43 | }
44 |
45 | class MyMessageBox : public QWidget
46 | {
47 | Q_OBJECT
48 |
49 | public:
50 | explicit MyMessageBox(QWidget *parent = nullptr);
51 | ~MyMessageBox();
52 |
53 | // 自定义显示函数
54 | int showMyMessageBox(QWidget* parent, const QString &title,const QString &contentText , MessageType \
55 | messageType, MessageButtonType messageButtonType , bool isModelWindow = false);
56 |
57 | private slots:
58 | // 确定按钮-点击信号槽
59 | void on_okButton_clicked();
60 | // 取消按钮-点击槽函数
61 | void on_cancelButton_clicked();
62 |
63 | private:
64 | Ui::MyMessageBox *ui;
65 |
66 | // 初始化标题栏
67 | void initTitleBar();
68 | // 设置标题内容
69 | void setWindowTitle(QString title, int titleFontSize = 10);
70 | // 设置提示内容
71 | void setContentText(QString contentText);
72 | // 设置对话框类型
73 | void setMessageType(MessageType messageType);
74 | // 设置按钮类型
75 | void setButtonType(MessageButtonType buttonType);
76 | // 加载本地样式文件:可以将样式直接写在文件中,程序运行时直接加载进来
77 | void loadStyleSheet(const QString &sheetName);
78 | // 关闭事件:在exec()方法中返回选择结果
79 | void closeEvent(QCloseEvent *event);
80 | // 因为QWidget没有exec()方法,所以需要自己定义来完成exec()方法
81 | int exec();
82 |
83 | MyTitleBar* m_titleBar; // 自定义标题栏
84 | bool m_isModalFlag; // 是否为模态窗口的标志位
85 | QEventLoop* m_eventLoop; // 事件循环
86 | ChosseResult m_chooseResult; // 用户选择的结果
87 | };
88 |
89 | #endif // MYMESSAGEBOX_H
90 |
--------------------------------------------------------------------------------
/mymessagebox.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MyMessageBox
4 |
5 |
6 |
7 | 0
8 | 0
9 | 388
10 | 158
11 |
12 |
13 |
14 |
15 | 388
16 | 158
17 |
18 |
19 |
20 |
21 | 388
22 | 158
23 |
24 |
25 |
26 | MyMessage
27 |
28 |
29 |
30 | 0
31 |
32 |
33 | 0
34 |
35 |
36 | 22
37 |
38 |
39 | 0
40 |
41 |
42 | 10
43 |
44 | -
45 |
46 |
47 | Qt::Vertical
48 |
49 |
50 |
51 | 20
52 | 30
53 |
54 |
55 |
56 |
57 | -
58 |
59 |
60 | 12
61 |
62 |
63 | 0
64 |
65 |
66 | 22
67 |
68 |
-
69 |
70 |
71 | Qt::Horizontal
72 |
73 |
74 | QSizePolicy::Preferred
75 |
76 |
77 |
78 | 48
79 | 20
80 |
81 |
82 |
83 |
84 | -
85 |
86 |
87 |
88 | 38
89 | 38
90 |
91 |
92 |
93 |
94 | 38
95 | 38
96 |
97 |
98 |
99 | Icon
100 |
101 |
102 |
103 | -
104 |
105 |
106 | Content
107 |
108 |
109 |
110 | -
111 |
112 |
113 |
114 | 180
115 | 25
116 |
117 |
118 |
119 |
120 | 16777215
121 | 25
122 |
123 |
124 |
125 |
126 |
127 |
128 | -
129 |
130 |
131 | Qt::Vertical
132 |
133 |
134 |
135 | 20
136 | 30
137 |
138 |
139 |
140 |
141 | -
142 |
143 |
144 | 12
145 |
146 |
147 | 10
148 |
149 |
-
150 |
151 |
152 | Qt::Horizontal
153 |
154 |
155 |
156 | 40
157 | 20
158 |
159 |
160 |
161 |
162 | -
163 |
164 |
165 |
166 | 70
167 | 25
168 |
169 |
170 |
171 |
172 | 70
173 | 25
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | -
182 |
183 |
184 |
185 | 70
186 | 25
187 |
188 |
189 |
190 |
191 | 70
192 | 25
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
--------------------------------------------------------------------------------
/mytitlebar.cpp:
--------------------------------------------------------------------------------
1 | #include "mytitlebar.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | MyTitleBar::MyTitleBar(QWidget *parent) : QWidget(parent)
8 | {
9 | // 初始化标题栏背景色
10 | m_colorR = 153;
11 | m_colorG = 153;
12 | m_colorB = 153;
13 | // 初始化移动窗口的变量
14 | m_isPressed = false;
15 | // 初始化按钮类型
16 | m_buttonType = MIN_MAX_BUTTON;
17 | // 窗口边框宽度
18 | m_windowBorderWidth = 0;
19 |
20 | // 设置标题栏高度
21 | this->setFixedHeight(TITLE_HEIGHT);
22 | // 设置默认标题栏隐藏
23 | this->setWindowFlags(Qt::FramelessWindowHint);
24 |
25 | // 初始化控件
26 | initControl();
27 |
28 | // 信号槽的绑定(最小化、最大化、还原、关闭按钮)
29 | connect(m_pButtonMin, SIGNAL(clicked(bool)), this, SLOT(onClicked()));
30 | connect(m_pButtonMax, SIGNAL(clicked(bool)), this, SLOT(onClicked()));
31 | connect(m_pButtonRestore, SIGNAL(clicked(bool)), this, SLOT(onClicked()));
32 | connect(m_pButtonClose, SIGNAL(clicked(bool)), this, SLOT(onClicked()));
33 |
34 | // 加载本地样式文件:可以将样式直接写在文件中,程序运行时直接加载进来
35 | loadStyleSheet("MyTitle");
36 | }
37 |
38 | MyTitleBar::~MyTitleBar()
39 | {
40 |
41 | }
42 |
43 | // 初始化控件
44 | void MyTitleBar::initControl()
45 | {
46 | // 标题栏图标
47 | m_pIcon = new QLabel;
48 | // 标题栏内容
49 | m_pTitleLabel = new QLabel;
50 |
51 | // 初始化按钮(最小化、最大化\还原、关闭按钮)
52 | m_pButtonMin = new QPushButton;
53 | m_pButtonRestore = new QPushButton;
54 | m_pButtonMax = new QPushButton;
55 | m_pButtonClose = new QPushButton;
56 | // 设置按钮的固定大小
57 | m_pButtonMin->setFixedSize(QSize(BUTTON_WIDTH, BUTTON_HEIGHT));
58 | m_pButtonRestore->setFixedSize(QSize(BUTTON_WIDTH, BUTTON_HEIGHT));
59 | m_pButtonMax->setFixedSize(QSize(BUTTON_WIDTH, BUTTON_HEIGHT));
60 | m_pButtonClose->setFixedSize(QSize(BUTTON_WIDTH, BUTTON_HEIGHT));
61 | // 设置按钮的名称
62 | m_pTitleLabel->setObjectName("TitleContent");
63 | m_pButtonMin->setObjectName("ButtonMin");
64 | m_pButtonRestore->setObjectName("ButtonRestore");
65 | m_pButtonMax->setObjectName("ButtonMax");
66 | m_pButtonClose->setObjectName("ButtonClose");
67 | // 设置按钮的提示信息
68 | m_pButtonMin->setToolTip("最小化");
69 | m_pButtonRestore->setToolTip("还原");
70 | m_pButtonMax->setToolTip("最大化");
71 | m_pButtonClose->setToolTip("关闭");
72 |
73 | // 将控件添加到水平布局中
74 | QHBoxLayout* mylayout = new QHBoxLayout(this);
75 | mylayout->addWidget(m_pIcon);
76 | mylayout->addWidget(m_pTitleLabel);
77 | mylayout->addWidget(m_pButtonMin);
78 | mylayout->addWidget(m_pButtonRestore);
79 | mylayout->addWidget(m_pButtonMax);
80 | mylayout->addWidget(m_pButtonClose);
81 | // 设置4个方向的外边距
82 | mylayout->setContentsMargins(5, 0, 0, 0);
83 | mylayout->setSpacing(0);
84 | // 设置控件在布局(layout)里面的大小变化的属性
85 | m_pTitleLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
86 | }
87 |
88 | // 设置标题栏背景色,在paintEvent事件中进行绘制标题栏背景色
89 | void MyTitleBar::setBackgroundColor(int colorR, int colorG, int colorB)
90 | {
91 | // 在构造函数中给了默认值,可以外部设置颜色值改变标题栏背景色
92 | m_colorR = colorR;
93 | m_colorG = colorG;
94 | m_colorB = colorB;
95 | // 重新绘制(调用paintEvent事件)
96 | this->update();
97 | }
98 |
99 | // 设置标题栏图标
100 | void MyTitleBar::setTitleIcon(QString filePath, QSize IconSize)
101 | {
102 | QPixmap titleIcon(filePath);
103 | m_pIcon->setPixmap(titleIcon.scaled(IconSize));
104 | }
105 |
106 | // 设置标题内容
107 | void MyTitleBar::setTitleContent(QString titleContent, int titleFontSize)
108 | {
109 | // 设置标题字体大小
110 | QFont font = m_pTitleLabel->font();
111 | font.setPointSize(titleFontSize);
112 | m_pTitleLabel->setFont(font);
113 | // 设置标题标签内容
114 | m_pTitleLabel->setText(titleContent);
115 | // 设置标题栏内容
116 | m_titleContent = titleContent;
117 | }
118 |
119 | // 设置标题栏长度
120 | void MyTitleBar::setTitleWidth(int width)
121 | {
122 | this->setFixedWidth(width);
123 | }
124 |
125 | // 设置标题颜色
126 | void MyTitleBar::setTitleColor(int colorR, int colorG, int colorB)
127 | {
128 | m_pTitleLabel->setStyleSheet(QString("color: rgb(%1, %2, %3);").arg(colorR).arg(colorG).arg(colorB));
129 | }
130 |
131 | // 设置标题栏上按钮类型
132 | void MyTitleBar::setButtonType(ButtonType buttonType)
133 | {
134 | // 由于不同窗口标题栏上的按钮都不一样,所以可以自定义标题栏中的按钮
135 | // 这里提供了四个按钮,分别为最小化、还原、最大化、关闭按钮,如果需要其他按钮可自行添加设置
136 | m_buttonType = buttonType;
137 | switch (buttonType)
138 | {
139 | case MIN_BUTTON:
140 | {
141 | m_pButtonRestore->setVisible(false);
142 | m_pButtonMax->setVisible(false);
143 | }
144 | break;
145 | case MIN_MAX_BUTTON:
146 | {
147 | m_pButtonRestore->setVisible(false);
148 | }
149 | break;
150 | case ONLY_CLOSE_BUTTON:
151 | {
152 | m_pButtonMin->setVisible(false);
153 | m_pButtonRestore->setVisible(false);
154 | m_pButtonMax->setVisible(false);
155 | }
156 | break;
157 | }
158 | }
159 |
160 | // 设置标题栏中的标题是否会自动滚动,跑马灯的效果
161 | void MyTitleBar::setTitleRoll()
162 | {
163 | // 一般情况下标题栏中的标题内容是不滚动的,但是既然自定义就看自己需要嘛,想怎么设计就怎么搞O(∩_∩)O!
164 | connect(&m_titleRollTimer, SIGNAL(timeout()), this, SLOT(onRollTitle()));
165 | m_titleRollTimer.start(200);
166 | }
167 |
168 | // 设置窗口边框宽度
169 | void MyTitleBar::setWindowBorderWidth(int borderWidth)
170 | {
171 | m_windowBorderWidth = borderWidth;
172 | }
173 |
174 | // 绘图事件:绘制标题栏背景色
175 | void MyTitleBar::paintEvent(QPaintEvent *event)
176 | {
177 | // 设置标题栏背景色
178 | QPainter painter(this);
179 | QPainterPath pathBack;
180 | pathBack.setFillRule(Qt::WindingFill);
181 | pathBack.addRoundedRect(QRect(0, 0, this->width(), this->height()), 3, 3);
182 | painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
183 | painter.fillPath(pathBack, QBrush(QColor(m_colorR, m_colorG, m_colorB)));
184 |
185 | // 当窗口最大化或者还原后,窗口长度变了,标题栏的长度应当一起改变
186 | // 这里减去m_windowBorderWidth,是因为窗口可能设置了不同宽度的边框
187 | // 如果窗口有边框则需要设置m_windowBorderWidth的值,否则m_windowBorderWidth默认为0
188 | if (this->width() != (this->parentWidget()->width() - m_windowBorderWidth))
189 | {
190 | this->setFixedWidth(this->parentWidget()->width() - m_windowBorderWidth);
191 | }
192 | QWidget::paintEvent(event);
193 | }
194 |
195 | // 鼠标双击事件:主要是实现双击标题栏进行最大化和最小化操作
196 | void MyTitleBar::mouseDoubleClickEvent(QMouseEvent *event)
197 | {
198 | // 只有存在最大化、还原按钮时双击才有效;
199 | if (m_buttonType == MIN_MAX_BUTTON)
200 | {
201 | // 通过最大化按钮的状态判断当前窗口是处于最大化还是原始大小状态
202 | // 或者通过单独设置变量来表示当前窗口状态
203 | if (m_pButtonMax->isVisible())
204 | {
205 | m_pButtonRestore->setVisible(true);
206 | m_pButtonMax->setVisible(false);
207 | QWidget *pWindow = this->window(); //获得标题栏所在的窗口
208 | pWindow->showMaximized();
209 | }
210 | else
211 | {
212 | m_pButtonRestore->setVisible(false);
213 | m_pButtonMax->setVisible(true);
214 | QWidget *pWindow = this->window(); //获得标题栏所在的窗口
215 | pWindow->showNormal();
216 | }
217 | }
218 |
219 | return QWidget::mouseDoubleClickEvent(event);
220 | }
221 |
222 | // 以下通过mousePressEvent、mouseMoveEvent、mouseReleaseEvent三个事件实现了鼠标拖动标题栏移动窗口的效果
223 | void MyTitleBar::mousePressEvent(QMouseEvent *event)
224 | {
225 | if (m_buttonType == MIN_MAX_BUTTON)
226 | {
227 | // 在窗口最大化时禁止拖动窗口
228 | if (m_pButtonMax->isVisible())
229 | {
230 | m_isPressed = true;
231 | m_startMovePos = event->globalPos();
232 | }
233 | }
234 | else
235 | {
236 | m_isPressed = true;
237 | m_startMovePos = event->globalPos();
238 | }
239 |
240 | return QWidget::mousePressEvent(event);
241 | }
242 |
243 | void MyTitleBar::mouseMoveEvent(QMouseEvent *event)
244 | {
245 | if (m_isPressed)
246 | {
247 | QPoint movePoint = event->globalPos() - m_startMovePos;
248 | QPoint widgetPos = this->parentWidget()->pos();
249 | m_startMovePos = event->globalPos();
250 | this->parentWidget()->move(widgetPos.x() + movePoint.x(), widgetPos.y() + movePoint.y());
251 | }
252 | return QWidget::mouseMoveEvent(event);
253 | }
254 |
255 | void MyTitleBar::mouseReleaseEvent(QMouseEvent *event)
256 | {
257 | m_isPressed = false;
258 | return QWidget::mouseReleaseEvent(event);
259 | }
260 |
261 | // 加载本地样式文件:可以将样式直接写在文件中,程序运行时直接加载进来
262 | void MyTitleBar::loadStyleSheet(const QString &sheetName)
263 | {
264 | // ://image/MyTitle/MyTitle.css
265 | QFile file("://image/MyTitle/" + sheetName + ".css");
266 | file.open(QFile::ReadOnly);
267 | if (file.isOpen())
268 | {
269 | QString styleSheet = this->styleSheet();
270 | styleSheet += QLatin1String(file.readAll());
271 | this->setStyleSheet(styleSheet);
272 | }
273 | }
274 |
275 | // 该方法主要是让标题栏中的标题显示为滚动的效果
276 | void MyTitleBar::onRollTitle()
277 | {
278 | static int nPos = 0;
279 | QString titleContent = m_titleContent;
280 | // 当截取的位置比字符串长时,从头开始
281 | if (nPos > titleContent.length())
282 | nPos = 0;
283 |
284 | m_pTitleLabel->setText(titleContent.mid(nPos));
285 | nPos++;
286 | }
287 |
288 | // 进行最小化、最大化/还原、关闭操作
289 | void MyTitleBar::onClicked()
290 | {
291 | //QObject::Sender()返回发送信号的对象的指针,返回类型为QObject *
292 | QPushButton *pButton = qobject_cast(sender());
293 |
294 | QWidget *pWindow = this->window(); // 获得标题栏所在的窗口
295 |
296 | if (pWindow->isTopLevel())
297 | {
298 | //判断发送信号的对象是哪个按钮
299 | if (pButton == m_pButtonMin) // 窗口最小化显示
300 | {
301 | pWindow->showMinimized();
302 | }
303 | else if (pButton == m_pButtonMax) // 窗口最大化
304 | {
305 | m_pButtonRestore->setVisible(true);
306 | m_pButtonMax->setVisible(false);
307 | pWindow->showMaximized();
308 | }
309 | else if (pButton == m_pButtonRestore) // 窗口还原
310 | {
311 | m_pButtonRestore->setVisible(false);
312 | m_pButtonMax->setVisible(true);
313 | pWindow->showNormal();
314 | }
315 | else if (pButton == m_pButtonClose) //窗口关闭
316 | {
317 | pWindow->close();
318 | }
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/mytitlebar.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019,浙江智澜科技有限公司
3 | * All rights reserved.
4 | *
5 | * 摘 要:实现自定义标题栏,暂时只用于消息对话框类
6 | *
7 | * 当前版本:1.5
8 | * 作 者:聂咸丰
9 | * 完成日期:2019 年 9 月 3 日
10 | */
11 | #ifndef MYTITLEBAR_H
12 | #define MYTITLEBAR_H
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | #define BUTTON_HEIGHT 30 // 按钮高度
23 | #define BUTTON_WIDTH 30 // 按钮宽度
24 | #define TITLE_HEIGHT 30 // 标题栏高度
25 |
26 | enum ButtonType
27 | {
28 | MIN_BUTTON = 0, // 最小化和关闭按钮
29 | MIN_MAX_BUTTON , // 最小化、最大化和关闭按钮
30 | ONLY_CLOSE_BUTTON, // 只有关闭按钮
31 | };
32 |
33 | class MyTitleBar : public QWidget
34 | {
35 | Q_OBJECT
36 |
37 | public:
38 | MyTitleBar(QWidget *parent = nullptr);
39 | ~MyTitleBar();
40 |
41 | // 设置标题栏背景色
42 | void setBackgroundColor(int colorR, int colorG, int colorB);
43 | // 设置标题栏图标
44 | void setTitleIcon(QString filePath , QSize IconSize = QSize(25 , 25));
45 | // 设置标题内容
46 | void setTitleContent(QString titleContent , int titleFontSize = 9);
47 | // 设置标题栏长度
48 | void setTitleWidth(int width);
49 | // 设置标题颜色
50 | void setTitleColor(int colorR, int colorG, int colorB);
51 | // 设置标题栏上按钮类型
52 | void setButtonType(ButtonType buttonType);
53 | // 设置标题栏中的标题是否会滚动;具体可以看效果
54 | void setTitleRoll();
55 | // 设置窗口边框宽度
56 | void setWindowBorderWidth(int borderWidth);
57 |
58 | private:
59 | // 绘图事件:绘制标题栏背景色
60 | void paintEvent(QPaintEvent *event);
61 | // 鼠标双击事件:主要是实现双击标题栏进行最大化和最小化操作
62 | void mouseDoubleClickEvent(QMouseEvent *event);
63 | // 以下通过mousePressEvent、mouseMoveEvent、mouseReleaseEvent三个事件实现了鼠标拖动标题栏移动窗口的效果
64 | void mousePressEvent(QMouseEvent *event);
65 | void mouseMoveEvent(QMouseEvent *event);
66 | void mouseReleaseEvent(QMouseEvent *event);
67 |
68 | // 初始化控件
69 | void initControl();
70 | // 加载本地样式文件:可以将样式直接写在文件中,程序运行时直接加载进来
71 | void loadStyleSheet(const QString &sheetName);
72 |
73 | private slots:
74 | // 该方法主要是让标题栏中的标题显示为滚动的效果
75 | void onRollTitle();
76 | // 进行最小化、最大化/还原、关闭操作
77 | void onClicked();
78 |
79 | private:
80 | // 标题栏背景色;
81 | int m_colorR;
82 | int m_colorG;
83 | int m_colorB;
84 |
85 | QLabel* m_pIcon; // 标题栏图标
86 | QLabel* m_pTitleLabel; // 标题栏标签
87 | QPushButton* m_pButtonMin; // 最小化按钮
88 | QPushButton* m_pButtonRestore; // 最大化还原按钮
89 | QPushButton* m_pButtonMax; // 最大化按钮
90 | QPushButton* m_pButtonClose; // 关闭按钮
91 |
92 | bool m_isPressed; // 移动窗口的变量
93 | QPoint m_startMovePos; // 开始移动窗口的坐标变量
94 | QString m_titleContent; // 标题栏内容
95 | ButtonType m_buttonType; // 按钮类型
96 | QTimer m_titleRollTimer; // 标题栏跑马灯效果时钟
97 | int m_windowBorderWidth; // 窗口边框宽度
98 | };
99 |
100 | #endif // MYTITLEBAR_H
101 |
--------------------------------------------------------------------------------
/serial.cpp:
--------------------------------------------------------------------------------
1 | #include "serial.h"
2 |
3 | Serial::Serial()
4 | {
5 | // 初始化串口
6 | m_serialPort = new QSerialPort;
7 |
8 | // 初始化成员变量
9 | m_readBuf.clear();
10 | m_tempAll.clear();
11 |
12 | // 串口错误处理信号槽(热插拔)
13 | connect(m_serialPort,SIGNAL(errorOccurred(QSerialPort::SerialPortError)),this,SLOT(handleSerialError(QSerialPort::SerialPortError)));
14 |
15 | // 串口号列表更新定时器
16 | serialTimer = new QTimer(this);
17 | connect(serialTimer, SIGNAL(timeout()), SLOT(onTimeOut()));
18 | serialTimer->start(1500);
19 | }
20 |
21 | // 扫描可用串口
22 | QStringList Serial::scanSerial()
23 | {
24 | QStringList serialStrList;
25 |
26 | // 读取串口信息
27 | foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
28 | {
29 | serialStrList.append(info.portName());
30 | }
31 |
32 | return serialStrList;
33 | }
34 |
35 | // 打开串口
36 | bool Serial::open(QString serialName, int baudRate)
37 | {
38 | // 设置串口名
39 | m_serialPort->setPortName(serialName);
40 | // 打开串口(以读写方式)
41 | if(m_serialPort->open(QIODevice::ReadWrite))
42 | {
43 | m_serialPort->setBaudRate(baudRate); // 设置波特率(默认为115200)
44 | m_serialPort->setDataBits(QSerialPort::Data8); // 设置数据位(数据位为8位)
45 | m_serialPort->setParity(QSerialPort::NoParity); // 设置校验位(无校验位)
46 | m_serialPort->setStopBits(QSerialPort::OneStop); // 设置停止位(停止位为1)
47 | m_serialPort->setFlowControl(QSerialPort::NoFlowControl); // 设置流控制(无数据流控制)
48 |
49 | // 当下位机中有数据发送过来时就会响应这个槽函数
50 | connect(m_serialPort, &QSerialPort::readyRead, this, &Serial::readData);
51 |
52 | return true;
53 | }
54 |
55 | return false;
56 | }
57 |
58 | // 关闭串口
59 | void Serial::close()
60 | {
61 | m_serialPort->clear();
62 | m_serialPort->close();
63 | }
64 |
65 | // 发送数据给下位机
66 | void Serial::sendData(QByteArray &sendData)
67 | {
68 | // 发送数据帧
69 | m_serialPort->write(sendData);
70 | }
71 |
72 | // 发送数据帧给下位机
73 | void Serial::sendDataFrame(char cmd, char passage, float kp, float ki, float kd)
74 | {
75 | TempAll tempAll;
76 | // 帧头、帧长度、命令类型、通道
77 | tempAll.int16Data[0] = static_cast(FRAME_HEAD);
78 | tempAll.cData[2] =FRAME_LENGTH;
79 | tempAll.cData[3] =cmd;
80 | tempAll.cData[4] =passage;
81 | // PID值
82 | TempPid tempPid;
83 | tempPid.fData[0] = kp;
84 | tempPid.fData[1] = ki;
85 | tempPid.fData[2] = kd;
86 | for(int i=0; i<12; i++)
87 | {
88 | tempAll.cData[5+i] = static_cast(tempPid.cData[i]);
89 | }
90 | // 校验和
91 | qint16 checkSum = 0;
92 | for(int i=0; i(tempAll.cData[i]);
95 | }
96 | tempAll.cData[17] = static_cast((checkSum & 0x00ff));
97 | tempAll.cData[18] = static_cast((checkSum & 0xff00) >> 8);
98 | // 帧尾
99 | tempAll.cData[19] = static_cast((FRAME_TAIL & 0xff00) >> 8);
100 | tempAll.cData[20] = static_cast((FRAME_TAIL & 0x00ff));
101 |
102 | // 发送数据帧
103 | m_serialPort->write(tempAll.cData, FRAME_LENGTH);
104 | }
105 |
106 | // 读取下位机发来数据
107 | void Serial::readData()
108 | {
109 | //这个判断尤为重要,否则的话直接延时再接收数据,空闲时和会出现高内存占用
110 | if(m_serialPort->bytesAvailable()<=0)
111 | {
112 | return;
113 | }
114 |
115 | // 将下位机发来数据存储在数据缓冲区
116 | QByteArray temp = m_serialPort->readAll();
117 | m_tempAll.append(temp);
118 | //qDebug() <<"temp: " << temp;
119 |
120 | // 由于Qt串口固有缺陷,每次只能接受一帧数据的一部分,所以协议添加帧尾,进而分离出完整的一帧数据
121 | if(!m_tempAll.isEmpty())
122 | {
123 | if(m_tempAll.contains("\r\n"))
124 | {
125 | m_readBuf = m_tempAll.split('\r').at(0) + "\r\n";
126 | m_tempAll.clear();
127 |
128 | if(m_readBuf.length()>LIMIT_LENGTH && m_readBuf.length()==m_readBuf.at(2)) // 判断帧长度
129 | {
130 | emit readOneFrame();
131 | }
132 | }
133 | }
134 | }
135 |
136 | // 处理串口错误(热插拔)
137 | void Serial::handleSerialError(QSerialPort::SerialPortError error)
138 | {
139 | if (this->isOpen() && error!=QSerialPort::NoError) // 调试PID时突然拔掉串口线(PermissionError)、突然按下关机键(ResourceError)等
140 | {
141 | // 首先关闭串口
142 | m_serialPort->close();
143 |
144 | emit errorSignal();
145 | }
146 | }
147 |
148 | // 周期扫描是否有新串口
149 | void Serial::onTimeOut()
150 | {
151 | if(!this->isOpen()) // 串口打开时,不扫描串口
152 | {
153 | // 扫描可用串口
154 | QStringList newPortStrList = this->scanSerial();
155 |
156 | //更新旧的串口列表
157 | if(newPortStrList.size() != oldPortStrList.size())
158 | {
159 | oldPortStrList = newPortStrList;
160 | emit onNewSerialPort(oldPortStrList);
161 | }
162 | }
163 | }
164 |
165 | // 获得读取数据缓冲区
166 | QByteArray Serial::getReadBuf()
167 | {
168 | return m_readBuf;
169 | }
170 |
171 | // 清除读取数据缓冲区
172 | void Serial::clearReadBuf()
173 | {
174 | m_readBuf.clear();
175 | }
176 |
177 | // 判断串口是否打开
178 | bool Serial::isOpen()
179 | {
180 | return m_serialPort->isOpen();
181 | }
182 |
--------------------------------------------------------------------------------
/serial.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019,浙江智澜科技有限公司
3 | * All rights reserved.
4 | *
5 | * 摘 要:串口类,实现串口相关功能
6 | *
7 | * 当前版本:1.5
8 | * 作 者:聂咸丰
9 | * 完成日期:2019 年 9 月 5 日
10 | */
11 | #ifndef SERIAL_H
12 | #define SERIAL_H
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include "mymessagebox.h"
19 | #include "common.h"
20 |
21 | class Serial:public QObject // 要继承QObject,才能使用 connect() 函数
22 | {
23 | Q_OBJECT
24 |
25 | public:
26 | Serial();
27 | QStringList scanSerial(); // 扫描可用串口
28 | bool open(QString serialName, int baudRate); // 打开串口
29 | void close(); // 关闭串口
30 | void sendData(QByteArray &sendData); // 发送数据给下位机
31 | void sendDataFrame(char cmd, char passage, float kp, float ki, float kd); // 发送数据帧给下位机
32 | QByteArray getReadBuf(); // 获得读取数据缓冲区
33 | void clearReadBuf(); // 清除读取数据缓冲区
34 | bool isOpen(); // 判断串口是否打开
35 |
36 | signals:
37 | void readOneFrame(); //读取满一帧数据,就会触发这个信号,以提示其它类对象
38 | void errorSignal(); // 串口发生错误的信号
39 | void onNewSerialPort(QStringList); // 扫描到新串口的信号
40 |
41 | public slots:
42 | void readData(); // 读取下位机发来数据
43 | void handleSerialError(QSerialPort::SerialPortError error); // 处理串口错误(热插拔)
44 | void onTimeOut(); // 周期扫描是否有新串口
45 |
46 | private:
47 | QSerialPort *m_serialPort; // 实例化一个指向串口的指针,可以用于访问串口
48 | QTimer *serialTimer; // 周期扫描串口号定时器
49 | QStringList oldPortStrList; // 存放旧的串口号列表
50 | QByteArray m_readBuf; // 存储接受数据的缓冲区
51 | QByteArray m_tempAll; // 存储接受数据的临时缓冲区
52 | };
53 |
54 | #endif // SERIAL_H
55 |
--------------------------------------------------------------------------------
/zhiLanIcon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/confidentFeng/PidAssistant/6c7a3993695673b22121a4357f879167aae58d1e/zhiLanIcon.ico
--------------------------------------------------------------------------------