我们知道在Qt中多线程的实现通常有两种方法,
1.自定义的类继承字QThread,并重新实现父类中的虚函数run
2.利用moveToThread
先来看第一种,定义MyThread类继承字QThread,重新实现run函数
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
#include <QDebug>
#include <QThread>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
class MyThread : public QThread
Q_OBJECT
public:
MyThread(QString *portName);
void run();
public slots:
void read_serial_data();
private:
QSerialPort *my_serialPort_thread;
QByteArray MyByteArray_thread;
QByteArray DataToAnalysis_thread;
public:
QString port_Name;
#endif
在MyThread.cpp中
#include "mythread.h"
#include <QDebug>
#include <QMessageBox>
#include <QByteArray>
#include <QEventLoop>
MyThread::MyThread(QString *portName){
port_Name=*portName;
void MyThread::run(){
my_serialPort_thread = new QSerialPort();
if (my_serialPort_thread->isOpen()) {
qDebug("COM already open");
return;
my_serialPort_thread->setPortName(port_Name);
my_serialPort_thread->setBaudRate(115200);
my_serialPort_thread->setDataBits(QSerialPort::Data8);
my_serialPort_thread->setParity(QSerialPort::NoParity);
my_serialPort_thread->setFlowControl( QSerialPort::NoFlowControl );
my_serialPort_thread->setStopBits(QSerialPort::OneStop);
bool flag = my_serialPort_thread->open( QIODevice::ReadWrite );
if(flag==false) {
qDebug("Uart not exist or being occupied");
return;
}else {
my_serialPort_thread->setDataTerminalReady(true);
qDebug() << my_serialPort_thread->portName() + " is open";
connect(my_serialPort_thread,SIGNAL(readyRead()),this,SLOT(read_serial_data()));
QEventLoop eventLoop;
eventLoop.exec();
void MyThread::read_serial_data()
qDebug()<<"SerialReadThread:" <<currentThreadId();
QString strDisplay_thread;
QByteArray byte_data = my_serialPort_thread->readAll();
if(!byte_data.isEmpty())
MyByteArray_thread.append(byte_data);
这里需要注意的是在run中开始事件循环,否则默认不开启,运行完run中的代码,此线程就结束了,开始事件循环才能在槽中接收到字节。然而脱离的run函数的串口读取槽,并没有在新的线程中运行,而只有run函数中的代码在新线程中等待事件循环。可以通过线程ID查看,这里就不看了。
再来看第二种,利用moveToThread
int main(int argc, char *argv[])
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QThread mySerialPortThread;
MySerialPort *myport = new MySerialPort();
myport->moveToThread(&mySerialPortThread);
mySerialPortThread.start();
MAVLinkProtocol *myMavlinkProtocol = new MAVLinkProtocol();
QObject::connect(myport, &MySerialPort::bytesReceived,myMavlinkProtocol, &MAVLinkProtocol::receiveBytes);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("MySerialPort",myport);
engine.rootContext()->setContextProperty("MavlinkProtocol",myMavlinkProtocol);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
运行后报如下错误
哎,因为要再Qml中访问C++类的属性,将该实例注册到了Qml的上下文,在将该实例moveToThread到另外一个线程,这个C++类和Qml引擎不再同一个线程里……醉了,Qml中这种方法也不能使用……吐血
还好天无绝人之路,CSDN找到了一个CopyFile的例程,给我了启发,再写一个串口和Qml交互的中间类不就可以了吗?接着撸代码吧,这次讲解不细了,直接贴代码吧,只要搞过串口的应该都看的懂
myserialport.h //这里不再有再Qml中需要访问的属性和方法,并定义了一个Timer检查串口设备变化
#ifndef MYSERIALPORT_H
#define MYSERIALPORT_H
#include <QObject>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
#include <QDebug>
#include <QTimer>
#include <QThread>
class MySerialPort : public QObject
Q_OBJECT
public:
explicit MySerialPort(QObject *parent = nullptr);
QStringList getMyPortName(){ return myPortName; }
bool getLinkStatus(){return myLinkStatus;}
signals:
void bytesReceived(QByteArray data);
void linkStatusChanged(bool linkStatus);
void portNameReady(QStringList portName);
public slots:
void serialPortOperate(int portNameIndex,int baudRateIndex,int stopbitsIndex,int databitsIndex,int parityIndex , int flowCtrlIndex);
void readMyCom();
void checkMySerialPortEvent();
private:
QList<QSerialPortInfo> availablePorts() ;
static bool isSystemPort(QSerialPortInfo *port);
private:
QSerialPort *mySerialPort;
QList<QSerialPortInfo> mySerialPortInfoList;
QStringList myPortName;
bool myLinkStatus=false;
QTimer *mySerialPortCheckTimer;
#endif
myserialport.cpp
#include "myserialport.h"
MySerialPort::MySerialPort(QObject *parent) : QObject(parent)
mySerialPortInfoList = availablePorts();
mySerialPortCheckTimer= new QTimer(this);
connect(mySerialPortCheckTimer, SIGNAL(timeout()), this, SLOT(checkMySerialPortEvent()));
mySerialPortCheckTimer->start(1000);
void MySerialPort::serialPortOperate(int portNameIndex, int baudRateIndex, int stopbitsIndex, int databitsIndex, int parityIndex, int flowCtrlIndex)
if(myLinkStatus==false)
mySerialPort = new QSerialPort();
if (mySerialPort->isOpen()){
qDebug("COM already open");
return;
mySerialPort->setPortName(myPortName[portNameIndex]);
mySerialPort->setBaudRate(115200);
mySerialPort->setDataBits(QSerialPort::Data8);
mySerialPort->setParity(QSerialPort::NoParity);
mySerialPort->setFlowControl( QSerialPort::NoFlowControl );
mySerialPort->setStopBits(QSerialPort::OneStop);
myLinkStatus = mySerialPort->open(QIODevice::ReadWrite);
if(myLinkStatus){
mySerialPort->setDataTerminalReady(true);
qDebug() << mySerialPort->portName() + " is open";
emit linkStatusChanged(myLinkStatus);
else {
qDebug("Uart not exist or being occupied");
return;
} QObject::connect(mySerialPort,SIGNAL(readyRead()),this,SLOT(readMyCom()),Qt::QueuedConnection);
else
if((mySerialPort->isOpen())){
qDebug() << mySerialPort->portName() + " is close";
mySerialPort->clear();
mySerialPort->close();
mySerialPort->deleteLater();
myLinkStatus=false;
emit linkStatusChanged(myLinkStatus);
void MySerialPort::readMyCom(){
qint64 byteCount = mySerialPort->bytesAvailable();
if (byteCount) {
QByteArray buffer;
buffer.resize(byteCount);
mySerialPort->read(buffer.data(), buffer.size());
emit bytesReceived(buffer);
else qWarning() << "Serial port not readable";
void MySerialPort::checkMySerialPortEvent()
if(myLinkStatus==false){
availablePorts();
}else return;
QList<QSerialPortInfo> MySerialPort::availablePorts()
QList<QSerialPortInfo> list;
QStringList portNameLocal;
foreach(QSerialPortInfo portInfo, QSerialPortInfo::availablePorts()) {
if (!isSystemPort(&portInfo)) {
list << *((QSerialPortInfo*)&portInfo);
portNameLocal<<(portInfo.portName());
myPortName = portNameLocal;
emit portNameReady(myPortName);
return list;
bool MySerialPort::isSystemPort(QSerialPortInfo *port)
if (port->systemLocation().contains("tty.MALS")
|| port->systemLocation().contains("tty.SOC")
|| port->systemLocation().contains("tty.Bluetooth-Incoming-Port")
|| port->systemLocation().contains("tty.usbserial")
|| port->systemLocation().contains("tty.usbmodem")) {
return true;
return false;
myserialportqml.h //和Qml交互的类
#ifndef MYSERIALPORTQML_H
#define MYSERIALPORTQML_H
#include <QObject>
#include "myserialport.h"
#include <QThread>
class MySerialPortQml : public QObject
Q_OBJECT
Q_PROPERTY(QStringList portNameQml READ portNameQml NOTIFY portNameQmlChanged)
public:
explicit MySerialPortQml(QObject *parent = nullptr);
QStringList portNameQml()const{return myPortNameQml; }
Q_INVOKABLE void getSerialPortQmlIndex(int portNameIndex,int baudRateIndex,int stopbitsIndex,int databitsIndex,int parityIndex , int flowCtrlIndex);
signals:
void portNameQmlChanged(QStringList);
void portLinkStatusQmlChanged(bool linkStatus);
void setSerialPortQmlIndex(int portNameIndex,int baudRateIndex,int stopbitsIndex,int databitsIndex,int parityIndex , int flowCtrlIndex);
public slots:
void getPortName(QStringList portName);
void getSerialPortQmlLinkStatus(bool status);
public:
MySerialPort *myPortQml;
private:
QStringList myPortNameQml;
bool myPortQmlLinkStatus;
#endif
myserialportqml.cpp文件
#include "myserialportqml.h"
MySerialPortQml::MySerialPortQml(QObject *parent) : QObject(parent)
myPortQml = new MySerialPort();
myPortNameQml = myPortQml->getMyPortName();
QThread * childThread = new QThread();
myPortQml->moveToThread(childThread);
QObject::connect(childThread, &QThread::finished, childThread, &QObject::deleteLater);
childThread->start();
QObject::connect(myPortQml,SIGNAL(portNameReady(QStringList)),this,SLOT(getPortName(QStringList)));
QObject::connect(this,SIGNAL(setSerialPortQmlIndex(int,int,int,int,int,int)),myPortQml,SLOT(serialPortOperate(int,int,int,int,int,int)));
QObject::connect(myPortQml,SIGNAL(linkStatusChanged(bool)),this,SLOT(getSerialPortQmlLinkStatus(bool)));
void MySerialPortQml::getSerialPortQmlIndex(int portNameIndex, int baudRateIndex, int stopbitsIndex, int databitsIndex, int parityIndex, int flowCtrlIndex)
emit setSerialPortQmlIndex(portNameIndex,baudRateIndex,stopbitsIndex,databitsIndex,parityIndex,flowCtrlIndex);
void MySerialPortQml::getPortName(QStringList portName)
myPortNameQml=portName;
emit portNameQmlChanged(myPortNameQml);
void MySerialPortQml::getSerialPortQmlLinkStatus(bool status)
myPortQmlLinkStatus=status;
emit portLinkStatusQmlChanged(myPortQmlLinkStatus);
qml类就不贴了,基本和前面一样的。现在我觉得对于Qml中多线程的实现稍微麻烦一些,也许单纯依靠号信号和槽完成前端Qml和C++交互是更简单的方法,但我目前不想尝试了,主要是花在写博客的时间有点多了……
如果您觉得本文对您有些许帮助,可以小小打赏一下作者哦
text: model.fileName
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: model.indentation * 10
Image {
source: model.isFolder ? "folder.png" : "file.png"
width: 16
height: 16
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 5
MouseArea {
anchors.fill: parent
onClicked: {
if (model.isFolder) {
model.collapsed = !model.collapsed;
} else {
selectedFilePath = model.filePath;
// 定义 ListView,用于展示文件列表
ListView {
id: fileListView
width: 200
height: parent.height
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
spacing: 5
visible: folderPath !== ""
model: folderModel
delegate: listItemDelegate
// 定义纯文本编辑框,用于打开和编辑文件
TextArea {
id: textArea
width: parent.width - fileListView.width
height: parent.height
anchors.left: fileListView.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
readOnly: folderPath === "" || selectedFilePath === ""
text: selectedFilePath !== "" ? Qt.rgba(0, 0, 0, 1).toHex() + "\n" + Qt.resolvedUrl(selectedFilePath).toLocalFile() : ""
font.family: "Courier"
wrapMode: TextArea.WrapAnywhere
// 定义打开文件夹的按钮,点击后打开文件夹选择框
Button {
text: "打开文件夹"
anchors.left: parent.left
anchors.top: parent.top
onClicked: {
folderPath = Qt.fileDialog.getExistingDirectory(parent, "选择文件夹", folderPath);
在上述代码中,我们使用了 `FolderListModel` 获取指定文件夹下的文件夹和 `.md` 文件,并在 `ListView` 中展示了这些文件。同时,在自定义的 `ListItem` 组件中添加了 `onClicked` 事件处理函数,实现了双击文件夹展开和收起节点的功能,以及双击 `.md` 文件打开文件的功能。在纯文本编辑框中,我们使用了 `TextArea` 组件来打开和编辑文件。