Qt CAN总线发送和接收案例

文章目录

设置比特率类

c 复制代码
// 文件: BitRateBox.h
// 作用: 定义了一个用于选择比特率的组合框类 BitRateBox,该类继承自QComboBox。
//       提供了设置和获取比特率、以及是否启用可变数据速率的功能。

#ifndef BITRATEBOX_H
#define BITRATEBOX_H

#include <QComboBox> // 引入Qt的组合框类QComboBox

// 使用QT宏来引入QIntValidator类的命名空间
QT_BEGIN_NAMESPACE
class QIntValidator;
QT_END_NAMESPACE

// BitRateBox 类定义,继承自QComboBox
class BitRateBox : public QComboBox
{
public:
    // 构造函数,接受一个可选的父窗口指针
    explicit BitRateBox(QWidget *parent = nullptr);
    // 析构函数
    ~BitRateBox();

    // 获取当前选定的比特率
    int bitRate() const;

    // 查询是否启用了灵活的数据速率(Flexible Data Rate)
    bool isFlexibleDataRateEnabled() const;
    // 设置灵活数据速率的启用状态
    void setFlexibleDateRateEnabled(bool enabled);

protected slots: // 更正:应为private slots,因为这些槽函数不应在类外部被直接调用
    // 根据下拉菜单的选择项检查并应用自定义速度策略
    void checkCustomSpeedPolicy(int idx);

private:
    // 初始化比特率选项
    void fillBitRates();

    // 标记是否启用了灵活数据速率,默认为禁用
    int m_isFlexibleDataRateEnabled; // 注意:此处类型应为bool而非int以匹配其用途
    // 用于验证用户输入的自定义比特率的整数验证器
    QIntValidator *m_customSpeedValidator;
};

#endif // BITRATEBOX_H // 头文件结束标记

设置比特率类实现

c 复制代码
#include "bitratebox.h"
#include <QLineEdit> // 包含QLineEdit头文件,用于访问lineEdit()方法

// BitRateBox构造函数,初始化组合框并设置验证器
BitRateBox::BitRateBox(QWidget *parent) :
    QComboBox(parent),
    m_customSpeedValidator(new QIntValidator(0, 1000000, this)) // 初始化验证器,允许0到1000000的整数输入
{
    fillBitRates(); // 填充默认比特率选项

    // 连接下拉菜单选项变化的信号到checkCustomSpeedPolicy槽函数
    connect(this, QOverload<int>::of(&QComboBox::currentIndexChanged),
            this, &BitRateBox::checkCustomSpeedPolicy);
}

// 析构函数,释放m_customSpeedValidator所占内存
BitRateBox::~BitRateBox()
{
    delete m_customSpeedValidator;
}

// 返回当前选中的比特率
int BitRateBox::bitRate() const
{
    // 如果选中的是"Custom"项,则直接解析文本为整数返回
    if (currentIndex() == (count() - 1))
        return currentText().toInt();

    // 否则,从itemData获取预设比特率值
    return itemData(currentIndex()).toInt();
}

// 查询灵活数据速率是否启用
bool BitRateBox::isFlexibleDataRateEnabled() const
{
    return m_isFlexibleDataRateEnabled;
}

// 设置灵活数据速率的启用状态,并根据状态调整验证器上限
void BitRateBox::setFlexibleDateRateEnabled(bool enabled)
{
    m_isFlexibleDataRateEnabled = enabled;
    // 根据状态调整自定义速度的最大值
    m_customSpeedValidator->setTop(enabled ? 10000000 : 1000000);
    fillBitRates(); // 重新填充比特率选项以反映设置
}

// 根据当前选中项决定是否进入自定义速度模式并设置验证器
void BitRateBox::checkCustomSpeedPolicy(int idx)
{
    const bool isCustomSpeed = !itemData(idx).isValid(); // 判断是否选择了"Custom"
    setEditable(isCustomSpeed); // 是否允许编辑(自定义输入)
    if (isCustomSpeed) {
        clearEditText(); // 清除已有编辑文本
        lineEdit()->setValidator(m_customSpeedValidator); // 设置验证器控制输入合法
    }
}

// 填充比特率选项到组合框
void BitRateBox::fillBitRates()
{
    // 静态比特率列表
    const QList<int> rates = {
        10000, 20000, 50000, 100000, 125000, 250000, 500000, 800000, 1000000
    };
    // 灵活数据速率列表,仅当功能启用时显示
    const QList<int> dataRates = {
        2000000, 4000000, 8000000
    };

    // 清空现有选项
    clear();

    // 添加静态比特率选项
    for (int rate : rates)
        addItem(QString::number(rate), rate);

    // 根据配置添加灵活数据速率选项
    if (isFlexibleDataRateEnabled()) {
        for (int rate : dataRates)
            addItem(QString::number(rate), rate);
    }

    // 添加"Custom"选项,允许用户输入自定义比特率
    addItem(tr("Custom"));
    // 默认选择500000 bits/sec
    setCurrentIndex(6);
}

