文章的目的为了记录使用QT QML开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 C++ QT QML 开发(四)复杂控件--Listview
开源 C++ QT QML 开发(五)复杂控件--Gridview
开源 C++ QT QML 开发(十一)通讯--TCP服务器端
开源 C++ QT QML 开发(十二)通讯--TCP客户端
推荐链接:
开源 C# 快速开发(十六)数据库--sqlserver增删改查
本章节主要内容是:实现了一个串口调试工具,可以对串口参数进行配置,可以接收和发送文本和十六进制数据。
1.代码分析
2.所有源码
3.效果演示
一、代码分析
- qml代码分析
ApplicationWindow 根元素
ApplicationWindow {
id: window
width: 1000
height: 800
title: "串口调试工具 - Qt 5.12"
visible: true
创建主应用程序窗口
设置固定尺寸 1000x800
定义窗口标题和可见性
颜色主题系统
// 定义颜色主题
property color primaryColor: "#3498db" // 主色调 - 蓝色
property color secondaryColor: "#2ecc71" // 次要色调 - 绿色
property color accentColor: "#e74c3c" // 强调色 - 红色
property color backgroundColor: "#f8f9fa" // 背景色
property color cardColor: "#ffffff" // 卡片背景色
property color textColor: "#2c3e50" // 主要文字颜色
property color subTextColor: "#7f8c8d" // 次要文字颜色
property color borderColor: "#bdc3c7" // 边框颜色
property color successColor: "#27ae60" // 成功颜色
property color warningColor: "#f39c12" // 警告颜色
property color errorColor: "#e74c3c" // 错误颜色
统一的设计系统,便于维护和修改
语义化颜色命名,提高代码可读性
背景设置
Rectangle {
anchors.fill: parent
color: backgroundColor
}
设置整个窗口的背景颜色
anchors.fill: parent 确保背景填充整个窗口
-
串口管理器实例化
SerialPortManager {
id: serialPort
}
创建 C++ SerialPortManager 类的 QML 实例
id: serialPort 用于在 QML 中引用该对象
- 主布局结构
ColumnLayout 主容器
ColumnLayout {
anchors.fill: parent
anchors.margins: 15
spacing: 12
垂直布局管理器,包含所有界面元素
anchors.fill: parent 填充整个窗口
设置边距和组件间距
- 串口配置区域详细分析
GroupBox 容器
GroupBox {
title: "📡 串口配置"
Layout.fillWidth: true
background: Rectangle {
color: cardColor
border.color: borderColor
border.width: 1
radius: 8
}
label: Label {
text: parent.title
color: primaryColor
font.bold: true
font.pixelSize: 14
padding: 5
}
创建分组框,包含所有串口配置控件
自定义背景和边框样式
自定义标题标签样式
GridLayout 网格布局
GridLayout {
columns: 6 // 6列网格
width: parent.width
rowSpacing: 8
columnSpacing: 8
6列网格布局,整齐排列配置控件
设置行间距和列间距
端口选择 ComboBox
ComboBox {
id: portComboBox
Layout.fillWidth: true
model: serialPort.portList // 绑定到C++的端口列表
background: Rectangle {
color: cardColor
border.color: borderColor
border.width: 1
radius: 4
}
onModelChanged: {
if (model.length > 0 && currentIndex === -1) {
currentIndex = 0 // 自动选择第一个可用端口
}
}
}
动态绑定到 C++ 的端口列表
自动选择第一个可用端口
自定义样式参数选择 ComboBox 组
// 波特率选择
ComboBox {
id: baudRateComboBox
model: ["1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"]
currentIndex: 3 // 默认选择9600
}
// 校验位选择(包含值映射)
ComboBox {
id: parityComboBox
model: ["无", "奇校验", "偶校验", "标记", "空格"]
property var parityValues: [0, 3, 2, 1, 4] // 映射到C++枚举值
}
预定义常用参数选项
使用属性存储枚举值映射
设置合理的默认值
连接按钮
Button {
id: connectButton
text: serialPort.isConnected ? "🔌 断开连接" : "🔗 连接"
Layout.columnSpan: 2
Layout.fillWidth: true
background: Rectangle {
color: serialPort.isConnected ? accentColor : secondaryColor
radius: 6
}
onClicked: {
if (serialPort.isConnected) {
serialPort.disconnectSerialPort()
} else {
if (portComboBox.currentText) {
serialPort.connectSerialPort(
portComboBox.currentText,
parseInt(baudRateComboBox.currentText),
parseInt(dataBitsComboBox.currentText),
parityComboBox.parityValues[parityComboBox.currentIndex],
stopBitsComboBox.stopBitsValues[stopBitsComboBox.currentIndex],
flowControlComboBox.flowControlValues[flowControlComboBox.currentIndex]
)
}
}
}
}
动态文本:根据连接状态改变按钮文字
动态颜色:连接/断开状态使用不同颜色
参数传递:收集所有配置参数传递给 C++ 函数
空值检查:确保选择了端口
- 发送数据区域
模式选择 CheckBox
CheckBox {
id: sendHexCheckBox
text: "十六进制发送"
contentItem: Text {
text: sendHexCheckBox.text
color: textColor
font.bold: true
}
}
控制发送数据的格式(文本/十六进制)
自定义文本样式
发送文本区域
ScrollView {
Layout.fillWidth: true
Layout.preferredHeight: 80
background: Rectangle {
color: cardColor
border.color: borderColor
border.width: 1
radius: 4
}
TextArea {
id: sendTextArea
placeholderText: "请输入要发送的数据..."
placeholderTextColor: subTextColor
wrapMode: TextEdit.Wrap
color: textColor
}
}
可滚动的文本输入区域
设置占位符文本
支持自动换行
发送控制按钮组
RowLayout {
Button {
text: "🚀 发送"
onClicked: {
serialPort.sendData(sendTextArea.text, sendHexCheckBox.checked)
}
}
Button {
text: "🗑️ 清空发送"
onClicked: sendTextArea.clear()
}
}
发送按钮:传递文本内容和格式模式
清空按钮:清除输入框内容
-
状态显示
Label {
text: "状态: " + serialPort.statusMessage
color: {
if (serialPort.isConnected) return successColor
else if (serialPort.statusMessage.includes("错误") ||
serialPort.statusMessage.includes("失败")) return errorColor
else return textColor
}
}
动态状态文本:显示当前操作状态
智能颜色切换:
连接成功:绿色
错误/失败:红色
其他状态:默认颜色
- 接收数据区域
接收显示区域
ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
background: Rectangle {
color: "#2c3e50" // 深色背景便于阅读
border.color: borderColor
border.width: 1
radius: 4
}
TextArea {
id: receiveTextArea
text: serialPort.receivedData // 绑定到C++接收数据
wrapMode: TextEdit.Wrap
readOnly: true
font.family: "Consolas, 'Courier New', monospace" // 等宽字体
font.pixelSize: 12
selectByMouse: true // 允许选择文本
color: "#ecf0f1" // 浅色文字
}
}
数据绑定:自动更新显示接收到的数据
等宽字体:便于对齐和阅读
深色主题:减少长时间使用的视觉疲劳
文本选择:允许用户复制接收到的数据
接收统计和控制
RowLayout {
Label {
text: "📊 接收字节数: " + receiveTextArea.text.length
}
CheckBox {
id: autoScrollCheckBox
text: "自动滚动"
checked: true // 默认开启自动滚动
}
Button {
text: "💾 保存数据"
onClicked: {
console.log("保存数据功能待实现")
}
}
}
字节统计:实时显示接收数据长度
自动滚动:控制是否自动滚动到最新内容
保存功能:预留数据保存接口
-
状态栏
footer: ToolBar {
background: Rectangle {
color: primaryColor
}
RowLayout {
anchors.fill: parent
Label {
text: "🔧 串口调试工具 v1.0 | 就绪"
color: "white"
font.bold: true
}
Item { Layout.fillWidth: true } // 占位空间
Label {
text: new Date().toLocaleString(Qt.locale(), "yyyy-MM-dd hh:mm:ss")
color: "white"
}
}
}
应用信息:显示版本和状态
实时时钟:显示当前时间
主色调背景:与整体设计保持一致
- 数据绑定和交互机制
属性绑定
model: serialPort.portList // 自动更新串口列表
text: serialPort.receivedData // 自动更新接收数据
checked: serialPort.isConnected // 自动更新连接状态
条件渲染
text: serialPort.isConnected ? "断开连接" : "连接"
color: serialPort.isConnected ? accentColor : secondaryColor
事件处理
onClicked: { /* 处理点击 */ }
onModelChanged: { /* 响应模型变化 */ }
SerialPortManager.cpp分析
-
构造函数 SerialPortManager::SerialPortManager
SerialPortManager::SerialPortManager(QObject *parent)
: QObject(parent)
, m_serialPort(new QSerialPort(this)) // 创建串口对象
, m_hexDisplay(false) // 初始化显示模式为文本
{
// 初始化定时器,定时刷新串口列表
m_portRefreshTimer = new QTimer(this);
connect(m_portRefreshTimer, &QTimer::timeout, this, &SerialPortManager::refreshPorts);
m_portRefreshTimer->start(2000); // 每2秒刷新一次// 连接串口信号到槽函数 connect(m_serialPort, &QSerialPort::readyRead, this, &SerialPortManager::onReadyRead); connect(m_serialPort, &QSerialPort::errorOccurred, this, &SerialPortManager::onErrorOccurred); refreshPorts(); // 初始刷新串口列表
}
功能:初始化所有成员变量,建立信号槽连接,启动定时刷新。
- 属性读取函数
portList()
QStringList SerialPortManager::portList() const
{
return m_portList; // 返回当前串口列表
}
isConnected()
bool SerialPortManager::isConnected() const
{
return m_serialPort->isOpen(); // 返回串口打开状态
}
receivedData()
QString SerialPortManager::receivedData() const
{
return m_receivedData; // 返回接收到的数据
}
statusMessage()
QString SerialPortManager::statusMessage() const
{
return m_statusMessage; // 返回状态消息
}
- 核心功能函数
refreshPorts() - 刷新串口列表
void SerialPortManager::refreshPorts()
{
QStringList newPortList;
const auto infos = QSerialPortInfo::availablePorts(); // 获取系统可用串口
for (const QSerialPortInfo &info : infos) {
newPortList << info.portName(); // 提取端口名
}
// 只有列表发生变化时才更新并发射信号
if (newPortList != m_portList) {
m_portList = newPortList;
emit portListChanged(); // 通知QML更新
}
}
connectSerialPort() - 连接串口
bool SerialPortManager::connectSerialPort(const QString &portName, int baudRate, int dataBits,
int parity, int stopBits, int flowControl)
{
if (m_serialPort->isOpen()) {
m_serialPort->close(); // 如果已连接,先关闭
}
// 设置串口参数
m_serialPort->setPortName(portName);
m_serialPort->setBaudRate(static_cast<QSerialPort::BaudRate>(baudRate));
m_serialPort->setDataBits(static_cast<QSerialPort::DataBits>(dataBits));
m_serialPort->setParity(static_cast<QSerialPort::Parity>(parity));
m_serialPort->setStopBits(static_cast<QSerialPort::StopBits>(stopBits));
m_serialPort->setFlowControl(static_cast<QSerialPort::FlowControl>(flowControl));
// 尝试以读写模式打开串口
if (m_serialPort->open(QIODevice::ReadWrite)) {
m_statusMessage = QString("已连接到 %1").arg(portName);
emit statusMessageChanged();
emit connectionChanged();
return true;
} else {
m_statusMessage = QString("连接失败: %1").arg(m_serialPort->errorString());
emit statusMessageChanged();
return false;
}
}
disconnectSerialPort() - 断开连接
void SerialPortManager::disconnectSerialPort()
{
if (m_serialPort->isOpen()) {
m_serialPort->close(); // 关闭串口
m_statusMessage = "串口已断开";
emit statusMessageChanged();
emit connectionChanged(); // 通知连接状态改变
}
}
sendData() - 发送数据
void SerialPortManager::sendData(const QString &data, bool hexMode)
{
if (!m_serialPort->isOpen()) {
m_statusMessage = "串口未连接";
emit statusMessageChanged();
return;
}
QByteArray sendArray;
if (hexMode) {
// 十六进制发送模式
QString cleanData = data.trimmed();
cleanData.remove(' '); // 移除空格
// 验证十六进制数据长度
if (cleanData.length() % 2 != 0) {
m_statusMessage = "十六进制数据长度必须为偶数";
emit statusMessageChanged();
return;
}
// 逐字节转换十六进制字符串为字节数据
for (int i = 0; i < cleanData.length(); i += 2) {
bool ok;
QString byteStr = cleanData.mid(i, 2);
char byte = static_cast<char>(byteStr.toInt(&ok, 16));
if (ok) {
sendArray.append(byte);
} else {
m_statusMessage = "十六进制数据格式错误";
emit statusMessageChanged();
return;
}
}
} else {
// 文本发送模式
sendArray = data.toUtf8(); // 转换为UTF-8编码
}
// 发送数据并检查结果
qint64 bytesWritten = m_serialPort->write(sendArray);
if (bytesWritten == -1) {
m_statusMessage = "发送失败";
emit statusMessageChanged();
} else {
m_statusMessage = QString("已发送 %1 字节").arg(bytesWritten);
emit statusMessageChanged();
}
}
clearReceivedData() - 清空接收数据
void SerialPortManager::clearReceivedData()
{
m_receivedData.clear(); // 清空数据缓冲区
emit receivedDataChanged(); // 通知QML更新显示
}
- 槽函数
onReadyRead() - 数据接收处理
void SerialPortManager::onReadyRead()
{
QByteArray data = m_serialPort->readAll(); // 读取所有可用数据
// 问题:这里硬编码为false,应该从QML获取设置
bool hexDisplay = false;
if (hexDisplay) {
// 十六进制显示模式
QString hexString;
for (char byte : data) {
// 每个字节格式化为两位十六进制数,用空格分隔
hexString += QString("%1 ").arg(static_cast<quint8>(byte), 2, 16, QLatin1Char('0')).toUpper();
}
m_receivedData += hexString;
} else {
// 文本显示模式
QString text;
for (char byte : data) {
if (byte >= 32 && byte <= 126) {
// 可打印字符直接显示
text += QChar(byte);
} else if (byte == '\r' || byte == '\n' || byte == '\t') {
// 特殊控制字符显示
text += QChar(byte);
} else {
// 不可见字符显示为十六进制格式
text += QString("[%1]").arg(static_cast<quint8>(byte), 2, 16, QLatin1Char('0')).toUpper();
}
}
m_receivedData += text;
}
emit receivedDataChanged(); // 通知数据更新
}
onErrorOccurred() - 错误处理
void SerialPortManager::onErrorOccurred(QSerialPort::SerialPortError error)
{
if (error != QSerialPort::NoError) {
// 生成错误信息
m_statusMessage = QString("串口错误: %1").arg(m_serialPort->errorString());
emit statusMessageChanged();
// 如果串口处于打开状态,关闭它
if (m_serialPort->isOpen()) {
m_serialPort->close();
emit connectionChanged(); // 通知连接状态改变
}
}
}
-
主函数 main()
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);// 注册C++类到QML系统 qmlRegisterType<SerialPortManager>("SerialPort", 1, 0, "SerialPortManager"); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); if (engine.rootObjects().isEmpty()) return -1; return app.exec();
}
二、所有源码
SerialPortManager.h文件源码
#ifndef SERIALPORTMANAGER_H
#define SERIALPORTMANAGER_H
#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QTimer>
#include <QDebug>
class SerialPortManager : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList portList READ portList NOTIFY portListChanged)
Q_PROPERTY(bool isConnected READ isConnected NOTIFY connectionChanged)
Q_PROPERTY(QString receivedData READ receivedData NOTIFY receivedDataChanged)
Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY statusMessageChanged)
public:
explicit SerialPortManager(QObject *parent = nullptr);
QStringList portList() const;
bool isConnected() const;
QString receivedData() const;
QString statusMessage() const;
Q_INVOKABLE void refreshPorts();
Q_INVOKABLE bool connectSerialPort(const QString &portName, int baudRate, int dataBits,
int parity, int stopBits, int flowControl);
Q_INVOKABLE void disconnectSerialPort();
Q_INVOKABLE void sendData(const QString &data, bool hexMode);
Q_INVOKABLE void clearReceivedData();
signals: // 添加signals关键字
void portListChanged();
void connectionChanged();
void receivedDataChanged();
void statusMessageChanged();
private slots:
void onReadyRead();
void onErrorOccurred(QSerialPort::SerialPortError error);
private:
QSerialPort *m_serialPort;
QStringList m_portList;
QString m_receivedData;
QString m_statusMessage;
bool m_hexDisplay;
QTimer *m_portRefreshTimer;
};
#endif // SERIALPORTMANAGER_H
SerialPortManager.cpp文件源码
#include "SerialPortManager.h"
SerialPortManager::SerialPortManager(QObject *parent)
: QObject(parent)
, m_serialPort(new QSerialPort(this))
, m_hexDisplay(false)
{
// 初始化定时器,定时刷新串口列表
m_portRefreshTimer = new QTimer(this);
connect(m_portRefreshTimer, &QTimer::timeout, this, &SerialPortManager::refreshPorts);
m_portRefreshTimer->start(2000); // 每2秒刷新一次
// 连接串口信号
connect(m_serialPort, &QSerialPort::readyRead, this, &SerialPortManager::onReadyRead);
connect(m_serialPort, &QSerialPort::errorOccurred, this, &SerialPortManager::onErrorOccurred);
refreshPorts();
}
QStringList SerialPortManager::portList() const
{
return m_portList;
}
bool SerialPortManager::isConnected() const
{
return m_serialPort->isOpen();
}
QString SerialPortManager::receivedData() const
{
return m_receivedData;
}
QString SerialPortManager::statusMessage() const
{
return m_statusMessage;
}
void SerialPortManager::refreshPorts()
{
QStringList newPortList;
const auto infos = QSerialPortInfo::availablePorts();
for (const QSerialPortInfo &info : infos) {
newPortList << info.portName();
}
if (newPortList != m_portList) {
m_portList = newPortList;
emit portListChanged();
}
}
bool SerialPortManager::connectSerialPort(const QString &portName, int baudRate, int dataBits,
int parity, int stopBits, int flowControl)
{
if (m_serialPort->isOpen()) {
m_serialPort->close();
}
m_serialPort->setPortName(portName);
m_serialPort->setBaudRate(static_cast<QSerialPort::BaudRate>(baudRate));
m_serialPort->setDataBits(static_cast<QSerialPort::DataBits>(dataBits));
m_serialPort->setParity(static_cast<QSerialPort::Parity>(parity));
m_serialPort->setStopBits(static_cast<QSerialPort::StopBits>(stopBits));
m_serialPort->setFlowControl(static_cast<QSerialPort::FlowControl>(flowControl));
if (m_serialPort->open(QIODevice::ReadWrite)) {
m_statusMessage = QString("已连接到 %1").arg(portName);
emit statusMessageChanged();
emit connectionChanged();
return true;
} else {
m_statusMessage = QString("连接失败: %1").arg(m_serialPort->errorString());
emit statusMessageChanged();
return false;
}
}
void SerialPortManager::disconnectSerialPort()
{
if (m_serialPort->isOpen()) {
m_serialPort->close();
m_statusMessage = "串口已断开";
emit statusMessageChanged();
emit connectionChanged();
}
}
void SerialPortManager::sendData(const QString &data, bool hexMode)
{
if (!m_serialPort->isOpen()) {
m_statusMessage = "串口未连接";
emit statusMessageChanged();
return;
}
QByteArray sendArray;
if (hexMode) {
// 十六进制发送
QString cleanData = data.trimmed();
cleanData.remove(' ');
if (cleanData.length() % 2 != 0) {
m_statusMessage = "十六进制数据长度必须为偶数";
emit statusMessageChanged();
return;
}
for (int i = 0; i < cleanData.length(); i += 2) {
bool ok;
QString byteStr = cleanData.mid(i, 2);
char byte = static_cast<char>(byteStr.toInt(&ok, 16));
if (ok) {
sendArray.append(byte);
} else {
m_statusMessage = "十六进制数据格式错误";
emit statusMessageChanged();
return;
}
}
} else {
// 文本发送
sendArray = data.toUtf8();
}
qint64 bytesWritten = m_serialPort->write(sendArray);
if (bytesWritten == -1) {
m_statusMessage = "发送失败";
emit statusMessageChanged();
} else {
m_statusMessage = QString("已发送 %1 字节").arg(bytesWritten);
emit statusMessageChanged();
}
}
void SerialPortManager::clearReceivedData()
{
m_receivedData.clear();
emit receivedDataChanged();
}
void SerialPortManager::onReadyRead()
{
QByteArray data = m_serialPort->readAll();
// 临时设置十六进制显示,实际应该从QML传递这个设置
bool hexDisplay = false; // 这里需要从QML获取设置
if (hexDisplay) {
// 十六进制显示
QString hexString;
for (char byte : data) {
hexString += QString("%1 ").arg(static_cast<quint8>(byte), 2, 16, QLatin1Char('0')).toUpper();
}
m_receivedData += hexString;
} else {
// 文本显示
// 过滤不可见字符,保留可打印字符
QString text;
for (char byte : data) {
if (byte >= 32 && byte <= 126) {
text += QChar(byte);
} else if (byte == '\r' || byte == '\n' || byte == '\t') {
text += QChar(byte);
} else {
text += QString("[%1]").arg(static_cast<quint8>(byte), 2, 16, QLatin1Char('0')).toUpper();
}
}
m_receivedData += text;
}
emit receivedDataChanged();
}
void SerialPortManager::onErrorOccurred(QSerialPort::SerialPortError error)
{
if (error != QSerialPort::NoError) {
m_statusMessage = QString("串口错误: %1").arg(m_serialPort->errorString());
emit statusMessageChanged();
if (m_serialPort->isOpen()) {
m_serialPort->close();
emit connectionChanged();
}
}
}
main.cpp文件源码
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "SerialPortManager.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
// 注册C++类到QML
qmlRegisterType<SerialPortManager>("SerialPort", 1, 0, "SerialPortManager");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml文件源码
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import SerialPort 1.0
ApplicationWindow {
id: window
width: 1000
height: 800
title: "串口调试工具"
visible: true
// 定义颜色主题
property color primaryColor: "#3498db" // 主色调 - 蓝色
property color secondaryColor: "#2ecc71" // 次要色调 - 绿色
property color accentColor: "#e74c3c" // 强调色 - 红色
property color backgroundColor: "#f8f9fa" // 背景色
property color cardColor: "#ffffff" // 卡片背景色
property color textColor: "#2c3e50" // 主要文字颜色
property color subTextColor: "#7f8c8d" // 次要文字颜色
property color borderColor: "#bdc3c7" // 边框颜色
property color successColor: "#27ae60" // 成功颜色
property color warningColor: "#f39c12" // 警告颜色
property color errorColor: "#e74c3c" // 错误颜色
// 设置窗口背景
Rectangle {
anchors.fill: parent
color: backgroundColor
}
// 串口管理器
SerialPortManager {
id: serialPort
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 15
spacing: 12
// 串口配置区域
GroupBox {
title: "📡 串口配置"
Layout.fillWidth: true
background: Rectangle {
color: cardColor
border.color: borderColor
border.width: 1
radius: 8
}
label: Label {
text: parent.title
color: primaryColor
font.bold: true
font.pixelSize: 14
padding: 5
}
GridLayout {
columns: 6
width: parent.width
rowSpacing: 8
columnSpacing: 8
// 第一行
Label {
text: "端口:"
color: textColor
font.bold: true
}
ComboBox {
id: portComboBox
Layout.fillWidth: true
model: serialPort.portList
background: Rectangle {
color: cardColor
border.color: borderColor
border.width: 1
radius: 4
}
onModelChanged: {
if (model.length > 0 && currentIndex === -1) {
currentIndex = 0
}
}
}
Label {
text: "波特率:"
color: textColor
font.bold: true
}
ComboBox {
id: baudRateComboBox
Layout.fillWidth: true
model: ["1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"]
currentIndex: 3 // 9600
background: Rectangle {
color: cardColor
border.color: borderColor
border.width: 1
radius: 4
}
}
Label {
text: "数据位:"
color: textColor
font.bold: true
}
ComboBox {
id: dataBitsComboBox
Layout.fillWidth: true
model: ["5", "6", "7", "8"]
currentIndex: 3 // 8
background: Rectangle {
color: cardColor
border.color: borderColor
border.width: 1
radius: 4
}
}
// 第二行
Label {
text: "校验位:"
color: textColor
font.bold: true
}
ComboBox {
id: parityComboBox
Layout.fillWidth: true
model: ["无", "奇校验", "偶校验", "标记", "空格"]
background: Rectangle {
color: cardColor
border.color: borderColor
border.width: 1
radius: 4
}
property var parityValues: [0, 3, 2, 1, 4]
}
Label {
text: "停止位:"
color: textColor
font.bold: true
}
ComboBox {
id: stopBitsComboBox
Layout.fillWidth: true
model: ["1", "1.5", "2"]
background: Rectangle {
color: cardColor
border.color: borderColor
border.width: 1
radius: 4
}
property var stopBitsValues: [1, 3, 2]
}
Label {
text: "流控制:"
color: textColor
font.bold: true
}
ComboBox {
id: flowControlComboBox
Layout.fillWidth: true
model: ["无", "硬件", "软件"]
background: Rectangle {
color: cardColor
border.color: borderColor
border.width: 1
radius: 4
}
property var flowControlValues: [0, 1, 2]
}
// 第三行 - 按钮
Button {
id: connectButton
text: serialPort.isConnected ? "🔌 断开连接" : "🔗 连接"
Layout.columnSpan: 2
Layout.fillWidth: true
background: Rectangle {
color: serialPort.isConnected ? accentColor : secondaryColor
radius: 6
}
contentItem: Text {
text: connectButton.text
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
}
onClicked: {
if (serialPort.isConnected) {
serialPort.disconnectSerialPort()
} else {
if (portComboBox.currentText) {
serialPort.connectSerialPort(
portComboBox.currentText,
parseInt(baudRateComboBox.currentText),
parseInt(dataBitsComboBox.currentText),
parityComboBox.parityValues[parityComboBox.currentIndex],
stopBitsComboBox.stopBitsValues[stopBitsComboBox.currentIndex],
flowControlComboBox.flowControlValues[flowControlComboBox.currentIndex]
)
}
}
}
}
Button {
text: "🔄 刷新端口"
Layout.fillWidth: true
background: Rectangle {
color: primaryColor
radius: 6
}
contentItem: Text {
text: parent.text
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
}
onClicked: serialPort.refreshPorts()
}
}
}
// 发送区域
GroupBox {
title: "📤 发送数据"
Layout.fillWidth: true
Layout.preferredHeight: 180
background: Rectangle {
color: cardColor
border.color: borderColor
border.width: 1
radius: 8
}
label: Label {
text: parent.title
color: primaryColor
font.bold: true
font.pixelSize: 14
padding: 5
}
ColumnLayout {
width: parent.width
spacing: 8
RowLayout {
CheckBox {
id: sendHexCheckBox
text: "十六进制发送"
Layout.alignment: Qt.AlignLeft
}
CheckBox {
id: receiveHexCheckBox
text: "十六进制显示"
Layout.alignment: Qt.AlignLeft
}
Item { Layout.fillWidth: true }
Button {
text: "🧹 清空接收"
background: Rectangle {
color: warningColor
radius: 6
}
contentItem: Text {
text: parent.text
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
}
onClicked: serialPort.clearReceivedData()
}
}
ScrollView {
Layout.fillWidth: true
Layout.preferredHeight: 80
background: Rectangle {
color: cardColor
border.color: borderColor
border.width: 1
radius: 4
}
TextArea {
id: sendTextArea
placeholderText: "请输入要发送的数据..."
placeholderTextColor: subTextColor
wrapMode: TextEdit.Wrap
color: textColor
background: Rectangle {
color: "transparent"
}
}
}
}
}
RowLayout {
Button {
text: "🚀 发送"
background: Rectangle {
color: secondaryColor
radius: 6
}
contentItem: Text {
text: parent.text
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
}
onClicked: {
serialPort.sendData(sendTextArea.text, sendHexCheckBox.checked)
}
}
Button {
text: "🗑️ 清空发送"
background: Rectangle {
color: subTextColor
radius: 6
}
contentItem: Text {
text: parent.text
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
}
onClicked: sendTextArea.clear()
}
Item { Layout.fillWidth: true }
Label {
text: "状态: " + serialPort.statusMessage
color: {
if (serialPort.isConnected) return successColor
else if (serialPort.statusMessage.includes("错误") ||
serialPort.statusMessage.includes("失败")) return errorColor
else return textColor
}
font.bold: true
padding: 8
background: Rectangle {
color: backgroundColor
radius: 4
border.color: borderColor
border.width: 1
}
}
}
// 接收区域
GroupBox {
title: "📥 接收数据"
Layout.fillWidth: true
Layout.fillHeight: true
background: Rectangle {
color: cardColor
border.color: borderColor
border.width: 1
radius: 8
}
label: Label {
text: parent.title
color: primaryColor
font.bold: true
font.pixelSize: 14
padding: 5
}
ColumnLayout {
width: parent.width
height: parent.height
spacing: 8
ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
background: Rectangle {
color: "#2c3e50"
border.color: borderColor
border.width: 1
radius: 4
}
TextArea {
id: receiveTextArea
text: serialPort.receivedData
wrapMode: TextEdit.Wrap
readOnly: true
font.family: "Consolas, 'Courier New', monospace"
font.pixelSize: 12
selectByMouse: true
color: "#ecf0f1"
background: Rectangle {
color: "transparent"
}
}
}
RowLayout {
Label {
text: "📊 接收字节数: " + receiveTextArea.text.length
color: textColor
font.bold: true
padding: 6
background: Rectangle {
color: backgroundColor
radius: 4
border.color: borderColor
border.width: 1
}
}
Item { Layout.fillWidth: true }
CheckBox {
id: autoScrollCheckBox
text: "自动滚动"
checked: true
contentItem: Text {
text: autoScrollCheckBox.text
color: textColor
font.bold: true
}
}
Button {
text: "💾 保存数据"
background: Rectangle {
color: primaryColor
radius: 6
}
contentItem: Text {
text: parent.text
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
}
onClicked: {
// 这里可以添加保存数据的功能
console.log("保存数据功能待实现")
}
}
}
}
}
}
// 状态栏
footer: ToolBar {
background: Rectangle {
color: primaryColor
}
RowLayout {
anchors.fill: parent
Label {
text: "🔧 串口调试工具 v1.0 | 就绪"
color: "white"
font.bold: true
padding: 8
}
Item { Layout.fillWidth: true }
Label {
text: new Date().toLocaleString(Qt.locale(), "yyyy-MM-dd hh:mm:ss")
color: "white"
padding: 8
}
}
}
}
三、效果演示
