qt实践教学(编写一个代码生成工具)持续更新至完成———
前言:
我的想法是搭建一个和STM32cubemux类似的图形化代码生成工具,可以把我平时用到的代码整合一下全部放入这个软件中,做一个我自己专门的代码生成工具,我初步的想法是在下拉选框中拉取需要配置的功能,然后就弹出对应的脚位图,只需要点击芯片上的脚位就可以配置对应的端口的功能,大家有好的建议欢迎指正。本工具实时更新到完成为止,我会实时更新进度和制作中遇到的问题和想法。
1.界面编写
界面大概框架布局,
界面的搭建可以参考下面链接的教程
第一部分 学习Qt必备知识 · Qt 快速入门系列教程
- 搭建好后,首先实现新建文件,保存文件,打开文件对应的功能。
选择新建动作,右击点击转到槽
选择
triggered()//
触发时机:当用户通过点击、快捷键等方式显式触发动作时(例如点击菜单项或工具栏按钮)
会在mainwindow.cpp自动生成一个槽函数
我们在该该函数里写上新建的代码
void MainWindow::on_action_N_triggered() {// 弹出文件对话框,让用户选择保存路径和文件名QString fileName = QFileDialog::getSaveFileName(this,"保存文件",QDir::homePath(), // 默认保存到用户主目录"文本文件 (*.c);;所有文件 (*)");if (fileName.isEmpty()) {QMessageBox::warning(this, "警告", "未选择文件名!");return;}// 使用 QFile 创建文件QFile file(fileName);if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {QTextStream stream(&file);stream << "这是一个新创建的文件\n"; // 写入初始内容file.close();QMessageBox::information(this, "成功", "文件已创建:" + fileName);} else {QMessageBox::critical(this, "错误", "无法创建文件:" + fileName);}}
就实现了新建的功能
然后实现保存功能
点击保存动作右击转到槽
选择
triggered()
在槽函数里写上保存的代码,现在保存的是TextEdit控件中的文本,之后再进行调整
void MainWindow::on_saveButton_clicked() {// 弹出文件保存对话框QString fileName = QFileDialog::getSaveFileName(this,"保存文件",QDir::homePath(),"文本文件 (*.txt);;所有文件 (*)");if (fileName.isEmpty()) {QMessageBox::warning(this, "警告", "未选择保存路径!");return;}// 打开文件并写入 QTextEdit 的内容QFile file(fileName);if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {QTextStream stream(&file);stream << ui->textEdit->toPlainText(); // 获取 QTextEdit 的文本内容file.close();QMessageBox::information(this, "成功", "文件已保存!");} else {QMessageBox::critical(this, "错误", "无法保存文件!");} }
我们先来设置主要的功能,就是点击配置选项的多选框,自动弹出芯片,并标出可选配置的脚位
下拉选项条转到槽填上以下代码
弹出窗口,和窗口自动跟随主窗口
void MainWindow::on_comboBox_activated(int index)//芯片选择
{// 获取当前选项的图片路径标识QString imageName = ui->comboBox->itemData(index).toString();// 假设图片存放在可执行文件同级目录的 images 文件夹下QString imagePath = ":/myimages/MCU/" + imageName;// 加载图片QPixmap pixmap(imagePath);if (pixmap.isNull()) {qDebug() << "无法加载图片:" << imagePath;ui-> picture->setText("图片加载失败");return;}// 显示图片(自动缩放适应 QLabel)ui->picture->setPixmap(pixmap.scaled(ui->picture->size(), Qt::KeepAspectRatio));
}
然后初始化下拉框的值
ui->comboBox->addItem("SC8P052", "SC8P052.png");ui->comboBox->addItem("SC8P054", "SC8P054.png");ui->comboBox->addItem("SC8P054_16A", "SC8P054_16A.png");ui->comboBox->addItem("SC8P062BD", "SC8P062BD.png");ui->comboBox->addItem("SC8P062BD_14A", "SC8P062BD_14A.png");ui->comboBox->addItem("SC8P8022D", "SC8P8022D.png");ui->comboBox->addItem("SC8P8122", "SC8P8122.png");ui->comboBox->addItem("MC30P6250", "MC30P6250.png");ui->comboBox->addItem("MC30P6280", "MC30P6280.png");ui->comboBox->addItem("P02", "P02.png");ui->comboBox->addItem("P04", "P04.png");ui->comboBox->addItem("SC8F073", "SC8F073.png");ui->comboBox->addItem("SC8F073_16A", "SC8F073_16A.png");
这是导入的图片文件
如何导入看这篇文章
Qt--导入整个目录的资源_现成的qt项目文件怎么导入进去-CSDN博客
我的设置完后,选择哪个芯片就会弹出对应的芯片脚位图
但是出来的图片有些模糊,可能是拉伸的比列不对,直接显示在界面上也有点占空间,我们把它变为浮窗的模式,下面是没有修改前的模式
创建一个新的界面,用来显示浮窗
FloatImageDialog.h.h文件
#ifndef FLOATIMAGEDIALOG_H
#define FLOATIMAGEDIALOG_H#include <QDialog>
#include <QLabel>
#include <QPixmap>class FloatImageDialog : public QDialog
{Q_OBJECTpublic:explicit FloatImageDialog(const QString &imagePath, QWidget *parent = nullptr);~FloatImageDialog();
private:QLabel *imageLabel; // 用于显示图片的标签
};#endif // FLOATIMAGEDIALOG_H
FloatImageDialog.cpp文件
#include "FloatImageDialog.h"
#include <QVBoxLayout>
#include <QPushButton>
#include <QMouseEvent> //
FloatImageDialog::FloatImageDialog(const QString &imagePath, QWidget *parent): QDialog(parent)
{// 设置窗口属性:无边框、置顶、透明背景(可选)setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);setAttribute(Qt::WA_TranslucentBackground); // 如果需要透明背景// 加载图片QPixmap pixmap(imagePath);if (pixmap.isNull()) {qDebug() << "无法加载图片:" << imagePath;return;}// 创建控件imageLabel = new QLabel(this);imageLabel->setPixmap(pixmap.scaled(400, 400, Qt::KeepAspectRatio, Qt::SmoothTransformation));// 可选:添加关闭按钮QPushButton *closeButton = new QPushButton("关闭", this);connect(closeButton, &QPushButton::clicked, this, &FloatImageDialog::close);// 布局QVBoxLayout *layout = new QVBoxLayout(this);layout->addWidget(imageLabel);layout->addWidget(closeButton);
}FloatImageDialog::~FloatImageDialog() {}void FloatImageDialog::mousePressEvent(QMouseEvent *event)
{if (event->button() == Qt::LeftButton) {dragPosition = event->globalPos() - frameGeometry().topLeft();event->accept();}
}void FloatImageDialog::mouseMoveEvent(QMouseEvent *event)
{if (event->buttons() & Qt::LeftButton) {move(event->globalPos() - dragPosition);event->accept();}
}
主函数MainWindow.h中加入头文件
#include "FloatImageDialog.h" // 包含自定义对话框头文件
转到用来隐藏显示MCU的槽函数
写入以下代码
if (!floatDialog) {// 创建浮动窗口(传入图片资源路径)floatDialog = new FloatImageDialog(":/images/SC8P052.png", this);floatDialog->show();} else {// 关闭并释放浮动窗口floatDialog->close();delete floatDialog;floatDialog = nullptr;}
实现了芯片图片在另一个窗口显示并可以拖动
给它初始化固定在右侧,用以下代码时不要忘记头文件#include <QScreen>
void MainWindow::onToggleFloatWindow()
{if (!floatDialog) {// 创建浮动窗口floatDialog = new FloatImageDialog(":/images/SC8P052.png", this);// 获取主窗口的屏幕位置和尺寸QRect mainGeometry = this->geometry();// 计算浮动窗口的初始位置(主窗口右侧 + 10 像素)int x = mainGeometry.right() + 10;int y = mainGeometry.top();// 获取屏幕的可用区域(避免窗口超出屏幕)QScreen *screen = QGuiApplication::primaryScreen();QRect screenGeometry = screen->availableGeometry();// 如果浮动窗口超出屏幕右侧,调整到屏幕边缘int maxX = screenGeometry.right() - floatDialog->width();if (x > maxX) {x = maxX;}// 设置浮动窗口位置floatDialog->move(x, y);floatDialog->show();} else {// 关闭并释放浮动窗口floatDialog->close();delete floatDialog;floatDialog = nullptr;}
}
窗口跟随移动
// MainWindow.h
protected:void moveEvent(QMoveEvent *event) override;// MainWindow.cpp
void MainWindow::moveEvent(QMoveEvent *event)
{QMainWindow::moveEvent(event);if (floatDialog) {// 主窗口移动时更新浮动窗口位置QRect mainGeometry = geometry();int x = mainGeometry.right() + 10;int y = mainGeometry.top();floatDialog->move(x, y);}
}
加更:
对界面进行了修改
原本想的是MCU选型完之后,根据不同的芯片,引脚的不同,跳转出相应的芯片,然后发现有点麻烦,我现在是直接把最大引脚的芯片放上来,如果是八脚的芯片就只用看上半部分,之后如果有时间再进行优化,现在的首要任务是把程序的功能先给实现。
我们新建一个专门用来填写,可以选择引脚功能的一个类。为了实现方便扩展,我们还要建一个读取文件的类,负责把各种芯片的引脚信息放在里面,程序来读取不同芯片引脚的功能。
文件读取类
cpp文件:
#include "FileHandler.h"
#include <QFile>
#include <QTextStream>
#include <QCoreApplication>FileHandler::FileHandler(QObject *parent) : QObject(parent)
{// 构造函数初始化(可留空)<button class="citation-flag" data-index="7">
}QString FileHandler::readTextFile(const QString &filePath)
{QFile file(filePath);if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {qWarning() << "读取失败:" << file.errorString(); // 错误处理 <button class="citation-flag" data-index="6">return QString();}QTextStream in(&file);QString content = in.readAll(); // 读取全部内容 <button class="citation-flag" data-index="5">file.close();return content;
}bool FileHandler::writeTextFile(const QString &filePath, const QString &content)
{QFile file(filePath);if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {emit writeCompleted(false); // 触发信号 <button class="citation-flag" data-index="6">return false;}QTextStream out(&file);out << content; // 写入内容 <button class="citation-flag" data-index="5">file.close();emit writeCompleted(true);return true;
}
.h文件
#ifndef FILEHANDLER_H
#define FILEHANDLER_H#include <QObject>
#include <QString>
#include <QByteArray>class FileHandler : public QObject
{Q_OBJECTpublic:explicit FileHandler(QObject *parent = nullptr);// 同步读取文本文件static QString readTextFile(const QString &filePath); // 声明静态方法 <button class="citation-flag" data-index="4">// 异步写入文件(带信号)bool writeTextFile(const QString &filePath, const QString &content);signals:void writeCompleted(bool success); // 自定义信号 <button class="citation-flag" data-index="6">private:// 可添加私有辅助方法
};#endif // FILEHANDLER_H
使用
QString data = FileHandler::readTextFile(":/MCU/DATA/MCU_IO/SC8P052.txt");qDebug() << "文件内容:" << data;
文件格式定义:
我们把芯片每个引脚的功能按以下格式写入txt文件。
PIN 16 P01 VDD P02 RB0 CMP2- OSCIN AN8 PWMD0 P03 RB1 CMP1- OSCOUT AN9 PWMB4 PWMD1 P04 RB2 AN10 PWMB3 PWMD4 P05 RB3 AN11 PWMD2 P06 RB4 AN12 PWMC4 PWMD3 P07 RB5 PWMC3 AN13 P08 RB6 PWMC2 AN14 P09 RB7 AN15 PWMC1 P10 RA5 AN5 PWMC0 P11 RA4 AN4 PWMA4 P12 RA3 AN3 PWMA3 P13 RA2 AN2 PWMA2 PWMB2 CMP0- P14 RA1 AN1 PWMA1 PWMB1 CMP+ CMP3 P15 RA0 AN0 PWMA0 PWMB0 CMPO INT P16 GND
QString data = FileHandler::readTextFile(":/MCU/DATA/MCU_IO/"+imagePath+".txt");qDebug() << "文件内容:" << data;
把这串代码放入槽函数中
在调试窗口可以看到文件正常的读出
现在我们要做的是把读到的文件,放入到数组当中,然后把数组里的每个引脚的可以设置的功能写到相应的combobox当中。
在调试的工程中,发现在别的类当中无法直接操作combobox,所以转来转去最后还是把操作combobox的代码写在了mainwindow当中,原本想直接在另外一个类中实现的,现在是通过信号和槽函数进行链接。
代码编写
主函数里加入的代码
mainwindow.cpp
// MainWindow.cpp
void MainWindow::parsePinData(const QString &data) {clearAllComboboxes();QStringList lines = data.split('\n', Qt::SkipEmptyParts);int pin = 0;QMap<QString, QString> pinMap;foreach (const QString &line, lines) {if (line.startsWith("PIN")) {QRegularExpression rx("PIN (\\d+)");QRegularExpressionMatch match = rx.match(line);if (match.hasMatch()) {pin = match.captured(1).toInt();pinMap = getPinMap(pin);}} else {QStringList parts = line.split(' ', Qt::SkipEmptyParts);if (parts.size() < 2) continue;QString srcPin = parts[0];QStringList items = parts.mid(1);QString targetPin = pinMap.value(srcPin, "");if (targetPin.isEmpty()) continue;// 直接访问 MainWindow 的控件QComboBox *combo = findChild<QComboBox*>(targetPin);if (combo) {combo->clear();combo->addItems(items);}}}
}void MainWindow::clearAllComboboxes() {QList<QComboBox*> combos = findChildren<QComboBox*>();foreach (QComboBox *combo, combos) {if (combo->objectName().startsWith("P")) {combo->clear();}}
}QMap<QString, QString> MainWindow::getPinMap(int pin) {QMap<QString, QString> map;if (pin == 8) {map = {{"P01", "P01"}, {"P02", "P02"}, {"P03", "P03"}, {"P04", "P04"},{"P05", "P13"}, {"P06", "P14"}, {"P07", "P15"}, {"P08", "P06"}};} else if (pin == 14) {map = { /* ... */ };} else if (pin == 16) {for (int i = 1; i <= 16; ++i) {QString key = QString("P%1").arg(i, 2, 10, QLatin1Char('0'));map[key] = key;}}return map;
}
// MainWindow.h
public: // 或 public slots:void parsePinData(const QString &data);
ConfigComboBox.cpp
void ConfigComboBox::setcombobox(QString &imageName)
{// if(imagePath=="SC8P052")// {// 同步读取QString data = FileHandler::readTextFile(":/MCU/DATA/MCU_IO/"+imageName+".txt");emit requestParsePinData(data);//信号// ConfigComboBox::parsePinData(data);qDebug() << "文件内容:" << data;// emit updateP01(data); // 触发信号//ui->P01->addItem("New Item");// ui->P01->addItem("New Item");// ui->P01->addItem("New Item");// }}
ConfigComboBox.h
#ifndef CONFIGCOMBOBOX_H
#define CONFIGCOMBOBOX_H
#include <QComboBox>
#include "ui_mainwindow.h"
#include <QMap>class ConfigComboBox : public QComboBox
{Q_OBJECTpublic:explicit ConfigComboBox(QWidget *parent = nullptr, Ui::MainWindow *mainUI = nullptr); void setcombobox(QString &imagePath);/
signals:void requestParsePinData(const QString &data);//信号声明private slots:private:};
#endif // CONFIGCOMBOBOX_H
其实 可以不用ConfigComboBox这个类了,原本我是想脚位的设置放在该类中实现的,但是发现别的类调用ui界面的控件很麻烦,所以把代码又放到mainwindow里面去了,ConfigComboBox里面就处理了以下文件,然后触发信号回到mainwindow,可以不加这个类,我这里先不删除了。
弄完这些后我们实现了选择芯片后,对应的脚位上显示出该脚位可以配置的功能,下面是示例
点击选择芯片,选择完后会出先每个脚位可选功能
效果:
接下来实现的功能是点击对应的功能,弹出配置界面,进行配置。
持续更新中————————