发送数据帧类

c 复制代码
// 文件: SendFrameBox.h
// 作用: 定义了一个用于构造和管理发送CAN总线数据帧界面的类SendFrameBox,该类继承自QGroupBox。
//       包括两个自定义验证器HexIntegerValidator和HexStringValidator,分别用于验证十六进制整数和十六进制字符串输入的有效性。

#ifndef SENDFRAMEBOX_H
#define SENDFRAMEBOX_H

#include <QCanBusFrame> // CAN总线数据帧类
#include <QGroupBox>    // Qt中的分组框基类
#include <QRegularExpression> // 正则表达式类,可能用于输入验证辅助
#include <QValidator>   // 输入验证基类

QT_BEGIN_NAMESPACE
namespace Ui { class SendFrameBox; } // 前向声明Ui命名空间内的SendFrameBox类,通常由Qt Designer生成
QT_END_NAMESPACE

// HexIntegerValidator类定义,用于验证输入是否为有效的十六进制整数
class HexIntegerValidator : public QValidator
{
    Q_OBJECT // 支持信号槽机制

public:
    // 构造函数,可选传入父对象指针
    explicit HexIntegerValidator(QObject *parent = nullptr);

    // 重写了QValidator的validate方法,用于实际的输入验证逻辑
    QValidator::State validate(QString &input, int &) const;

    // 设置验证器允许的最大十六进制数值
    void setMaximum(uint maximum);

private:
    uint m_maximum = 0; // 最大允许的十六进制数值
};

// HexStringValidator类定义,用于验证输入是否为有效的十六进制字符串
class HexStringValidator : public QValidator
{
    Q_OBJECT

public:
    // 构造函数
    explicit HexStringValidator(QObject *parent = nullptr);

    // 重写的验证逻辑
    QValidator::State validate(QString &input, int &pos) const;

    // 设置验证器允许的最长输入长度
    void setMaxLength(int maxLength);

private:
    int m_maxLength = 0; // 允许的最长输入长度
};

// SendFrameBox类定义,负责构建和管理发送CAN帧的UI界面
class SendFrameBox : public QGroupBox
{
    Q_OBJECT // 支持信号槽机制

public:
    // 构造函数,可选传入父对象指针
    explicit SendFrameBox(QWidget *parent = nullptr);
    // 析构函数
    ~SendFrameBox();

    // 信号,当用户操作完成并准备发送CAN帧时发出
    signals:
        void sendFrame(const QCanBusFrame &frame);

private:
    Ui::SendFrameBox *m_ui; // 指向由Qt Designer生成的用户界面对象

    HexIntegerValidator *m_hexIntegerValidator; // 十六进制整数验证器
    HexStringValidator *m_hexStringValidator;   // 十六进制字符串验证器
};

#endif // SENDFRAMEBOX_H // 头文件结束标记

发送数据帧类的实现

c 复制代码
#include "sendframebox.h"
#include "ui_sendframebox.h"

// 定义标准ID和扩展ID的最大值
enum {
    MaxStandardId = 0x7FF, // 标准CAN帧ID的最大值
    MaxExtendedId = 0x10000000 // 扩展CAN帧ID的最大值
};

// 定义CAN帧数据域最大负载长度,区分经典CAN与FD(灵活数据速率)CAN
enum {
    MaxPayload = 8, // 经典CAN帧最大负载字节数
    MaxPayloadFd = 64 // FD CAN帧最大负载字节数
};

// HexIntegerValidator构造函数,初始化最大验证值为标准CAN ID的最大值
HexIntegerValidator::HexIntegerValidator(QObject *parent) :
    QValidator(parent),
    m_maximum(MaxStandardId)
{
}

// 验证输入的十六进制整数是否有效
QValidator::State HexIntegerValidator::validate(QString &input, int &)
{
    bool ok;
    uint value = input.toUInt(&ok, 16); // 将输入转换为无符号整数

    if (input.isEmpty()) // 空输入视为中间状态,等待更多输入
        return Intermediate;

    if (!ok || value > m_maximum) // 转换失败或超过最大值则无效
        return Invalid;

    return Acceptable; // 否则,输入有效
}

// 设置验证的最大十六进制数值
void HexIntegerValidator::setMaximum(uint maximum)
{
    m_maximum = maximum;
}

// HexStringValidator构造函数,初始化最大验证长度为经典CAN负载长度
HexStringValidator::HexStringValidator(QObject *parent) :
    QValidator(parent),
    m_maxLength(MaxPayload)
{
}

