├── 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 | 986 | 987 | 988 | 0 989 | 0 990 | 1200 991 | 23 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 菜单 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 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 |
qcustomplot.h
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 --------------------------------------------------------------------------------