文章目录
设置比特率类
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