// 验证输入的十六进制字符串是否有效,同时格式化输入(每两个字符后加空格)
QValidator::State HexStringValidator::validate(QString &input, int &pos)
{
    const int maxSize = 2 * m_maxLength; // 计算最大字符长度(每字节2字符)
    const QChar space = QLatin1Char(' '); // 空格字符
    QString data = input.remove(space); // 移除所有空格进行验证

    if (data.isEmpty()) // 空输入视为中间状态
        return Intermediate;

    // 检查是否超过最大长度或以空格结尾(不允许尾随空格)
    if ((data.size() > maxSize) || (data.size() == maxSize && input.endsWith(space)))
        return Invalid;

    // 使用正则表达式检查所有字符是否为十六进制
    const QRegularExpression re(QStringLiteral("^[[:xdigit:]]*$"));
    if (!re.match(data).hasMatch())
        return Invalid;

    // 每两个十六进制字符后插入空格,如果需要
    const QRegularExpression insertSpace(QStringLiteral("(?:[[:xdigit:]]{2} )*[[:xdigit:]]{3}"));
    if (insertSpace.match(input).hasMatch()) {
        input.insert(input.size() - 1, space); // 在倒数第二个字符后插入空格
        pos = input.size(); // 更新光标位置
    }

    return Acceptable; // 输入有效
}

// 设置验证的最大十六进制字符串长度
void HexStringValidator::setMaxLength(int maxLength)
{
    m_maxLength = maxLength;
}

// SendFrameBox构造函数,初始化界面及连接信号槽
SendFrameBox::SendFrameBox(QWidget *parent) :
    QGroupBox(parent),
    m_ui(new Ui::SendFrameBox)
{
    m_ui->setupUi(this); // 使用Qt Designer生成的UI

    // 设置ID和数据的验证器
    m_hexIntegerValidator = new HexIntegerValidator(this);
    m_ui->frameIdEdit->setValidator(m_hexIntegerValidator);
    m_hexStringValidator = new HexStringValidator(this);
    m_ui->payloadEdit->setValidator(m_hexStringValidator);

    // 数据帧类型切换时,控制"灵活数据速率"选项的可用性
    connect(m_ui->dataFrame, &QRadioButton::toggled, [this](bool set) {
        if (set) m_ui->flexibleDataRateBox->setEnabled(true);
    });
    connect(m_ui->remoteFrame, &QRadioButton::toggled, [this](bool set) {
        if (set) {
            m_ui->flexibleDataRateBox->setEnabled(false);
            m_ui->flexibleDataRateBox->setChecked(false);
        }
    });
    connect(m_ui->errorFrame, &QRadioButton::toggled, [this](bool set) {
        if (set) {
            m_ui->flexibleDataRateBox->setEnabled(false);
            m_ui->flexibleDataRateBox->setChecked(false);
        }
    });

    // 扩展ID选项改变时,更新ID验证器的最大值
    connect(m_ui->extendedFormatBox, &QCheckBox::toggled, [this](bool set) {
        m_hexIntegerValidator->setMaximum(set ? MaxExtendedId : MaxStandardId);
    });

    // 灵活数据速率选项改变时,更新数据验证器的最大长度和控制位速率切换选项
    connect(m_ui->flexibleDataRateBox, &QCheckBox::toggled, [this](bool set) {
        m_hexStringValidator->setMaxLength(set ? MaxPayloadFd : MaxPayload);
        m_ui->bitrateSwitchBox->setEnabled(set);
        if (!set) m_ui->bitrateSwitchBox->setChecked(false);
    });

    // 监听ID编辑框文本变化,启用或禁用发送按钮
    auto frameIdTextChanged = [this]() {
        bool hasFrameId = !m_ui->frameIdEdit->text().isEmpty();
        m_ui->sendButton->setEnabled(hasFrameId);
        m_ui->sendButton->setToolTip(hasFrameId
                                     ? QString() : tr("Cannot send because no Frame ID was given."));
    };
    connect(m_ui->frameIdEdit, &QLineEdit::textChanged, frameIdTextChanged);
    frameIdTextChanged(); // 初始化时也检查一次

    // 发送按钮点击事件处理,构造并发送CAN帧
    connect(m_ui->sendButton, &QPushButton::clicked, [this]() {
        uint frameId = m_ui->frameIdEdit->text().toUInt(nullptr, 16);
        QString data = m_ui->payloadEdit->text();
        QByteArray payload = QByteArray::fromHex(data.remove(QLatin1Char(' ')).toLatin1()); // 转换为字节数组

        QCanBusFrame frame(frameId, payload); // 创建CAN帧
        frame.setExtendedFrameFormat(m_ui->extendedFormatBox->isChecked()); // 设置扩展帧格式
        frame.setFlexibleDataRateFormat(m_ui->flexibleDataRateBox->isChecked()); // 设置灵活数据速率格式
        frame.setBitrateSwitch(m_ui->bitrateSwitchBox->isChecked()); // 设置位速率切换

        if (m_ui->errorFrame->isChecked())
            frame.setFrameType(QCanBusFrame::ErrorFrame); // 错误帧
        else if (m_ui->remoteFrame->isChecked())
            frame.setFrameType(QCanBusFrame::RemoteRequestFrame); // 远程请求帧

        emit sendFrame(frame); // 发射信号,携带构造好的CAN帧
    });
}

