前言
本文記錄一下用QT Creator 寫一個基本功能齊全的串口助手的過程,整個工程只有幾百行代碼,跟著做下來對新手來說可以更快了解整個QT項目的開發(fā)過程和一些常用控件的使用方法。對新手學(xué)習(xí)QT能增強信心,話不多說,正文開始
先看成品:
制作過程
1. 布局UI界面
(1) 創(chuàng)建QMainWindow工程。這一步就不贅述了,參考:QT C++入門學(xué)習(xí)(1) QT Creator安裝和使用
創(chuàng)建項目時項目名稱可以設(shè)為Serial,基類可以選擇QMainWindow也可以選擇Qwiget。
注意默認(rèn)勾選“Generate form”,生成 ui 窗體文件 mainwindow.ui 后面要用到。
完成后項目文件展示:
(2)雙擊mainwindow.ui打開UI布局界面,從左側(cè)控件選擇區(qū)找到需要的控件拖動到界面設(shè)計區(qū)的對應(yīng)位置
(請注意:以下提到的擺放位置只放大概位置即可,因為必須借用布局工具才能規(guī)正)
找到Label控件:
用6個Lable控件分別按下圖所示紅框位置擺放,并且雙擊改顯示的文字,或單擊選擇對應(yīng)Lable后在其右邊屬性設(shè)置界面里的text屬性更改
找到Combo Box控件:
用5個Combo Box控件分別按下圖綠色框所示位置擺放
找到Push Button控件:
用5個Push Button控件分別按下圖紫色框所示位置擺放,并且雙擊按鈕改顯示的文字
找到Check Box控件:
用6個Check Box控件分別按下圖藍(lán)色框所示位置擺放,并且雙擊更改顯示的文字
用1個Spin Box控件分別按下圖棕色框所示位置擺放并調(diào)整大小
最后兩個大的白色區(qū)域就是接收框和發(fā)送輸入框,上面接收框用Plain Text Edit控件,下面輸入框用Text Edit控件
接收框用的Plain Text Edit控件需要更改屬性為只讀
2. 添加下拉列表項
5個Combo Box控件分別雙擊添加列表項(端口對應(yīng)的Combo Box不用改)
波特率:
(點擊綠色加號即可添加)
數(shù)據(jù)位:
停止位:
校驗位:
對于波特率和數(shù)據(jù)位的下拉列表控件還需要通過更改屬性currentIndex屬性,改變默認(rèn)值。波特率默認(rèn)9600,數(shù)據(jù)位默認(rèn)8。
3. 修改控件名稱
下面對控件進(jìn)行改名,以便對應(yīng)程序中的對象名,用默認(rèn)名不直觀。
如何改名?
點擊控件,找到界面右邊屬性欄的objectName。
名稱參考下圖:
注意這一步一定要把對象名設(shè)置對,否則在編譯程序時會有問題。
4. 利用布局工具
第一步放置控件的時候,想必就會發(fā)現(xiàn)根本很難通過鼠標(biāo)拖動的方法完成對齊,這時還得用上方菜單欄的布局工具:
下圖中的紅框就是布局后才顯示的:
操作方法是先手動把控件擺放到大概位置,然后鼠標(biāo)左鍵拉一個框選定幾個控件,再點擊上面的布局工具。
接收設(shè)置和發(fā)送設(shè)置就是用了柵格布局,發(fā)送和清空發(fā)送按鈕是垂直布局,串口設(shè)置中是上面部分是柵格布局,然后整體再用垂直布局。布局后的紅框還可以調(diào)整大小
最后這3個框是Group Box控件,雙擊就可以改文字。為啥把這步放最后了是因為發(fā)現(xiàn)Group Box控件放上去后直接選不了里面的控件了,暫時不知道怎么操作。這里跟VS里不一樣,VS就比較方便
5. 編輯代碼
雙擊打開.pro文件
core gui后面添加serialport,即 QT += core gui serialport
mainwindow.h文件源碼:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QSerialPort>
#include <QString>
#include <QSerialPortInfo>
#include <QMessageBox>
#include <QTimer>
#include <QPainter>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
QSerialPort *serialPort;//定義串口指針
private slots:
/*手動連接槽函數(shù)*/
void manual_serialPortReadyRead();
/*以下為mainwindow.ui文件中點擊“轉(zhuǎn)到槽”自動生成的函數(shù)*/
void on_openBt_clicked();
void on_sendBt_clicked();
void on_clearBt_clicked();
void on_btnClearSend_clicked();
void on_chkTimSend_stateChanged(int arg1);
void on_btnSerialCheck_clicked();
private:
Ui::MainWindow *ui;
// 發(fā)送、接收字節(jié)計數(shù)
long sendNum, recvNum;
QLabel *lblSendNum;
QLabel *lblRecvNum;
QLabel *lblPortState;
void setNumOnLabel(QLabel *lbl, QString strS, long num);
// 定時發(fā)送-定時器
QTimer *timSend;
//QTimer *timCheckPort;
};
#endif // MAINWINDOW_H
mainwindow.cpp文件源碼:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QSerialPortInfo"
#include <QSerialPort>
#include <QMessageBox>
#include <QDateTime>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QStringList serialNamePort;
serialPort = new QSerialPort(this);
connect(serialPort,SIGNAL(readyRead()),this,SLOT(manual_serialPortReadyRead()));/*手動連接槽函數(shù)*/
/*找出當(dāng)前連接的串口并顯示到serailCb*/
//foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
//{
//serialNamePort<<info.portName();// 自動掃描當(dāng)前可用串口,返回值追加到字符數(shù)組中
//}
//ui->serailCb->addItems(serialNamePort);// 可用串口號,顯示到串口選擇下拉框中
ui->serailCb->clear();
//通過QSerialPortInfo查找可用串口
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{
ui->serailCb->addItem(info.portName());
}
// 發(fā)送、接收計數(shù)清零
sendNum = 0;
recvNum = 0;
// 狀態(tài)欄
QStatusBar *sBar = statusBar();
// 狀態(tài)欄的收、發(fā)計數(shù)標(biāo)簽
lblSendNum = new QLabel(this);
lblRecvNum = new QLabel(this);
lblPortState = new QLabel(this);
lblPortState->setText("Connected");
//設(shè)置串口狀態(tài)標(biāo)簽為綠色 表示已連接狀態(tài)
lblPortState->setStyleSheet("color:red");
// 設(shè)置標(biāo)簽最小大小
lblSendNum->setMinimumSize(100, 20);
lblRecvNum->setMinimumSize(100, 20);
lblPortState->setMinimumSize(550, 20);
setNumOnLabel(lblSendNum, "S: ", sendNum);
setNumOnLabel(lblRecvNum, "R: ", recvNum);
// 從右往左依次添加
sBar->addPermanentWidget(lblPortState);
sBar->addPermanentWidget(lblSendNum);
sBar->addPermanentWidget(lblRecvNum);
// 定時發(fā)送-定時器
timSend = new QTimer;
timSend->setInterval(1000);// 設(shè)置默認(rèn)定時時長1000ms
connect(timSend, &QTimer::timeout, this, [=](){
on_sendBt_clicked();
});
}
MainWindow::~MainWindow()
{
delete ui;
}
//檢測通訊端口槽函數(shù)
void MainWindow::on_btnSerialCheck_clicked()
{
ui->serailCb->clear();
//通過QSerialPortInfo查找可用串口
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{
ui->serailCb->addItem(info.portName());
}
}
/*手動實現(xiàn)接收數(shù)據(jù)函數(shù)*/
void MainWindow::manual_serialPortReadyRead()
{
QByteArray recBuf = serialPort->readAll();;
QString str_rev;
// 接收字節(jié)計數(shù)
recvNum += recBuf.size();
// 狀態(tài)欄顯示計數(shù)值
setNumOnLabel(lblRecvNum, "R: ", recvNum);
if(ui->chk_rev_hex->checkState() == false){
if(ui->chk_rev_time->checkState() == Qt::Checked){
QDateTime nowtime = QDateTime::currentDateTime();
str_rev = "[" + nowtime.toString("yyyy-MM-dd hh:mm:ss") + "] ";
str_rev += QString(recBuf).append("\r\n");
}
else{
// 在當(dāng)前位置插入文本,不會發(fā)生換行。如果沒有移動光標(biāo)到文件結(jié)尾,會導(dǎo)致文件超出當(dāng)前界面顯示范圍,界面也不會向下滾動。
//ui->recvEdit->appendPlainText(buf);
if(ui->chk_rev_line->checkState() == Qt::Checked){
str_rev = QString(recBuf).append("\r\n");
}
else
{
str_rev = QString(recBuf);
}
}
}else{
// 16進(jìn)制顯示,并轉(zhuǎn)換為大寫
QString str1 = recBuf.toHex().toUpper();//.data();
// 添加空格
QString str2;
for(int i = 0; i<str1.length (); i+=2)
{
str2 += str1.mid (i,2);
str2 += " ";
}
if(ui->chk_rev_time->checkState() == Qt::Checked)
{
QDateTime nowtime = QDateTime::currentDateTime();
str_rev = "[" + nowtime.toString("yyyy-MM-dd hh:mm:ss") + "] ";
str_rev += str2.append("\r\n");
}
else
{
if(ui->chk_rev_line->checkState() == Qt::Checked)
str_rev += str2.append("\r\n");
else
str_rev = str2;
}
}
ui->recvEdit->insertPlainText(str_rev);
ui->recvEdit->moveCursor(QTextCursor::End);
}
/*打開串口*/
void MainWindow::on_openBt_clicked()
{
/*串口初始化*/
QSerialPort::BaudRate baudRate;
QSerialPort::DataBits dataBits;
QSerialPort::StopBits stopBits;
QSerialPort::Parity checkBits;
// 獲取串口波特率
// baudRate = ui->baundrateCb->currentText().toInt();直接字符串轉(zhuǎn)換為 int 的方法
if(ui->baundrateCb->currentText()=="1200")
baudRate=QSerialPort::Baud1200;
else if(ui->baundrateCb->currentText()=="2400")
baudRate=QSerialPort::Baud2400;
else if(ui->baundrateCb->currentText()=="4800")
baudRate=QSerialPort::Baud4800;
else if(ui->baundrateCb->currentText()=="9600")
baudRate=QSerialPort::Baud9600;
else if(ui->baundrateCb->currentText()=="19200")
baudRate=QSerialPort::Baud19200;
else if(ui->baundrateCb->currentText()=="38400")
baudRate=QSerialPort::Baud38400;
else if(ui->baundrateCb->currentText()=="57600")
baudRate=QSerialPort::Baud57600;
else if(ui->baundrateCb->currentText()=="115200")
baudRate=QSerialPort::Baud115200;
// 獲取串口數(shù)據(jù)位
if(ui->databitCb->currentText()=="5")
dataBits=QSerialPort::Data5;
else if(ui->databitCb->currentText()=="6")
dataBits=QSerialPort::Data6;
else if(ui->databitCb->currentText()=="7")
dataBits=QSerialPort::Data7;
else if(ui->databitCb->currentText()=="8")
dataBits=QSerialPort::Data8;
// 獲取串口停止位
if(ui->stopbitCb->currentText()=="1")
stopBits=QSerialPort::OneStop;
else if(ui->stopbitCb->currentText()=="1.5")
stopBits=QSerialPort::OneAndHalfStop;
else if(ui->stopbitCb->currentText()=="2")
stopBits=QSerialPort::TwoStop;
// 獲取串口奇偶校驗位
if(ui->checkbitCb->currentText() == "none"){
checkBits = QSerialPort::NoParity;
}else if(ui->checkbitCb->currentText() == "奇校驗"){
checkBits = QSerialPort::OddParity;
}else if(ui->checkbitCb->currentText() == "偶校驗"){
checkBits = QSerialPort::EvenParity;
}else{
}
// 初始化串口屬性,設(shè)置 端口號、波特率、數(shù)據(jù)位、停止位、奇偶校驗位數(shù)
serialPort->setPortName(ui->serailCb->currentText());
serialPort->setBaudRate(baudRate);
serialPort->setDataBits(dataBits);
serialPort->setStopBits(stopBits);
serialPort->setParity(checkBits);
// 根據(jù)初始化好的串口屬性,打開串口
// 如果打開成功,反轉(zhuǎn)打開按鈕顯示和功能。打開失敗,無變化,并且彈出錯誤對話框。
if(ui->openBt->text() == "打開串口"){
if(serialPort->open(QIODevice::ReadWrite) == true){
//QMessageBox::
ui->openBt->setText("關(guān)閉串口");
// 讓端口號下拉框不可選,避免誤操作(選擇功能不可用,控件背景為灰色)
ui->serailCb->setEnabled(false);
}else{
QMessageBox::critical(this, "錯誤提示", "串口打開失敗?。?!\r\n該串口可能被占用\r\n請選擇正確的串口");
}
//statusBar 狀態(tài)欄顯示端口狀態(tài)
QString sm = "%1 OPENED, %2, 8, NONE, 1";
QString status = sm.arg(serialPort->portName()).arg(serialPort->baudRate());
lblPortState->setText(status);
lblPortState->setStyleSheet("color:green");
}else{
serialPort->close();
ui->openBt->setText("打開串口");
// 端口號下拉框恢復(fù)可選,避免誤操作
ui->serailCb->setEnabled(true);
//statusBar 狀態(tài)欄顯示端口狀態(tài)
QString sm = "%1 CLOSED";
QString status = sm.arg(serialPort->portName());
lblPortState->setText(status);
lblPortState->setStyleSheet("color:red");
}
}
/*發(fā)送數(shù)據(jù)*/
void MainWindow::on_sendBt_clicked()
{
QByteArray array;
//Hex復(fù)選框
if(ui->chk_send_hex->checkState() == Qt::Checked){
//array = QString2Hex(data); //HEX 16進(jìn)制
array = QByteArray::fromHex(ui->sendEdit->toPlainText().toUtf8()).data();
}else{
//array = data.toLatin1(); //ASCII
array = ui->sendEdit->toPlainText().toLocal8Bit().data();
}
if(ui->chk_send_line->checkState() == Qt::Checked){
array.append("\r\n");
}
// 如發(fā)送成功,會返回發(fā)送的字節(jié)長度。失敗,返回-1。
int a = serialPort->write(array);
// 發(fā)送字節(jié)計數(shù)并顯示
if(a > 0)
{
// 發(fā)送字節(jié)計數(shù)
sendNum += a;
// 狀態(tài)欄顯示計數(shù)值
setNumOnLabel(lblSendNum, "S: ", sendNum);
}
}
// 狀態(tài)欄標(biāo)簽顯示計數(shù)值
void MainWindow::setNumOnLabel(QLabel *lbl, QString strS, long num)
{
// 標(biāo)簽顯示
QString strN;
strN.sprintf("%ld", num);
QString str = strS + strN;
lbl->setText(str);
}
/*清空*/
void MainWindow::on_clearBt_clicked()
{
ui->recvEdit->clear();
// 清除發(fā)送、接收字節(jié)計數(shù)
sendNum = 0;
recvNum = 0;
// 狀態(tài)欄顯示計數(shù)值
setNumOnLabel(lblSendNum, "S: ", sendNum);
setNumOnLabel(lblRecvNum, "R: ", recvNum);
}
void MainWindow::on_btnClearSend_clicked()
{
ui->sendEdit->clear();
// 清除發(fā)送字節(jié)計數(shù)
sendNum = 0;
// 狀態(tài)欄顯示計數(shù)值
setNumOnLabel(lblSendNum, "S: ", sendNum);
}
// 定時發(fā)送開關(guān) 選擇復(fù)選框
void MainWindow::on_chkTimSend_stateChanged(int arg1)
{
// 獲取復(fù)選框狀態(tài),未選為0,選中為2
if(arg1 == 0){
timSend->stop();
// 時間輸入框恢復(fù)可選
ui->txtSendMs->setEnabled(true);
}else{
// 對輸入的值做限幅,小于10ms會彈出對話框提示
if(ui->txtSendMs->text().toInt() >= 10){
timSend->start(ui->txtSendMs->text().toInt());// 設(shè)置定時時長,重新計數(shù)
// 讓時間輸入框不可選,避免誤操作(輸入功能不可用,控件背景為灰色)
ui->txtSendMs->setEnabled(false);
}else{
ui->chkTimSend->setCheckState(Qt::Unchecked);
QMessageBox::critical(this, "錯誤提示", "定時發(fā)送的最小間隔為 10ms\r\n請確保輸入的值 >=10");
}
}
}
如果一切順利的話,點擊左下角的三角符號進(jìn)行編譯運行就可以測試效果了。
打包可執(zhí)行文件
當(dāng)經(jīng)過上面的步驟,編譯后能成功運行。這時如果希望像我們平時用到的各種免安裝的exe工具一樣可以分享給其他小伙伴使用,就需要再打包一下才行。
傳送門:QT如何打包生成獨立可執(zhí)行.exe文件
遺留問題:
本來想定義一個定時器,然后在定時器觸發(fā)的槽函數(shù)中進(jìn)行掃描端口以達(dá)到自動更新端口的效果,就不需要在新插入串口設(shè)備時要點擊一次檢測串口的按鈕。但如果槽函數(shù)中每次都需要先清除之前檢測到的端口,再重新掃描。實際運行時就會出現(xiàn)剛想點開端口下拉列表還沒選就刷新成COM1了,會干擾選擇端口的操作。嘗試了一番不能解決只能作罷。
定時器的槽函數(shù)類似下面這樣(這是一個結(jié)果有問題的示范):
void MainWindow::slot_timCheckPort()
{
if(ui->openBt->text() == "打開串口"){
ui->serailCb->clear();
//通過QSerialPortInfo查找可用串口
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{
ui->serailCb->addItem(info.portName());
}
}
}
相關(guān)好文推薦:
https://blog.csdn.net/hanhui22/article/details/111594742
https://blog.csdn.net/weixin_46183891/article/details/124368488
https://blog.csdn.net/qq_30255657/article/details/125247114
https://blog.csdn.net/zzssdd2/category_10730183.html文章來源:http://www.zghlxwxcb.cn/news/detail-454643.html
https://blog.csdn.net/Mark_md/article/details/108928314文章來源地址http://www.zghlxwxcb.cn/news/detail-454643.html
到了這里,關(guān)于QT初體驗:手把手帶你寫一個自己的串口助手的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!