// 析构函数,释放UI指针
SendFrameBox::~SendFrameBox()
{
    delete m_ui;
}

m_ui 发送帧界面

连接类

c 复制代码
/*
 * 文件名:ConnectDialog.h
 * 描述:此文件定义了ConnectDialog类,这是一个用于配置和选择CAN总线设备的对话框。
 *       用户可以通过该对话框选择CAN插件、设备接口以及配置CAN设备的特定参数。
 */

#ifndef CONNECTDIALOG_H
#define CONNECTDIALOG_H

// 包含必要的头文件
#include <QCanBusDevice>      // 提供CAN总线设备操作接口
#include <QCanBusDeviceInfo>  // 表示一个物理CAN设备的信息
#include <QDialog>           // 继承自QDialog,作为基础对话框类

// 包含自动生成的用户界面类头文件(由Qt Designer生成)
QT_BEGIN_NAMESPACE
namespace Ui {
class ConnectDialog;
}
QT_END_NAMESPACE

// 前向声明Settings结构体,用于存储当前的设置信息
class ConnectDialog : public QDialog {
    Q_OBJECT

public:
    // 使用QPair存储配置键和对应的值
    typedef QPair<QCanBusDevice::ConfigurationKey, QVariant> ConfigurationItem;

    // 定义一个结构体来保存所有设置
    struct Settings {
        QString pluginName;          // CAN总线插件名称
        QString deviceInterfaceName; // 设备接口名称
        QList<ConfigurationItem> configurations; // 额外的设备配置项列表
        bool useConfigurationEnabled;           // 是否使用自定义配置标志
    };

    // 构造函数,可选传入父窗口指针
    explicit ConnectDialog(QWidget *parent = nullptr);
    // 析构函数
    ~ConnectDialog();

    // 获取当前设置信息
    Settings settings() const;

private slots:
    // 插件改变时的槽函数
    void pluginChanged(const QString &plugin);
    // 接口改变时的槽函数
    void interfaceChanged(const QString &interface);
    // 确定按钮点击时的槽函数
    void ok();
    // 取消按钮点击时的槽函数
    void cancel();

private:
    // 根据配置键获取配置值
    QString configurationValue(QCanBusDevice::ConfigurationKey key);
    // 恢复默认设置
    void revertSettings();
    // 更新界面上显示的设置信息
    void updateSettings();

    // 私有成员变量
    Ui::ConnectDialog *m_ui;            // 对话框的用户界面指针
    Settings m_currentSettings;         // 当前对话框中的设置
    QList<QCanBusDeviceInfo> m_interfaces; // 可用的CAN设备接口信息列表
};

#endif // CONNECTDIALOG_H

连接类实现

c 复制代码
#include "connectdialog.h"
#include "ui_connectdialog.h"

// 引入QCanBus相关头文件,用于访问CAN总线设备操作接口和设备信息
#include <QCanBus>

// ConnectDialog类的实现开始
ConnectDialog::ConnectDialog(QWidget *parent) :
    QDialog(parent), // 初始化父窗口指针
    m_ui(new Ui::ConnectDialog) // 初始化用户界面指针并分配内存
{
    m_ui->setupUi(this); // 使用Qt Designer生成的用户界面配置此对话框

    // 设置错误过滤器编辑框的验证器,允许0至0x1FFFFFFF之间的整数
    m_ui->errorFilterEdit->setValidator(new QIntValidator(0, 0x1FFFFFFFU, this));

    // 为"回环"和"接收自身消息"组合框添加选项
    m_ui->loopbackBox->addItem(tr("unspecified"), QVariant());
    m_ui->loopbackBox->addItem(tr("false"), QVariant(false));
    m_ui->loopbackBox->addItem(tr("true"), QVariant(true));

    m_ui->receiveOwnBox->addItem(tr("unspecified"), QVariant());
    m_ui->receiveOwnBox->addItem(tr("false"), QVariant(false));
    m_ui->receiveOwnBox->addItem(tr("true"), QVariant(true));

    // 添加CAN-FD支持选项
    m_ui->canFdBox->addItem(tr("false"), QVariant(false));
    m_ui->canFdBox->addItem(tr("true"), QVariant(true));

    // 允许数据速率框支持灵活数据速率
    m_ui->dataBitrateBox->setFlexibleDateRateEnabled(true);

    // 连接信号和槽
    connect(m_ui->okButton, &QPushButton::clicked, this, &ConnectDialog::ok); // 确定按钮点击
    connect(m_ui->cancelButton, &QPushButton::clicked, this, &ConnectDialog::cancel); // 取消按钮点击
    connect(m_ui->useConfigurationBox, &QCheckBox::clicked, 
             m_ui->configurationBox, &QGroupBox::setEnabled); // 配置使用状态改变
    connect(m_ui->pluginListBox, &QComboBox::currentTextChanged, 
             this, &ConnectDialog::pluginChanged); // 插件选择变化
    connect(m_ui->interfaceListBox, &QComboBox::currentTextChanged, 
             this, &ConnectDialog::interfaceChanged); // 接口选择变化

    // 隐藏原始过滤器编辑框及其标签,默认不显示
    m_ui->rawFilterEdit->hide();
    m_ui->rawFilterLabel->hide();

    // 加载可用的CAN总线插件到插件列表框
    m_ui->pluginListBox->addItems(QCanBus::instance()->plugins());

    // 初始化设置并更新UI
    updateSettings();
}

ConnectDialog::~ConnectDialog()
{
    delete m_ui; // 释放UI指针占用的内存
}

// 获取当前设置
ConnectDialog::Settings ConnectDialog::settings() const
{
    return m_currentSettings;
}

// 插件改变时更新界面和设备列表
void ConnectDialog::pluginChanged(const QString &plugin)
{
    m_ui->interfaceListBox->clear(); // 清空现有设备列表
    m_interfaces = QCanBus::instance()->availableDevices(plugin); // 获取新插件下的设备
    for (const QCanBusDeviceInfo &info : qAsConst(m_interfaces))
        m_ui->interfaceListBox->addItem(info.name()); // 添加设备接口到列表
}

// 接口改变时更新接口相关信息
void ConnectDialog::interfaceChanged(const QString &interface)
{
    // 重置并更新当前选定接口的相关信息
    m_ui->isVirtual->setChecked(false);
    m_ui->isFlexibleDataRateCapable->setChecked(false);

    for (const QCanBusDeviceInfo &info : qAsConst(m_interfaces)) {
        if (info.name() == interface) {
            m_ui->descriptionLabel->setText(info.description());
            QString serialNumber = info.serialNumber();
            if (serialNumber.isEmpty())
                serialNumber = tr("n/a");
            m_ui->serialNumberLabel->setText(tr("Serial: %1").arg(serialNumber));
            m_ui->channelLabel->setText(tr("Channel: %1").arg(info.channel()));
            m_ui->isVirtual->setChecked(info.isVirtual());
            m_ui->isFlexibleDataRateCapable->setChecked(info.hasFlexibleDataRate());
            break;
        }
    }
}

// 点击确定按钮时执行的操作
void ConnectDialog::ok()
{
    updateSettings(); // 更新设置
    accept(); // 接受对话框
}

// 点击取消按钮时执行的操作
void ConnectDialog::cancel()
{
    revertSettings(); // 恢复原始设置
    reject(); // 拒绝对话框
}

// 根据键获取配置的值
QString ConnectDialog::configurationValue(QCanBusDevice::ConfigurationKey key)
{
    QVariant result;

    // 遍历配置项寻找匹配的键
    for (const ConfigurationItem &item : qAsConst(m_currentSettings.configurations)) {
        if (item.first == key) {
            result = item.second;
            break;
        }
    }

    // 特殊处理未指定情况
    if (result.isNull() && (
                key == QCanBusDevice::LoopbackKey ||
                key == QCanBusDevice::ReceiveOwnKey)) {
        return tr("unspecified");
    }

    // 返回配置值的字符串表示形式
    return result.toString();
}

// 恢复到初始设置状态
void ConnectDialog::revertSettings()
{
    // 重置各UI组件到上次保存的设置状态
    m_ui->pluginListBox->setCurrentText(m_currentSettings.pluginName);
    m_ui->interfaceListBox->setCurrentText(m_currentSettings.deviceInterfaceName);
    m_ui->useConfigurationBox->setChecked(m_currentSettings.useConfigurationEnabled);
    // ...省略其他设置恢复逻辑
}

// 根据UI当前状态更新设置
void ConnectDialog::updateSettings()
{
    // 从UI获取最新选择和输入值,更新设置结构体
    m_currentSettings.pluginName = m_ui->pluginListBox->currentText();
    m_currentSettings.deviceInterfaceName = m_ui->interfaceListBox->currentText();
    m_currentSettings.useConfigurationEnabled = m_ui->useConfigurationBox->isChecked();
    // ...省略其他设置更新逻辑,包括循环遍历配置项并添加到m_currentSettings.configurations
}

连接类UI设计


主窗口类

c 复制代码
/*
 * 主窗口类(MainWindow)的声明文件
 *
 * 该文件定义了MainWindow类,继承自QMainWindow,用于管理应用程序的主要界面和交互逻辑。
 * 包含了对CAN总线设备的连接、数据发送接收、错误处理以及界面更新等功能。
 */

#ifndef MAINWINDOW_H // 防止多重包含的预处理器宏
#define MAINWINDOW_H

// 引入必要的头文件
#include <QCanBusDevice> // 包含CAN总线设备相关的错误类型
#include <QMainWindow> // 继承自QMainWindow的基础类

// 类的前向声明,减少编译依赖
class ConnectDialog; // 连接对话框类的前向声明
class QLabel; // 标签控件类的前向声明,用于显示状态和写入帧的数量
class QTimer; // 定时器类的前向声明,用于监控总线状态

// 命名空间QT的开始与结束,包含自定义或Qt相关的内容
QT_BEGIN_NAMESPACE

// 前向声明QCanBusFrame,用于CAN数据帧处理
class QCanBusFrame;

// 结束命名空间QT
QT_END_NAMESPACE

// MainWindow类的声明开始
class MainWindow : public QMainWindow
{
    Q_OBJECT // 必须的宏,启用Qt的信号和槽机制

public:
    // 构造函数,可选传入父窗口指针
    explicit MainWindow(QWidget *parent = nullptr);
    
    // 析构函数
    ~MainWindow();

private slots: // 私有槽函数部分
    // 处理接收到的数据帧
    void processReceivedFrames();
    
    // 发送数据帧
    void sendFrame(const QCanBusFrame &frame) const;
    
    // 处理CAN总线错误
    void processErrors(QCanBusDevice::CanBusError) const;
    
    // 连接CAN设备
    void connectDevice();
    
    // 总线状态更新
    void busStatus();
    
    // 断开与CAN设备的连接
    void disconnectDevice();
    
    // 处理已写入数据帧的数量更新
    void processFramesWritten(qint64);

protected: // 保护成员函数,子类可访问
    // 重写的关闭事件处理函数
    void closeEvent(QCloseEvent *event) override;

private: // 私有成员函数和变量
    // 初始化动作与信号槽连接
    void initActionsConnections();

    // 记录已写入数据帧的数量
    qint64 m_numberFramesWritten;
    
    // 界面相关的UI指针
    Ui::MainWindow *m_ui; // 主窗口界面
    QLabel *m_status; // 显示状态的标签
    QLabel *m_written; // 显示已写入帧数的标签
    
    // 连接对话框实例,用于设备连接设置
    ConnectDialog *m_connectDialog;
    
    // CAN总线设备指针,采用unique_ptr智能指针管理
    std::unique_ptr<QCanBusDevice> m_canDevice;
    
    // 用于定期检查总线状态的定时器
    QTimer *m_busStatusTimer;
};

主窗口类实现

c 复制代码
#include "mainwindow.h"

#include "ui_mainwindow.h"

#include "connectdialog.h"


// 引入Qt CAN总线模块相关的头文件

#include <QCanBus>

#include <QCanBusFrame>

#include <QCloseEvent> // 处理窗口关闭事件

#include <QDesktopServices> // 用于打开外部链接,如文档或网页

#include <QTimer> // 定时器,用于定期检查CAN总线状态


// MainWindow构造函数,负责建立用户界面及基础设置

MainWindow::MainWindow(QWidget *parent)

    : QMainWindow(parent) // 继承自QWidget的构造函数

    , m_ui(new Ui::MainWindow) // 使用Qt User Interface Compiler产生的界面类

    , m_busStatusTimer(new QTimer(this)) // 初始化一个定时器来监控CAN总线状态

{

    m_ui->setupUi(this); // 调用UI设置方法,加载设计的界面布局


    // 初始化连接对话框,用于选择CAN设备

    m_connectDialog = new ConnectDialog;


    // 在状态栏添加两个标签,分别显示总线状态和已发送帧的数量

    m_status = new QLabel;

    m_ui->statusBar->addPermanentWidget(m_status);

    m_written = new QLabel;

    m_ui->statusBar->addWidget(m_written);


    // 初始化界面元素与功能之间的连接关系

    initActionsConnections();


    // 稍后自动显示连接设备对话框,给予用户时间准备

    QTimer::singleShot(50, m_connectDialog, &ConnectDialog::show);


    // 设置定时器触发时调用busStatus函数检查CAN总线状态

    connect(m_busStatusTimer, &QTimer::timeout, this, &MainWindow::busStatus);

}


// 析构函数,清理分配的资源

MainWindow::~MainWindow(){

    delete m_connectDialog; // 删除连接对话框实例

    delete m_ui; // 删除界面设置实例

}


// 初始化窗口中的动作(如菜单项)与槽函数的关联

void MainWindow::initActionsConnections(){

    // 初始化界面按钮状态,初始时只有"连接"可用

    m_ui->actionDisconnect->setEnabled(false);

    m_ui->sendFrameBox->setEnabled(false);


    // 当用户在SendFrameBox中点击发送时,触发sendFrame槽处理发送CAN帧

    connect(m_ui->sendFrameBox, &SendFrameBox::sendFrame, this, &MainWindow::sendFrame);


    // "连接"菜单项点击后显示连接设备对话框

    connect(m_ui->actionConnect, &QAction::triggered, [this]() {

        // 断开当前连接(如果存在),然后显示连接对话框

        m_canDevice.release()->deleteLater();

        m_connectDialog->show();

    });


    // 对话框确认后尝试连接选定的CAN设备
    connect(m_connectDialog, &QDialog::accepted, this, &MainWindow::connectDevice);
    // "断开"菜单项点击后调用disconnectDevice函数断开CAN总线
    connect(m_ui->actionDisconnect, &QAction::triggered, this, &MainWindow::disconnectDevice);
    // "重置控制器"菜单项点击后调用CAN设备的resetController方法
    connect(m_ui->actionResetController, &QAction::triggered, this, [this]() {

        m_canDevice->resetController();
    });

    // "退出"菜单项点击后关闭主窗口
    connect(m_ui->actionQuit, &QAction::triggered, this, &QWidget::close);

    // "关于Qt"菜单项点击后显示Qt的关于信息
    connect(m_ui->actionAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt);
    // "清除日志"菜单项点击后清空接收消息的文本编辑框
    connect(m_ui->actionClearLog, &QAction::triggered, m_ui->receivedMessagesEdit, &QTextEdit::clear);
    // "插件文档"菜单项点击后打开Qt CAN Bus插件的在线文档页面
    connect(m_ui->actionPluginDocumentation, &QAction::triggered, this, []() {
        QDesktopServices::openUrl(QUrl("http://doc.qt.io/qt-5/qtcanbus-backends.html"));
    });

}
//处理CAN总线设备发生的错误,根据不同的错误类型更新状态栏显示错误信息。
void MainWindow::processErrors(QCanBusDevice::CanBusError error) const
{
    switch (error) {
    case QCanBusDevice::ReadError:
    case QCanBusDevice::WriteError:
    case QCanBusDevice::ConnectionError:
    case QCanBusDevice::ConfigurationError:
    case QCanBusDevice::UnknownError:
        m_status->setText(m_canDevice->errorString());
        break;
    default:
        break;
    }
}
//连接CAN总线设备的过程,包括设置设备、配置参数、连接状态更新和界面反馈。
void MainWindow::connectDevice()
{
    // 读取连接对话框中的设置
    const ConnectDialog::Settings p = m_connectDialog->settings();
    // 尝试创建CAN设备
    QString errorString;
    m_canDevice.reset(QCanBus::instance()->createDevice(p.pluginName, p.deviceInterfaceName, &errorString));
    // 错误处理
    if (!m_canDevice) {
        m_status->setText(tr("Error creating device '%1', reason: '%2'").arg(p.pluginName).arg(errorString));
        return;
    }
    // 初始化帧计数
    m_numberFramesWritten = 0;
    // 连接错误、接收帧、写入帧的信号连接
    connect(m_canDevice.get(), &QCanBusDevice::errorOccurred, this, &MainWindow::processErrors);
    connect(m_canDevice.get(), &QCanBusDevice::framesReceived, this, &MainWindow::processReceivedFrames);
    connect(m_canDevice.get(), &QCanBusDevice::framesWritten, this, &MainWindow::processFramesWritten);
    // 应用设置
    if (p.useConfigurationEnabled) {
        for (const ConnectDialog::ConfigurationItem &item : p.configurations)
            m_canDevice->setConfigurationParameter(item.first, item.second);
    }

    // 连接设备
    if (!m_canDevice->connectDevice()) {
        // 错误处理
        m_status->setText(tr("Connection error: %1").arg(m_canDevice->errorString()));
        m_canDevice.reset();
    } else {
        // 更新界面状态
        m_ui->actionConnect->setEnabled(false);
        m_ui->actionDisconnect->setEnabled(true);
        m_ui->sendFrameBox->setEnabled(true);
        // 更新状态栏显示连接信息
        const QVariant bitRate = m_canDevice->configurationParameter(QCanBusDevice::BitRateKey);
        if (bitRate.isValid()) {
            const bool isCanFd = m_canDevice->configurationParameter(QCanBusDevice::CanFdKey).toBool();
            const QVariant dataBitRate = m_canDevice->configurationParameter(QCanBusDevice::DataBitRateKey);
            if (isCanFd && dataBitRate.isValid()) {
                m_status->setText(tr("Plugin: %1, connected to %2 at %3 / %4 kBit/s").arg(p.pluginName).arg(p.deviceInterfaceName).arg(bitRate.toInt() / 1000).arg(dataBitRate.toInt() / 1000));

            } else {
                m_status->setText(tr("Plugin: %1, connected to %2 at %3 kBit/s").arg(p.pluginName).arg(p.deviceInterfaceName).arg(bitRate.toInt() / 1000));
            }
        } else {
            m_status->setText(tr("Plugin: %1, connected to %2").arg(p.pluginName).arg(p.deviceInterfaceName));

        }

        // 总线状态定时器
        if (m_canDevice->hasBusStatus())
            m_busStatusTimer->start(2000);
        else
            m_ui->busStatus->setText(tr("No CAN bus status available."));
    }

}
//更新并显示CAN总线的状态,如Good, Warning, Error等。
void MainWindow::busStatus()
{
    // 检查设备和状态
    if (!m_canDevice || !m_canDevice->hasBusStatus()) {
        m_ui->busStatus->setText(tr("No CAN bus status available."));
        m_busStatusTimer->stop();
        return;
    }
    // 更新总线状态
    switch (m_canDevice->busStatus()) {
    case QCanBusDevice::CanBusStatus::Good:
        m_ui->busStatus->setText("CAN bus status: Good.");
        break;
    case QCanBusDevice::CanBusStatus::Warning:
        m_ui->busStatus->setText("CAN bus status: Warning.");
        break;
    case QCanBusDevice::CanBusStatus::Error:
        m_ui->busStatus->setText("CAN bus status: Error.");
        break;
    case QCanBusDevice::CanBusStatus::BusOff:
        m_ui->busStatus->setText("CAN bus status: Bus Off.");
        break;
    default:
        m_ui->busStatus->setText("CAN bus status: Unknown.");
        break;
    }
}
//断开与CAN总线设备的连接并更新界面状态。
void MainWindow::disconnectDevice()
{
    // 断开设备
    if (!m_canDevice)
        return;
    
    m_busStatusTimer->stop();
    m_canDevice->disconnectDevice();
    
    // 更新界面状态
    m_ui->actionConnect->setEnabled(true);
    m_ui->actionDisconnect->setEnabled(false);
    m_ui->sendFrameBox->setEnabled(false);
    m_status->setText(tr("Disconnected"));
}
//处理写入帧完成的回调,更新已写入帧计数。
void MainWindow::processFramesWritten(qint64 count)
{
    m_numberFramesWritten += count;
    m_written->setText(tr("%1 frames written").arg(m_numberFramesWritten));
}
//关闭事件处理,先关闭连接对话框再接受关闭事件。
void MainWindow::closeEvent(QCloseEvent *event)
{
    m_connectDialog->close();
    event->accept();
}
//根据CAN帧标志位返回字符串表示,如B代表BitrateSwitch开启,E代表Error State Indicator,L代表Local Echo。
static QString frameFlags(const QCanBusFrame &frame)
{
    QString result = QLatin1String(" --- ");
    
    if (frame.hasBitrateSwitch())
        result[1] = QLatin1Char('B');
    if (frame.hasErrorStateIndicator())
        result[2] = QLatin1Char('E');
    if (frame.hasLocalEcho())
        result[3] = QLatin1Char('L');
    
    return result;
}
//处理接收到的CAN帧,解析并显示到界面。
void MainWindow::processReceivedFrames()
{
    if (!m_canDevice)
        return;
    
    while (m_canDevice->framesAvailable()) {
        const QCanBusFrame frame = m_canDevice->readFrame();
        
        QString view;
        if (frame.frameType() == QCanBusFrame::ErrorFrame)
            view = m_canDevice->interpretErrorFrame(frame);
        else
            view = frame.toString();
        
        const QString time = QString::fromLatin1("%1.%2  ")
                .arg(frame.timeStamp().seconds(), 10, 10, QLatin1Char(' '))
                .arg(frame.timeStamp().microSeconds() / 100, 4, 10, QLatin1Char('0'));
        
        const QString flags = frameFlags(frame);
        
        m_ui->receivedMessagesEdit->append(time + flags + view);
    }
}
//发送CAN帧到设备。
void MainWindow::sendFrame(const QCanBusFrame &frame) const
{
    if (!m_canDevice)
        return;
    
    m_canDevice->writeFrame(frame);
}

主界面UI




整体UI

QT案例

相关推荐
WaaTong2 分钟前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
m0_743048442 分钟前
初识Java EE和Spring Boot
java·java-ee
AskHarries4 分钟前
Java字节码增强库ByteBuddy
java·后端
MonkeyKing_sunyuhua22 分钟前
ubuntu22.04 docker-compose安装postgresql数据库
数据库·docker·postgresql
天郁青22 分钟前
数据库交互的本地项目:后台管理系统
数据库·交互
小灰灰__24 分钟前
IDEA加载通义灵码插件及使用指南
java·ide·intellij-idea
马剑威(威哥爱编程)28 分钟前
MongoDB面试专题33道解析
数据库·mongodb·面试
夜雨翦春韭28 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
程序媛小果1 小时前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
小光学长1 小时前
基于vue框架的的流浪宠物救助系统25128(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
数据库·vue.js·宠物