Qt——实现报警信息弹窗界面


前言

基于VS2019、Qt5.15.5,纯代码实现报警信息弹窗界面,在我的上一篇基础上,增加代码即可,主要知识点:QMediaPlayer相关,还有鼠标事件,窗体移动等。


效果

原视频很清晰,CSDN上传的后太模糊,估计处理了。可以移步至B站观看演示视频。

B站 报警信息弹窗界面

说明

在这我将修改的代码和新增的代码放在这里,代码里有注释和说明,这里就不赘述啦。

一、报警信号的创建和修改

1.alarmMessageTab.h

#pragma once

#include "ui_alarmMessageTab.h"
//#include "qtCommon.h"//【注意】结构体重复包含的问题
#include "alarmSignalWin.h"
#include "analyzeAlarmInformation.h"
#include "analyzeAlarmInfoWin.h"

class alarmMessageTab : public QWidget
{
    Q_OBJECT

public:
    alarmMessageTab(QWidget *parent = nullptr);
    ~alarmMessageTab();

    QList<AlarmPoint> alarmPoints;

	QList<AlarmPoint> juleiPoints;
	QList<analyzeAlarmInfoWin*> m_analyzeAlarmInfoWins; // 将 plot 定义为类的成员变量

    QTimer* timer = nullptr;
	
	QCheckBox* popupCheckbox = nullptr;
    QTableWidget* tableWidget = nullptr; // Declare QTableWidget as a private member variable
    QMenu* popMenu = nullptr; //右键菜单
    QAction* actionCheckSignalInfo = nullptr;

	bool isTanchuang = true; //控制是否需要打开弹窗,默认打开。
	int juleicount = 0;		//聚类中心数
	int juleiThre = 50;		//2点之间的距离阈值

private:
    Ui::alarmMessageTabClass ui;
    int timerCount = 0;; // 记录定时器触发次数
private slots:
    void generateAlarmPoint();
    void slotContextMenu(QPoint pos);           //右键菜单响应函数
	void popupCheckboxStateChanged(int state);  //复选框响应函数
    void showSelectedAlarmInfo(int row);
};

2.alarmMessageTab.cpp

2.1 构造函数和析构函数
alarmMessageTab::alarmMessageTab(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
    this->resize(1200, 600);
    
    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &alarmMessageTab::generateAlarmPoint);
    timer->start(12000); // Generate alarm point every second

    // 在类的实现文件中初始化复选框控件
    popupCheckbox = new QCheckBox(QString::fromLocal8Bit("弹窗功能"));
    popupCheckbox->setChecked(true); // 设置默认勾选
    popupCheckbox->setMinimumHeight(30);//设置popupCheckbox的最小高度解决布局高度改变的问题

    // 连接复选框信号和槽
    connect(popupCheckbox, &QCheckBox::stateChanged, this, &alarmMessageTab::popupCheckboxStateChanged);
    
    // Create and set up the table widget
    tableWidget = new QTableWidget(this);
    tableWidget->setColumnCount(4); // 4 columns for time, location, type, channel

    QStringList headers;
    headers << "Time" << "Location" << "Type" << "Channel";
    tableWidget->setHorizontalHeaderLabels(headers);
    tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); // Stretch columns to fill the table
    tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); // Disable editing of table cells
    
    //设置tableWidget的上下文菜单策略为自定义。这意味着我们将会自己创建一个上下文菜单,并在需要时显示它。
    tableWidget->setContextMenuPolicy(Qt::CustomContextMenu);
    popMenu = new QMenu(tableWidget);//创建一个新的上下文菜单对象popMenu,并将其与tableWidget关联。
    actionCheckSignalInfo = new QAction();
    actionCheckSignalInfo->setText(QString::fromLocal8Bit("查看扰动信号"));
    popMenu->addAction(actionCheckSignalInfo);
    connect(tableWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotContextMenu(QPoint)));
    connect(actionCheckSignalInfo, &QAction::triggered, this, [this]() {
        int row = tableWidget->currentRow();
        showSelectedAlarmInfo(row);
        });

    QVBoxLayout* layout = new QVBoxLayout(this);
    layout->addWidget(popupCheckbox);
    layout->addWidget(tableWidget);
    setLayout(layout);
}

alarmMessageTab::~alarmMessageTab()
{
    delete timer;
    timer = nullptr;

    delete actionCheckSignalInfo;
    actionCheckSignalInfo = nullptr;
    delete popMenu;
    popMenu = nullptr;

    delete tableWidget;
    tableWidget = nullptr;
    delete popupCheckbox;
    popupCheckbox = nullptr;
}

2.2 generateAlarmPoint函数

void alarmMessageTab::generateAlarmPoint() {
   
    AlarmPoint newAlarm;
    //为了生成不同位置的点
    if (juleicount % 2 == 0) {
        //AlarmPoint newAlarm;
        newAlarm.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
        newAlarm.location = 110.1;
        newAlarm.type = 2;
        newAlarm.channel = 3;
        newAlarm.count = 1;
        //qDebug() << "juleicount is even";
    }
    else {
        //AlarmPoint newAlarm;
        newAlarm.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
        newAlarm.location = 210.1 ;
        newAlarm.type = 4;
        newAlarm.channel = 2;
        newAlarm.count = 1;
        //qDebug() << "juleicount is odd";
    }
    if (juleiPoints.isEmpty())//判断一开始是否为空
    {
        juleiPoints.append(newAlarm);
        if (isTanchuang)//是否启用弹窗标志
        {
            for (int i = 0; i < juleiPoints.size(); ++i) {//用for循环是为了预防同时有几个新的报警点
                QString objectName = QString::number(floor(juleiPoints.at(i).location));//获取该弹窗,并以位置命名
                analyzeAlarmInfoWin* win = new analyzeAlarmInfoWin(juleiPoints[i]);
                win->setObjectName(objectName);
                m_analyzeAlarmInfoWins.append(win); // 将弹窗添加到成员变量中
                if (!win)
                {
                    // 调试信息:确认 alarmViewer 对象创建失败
                    qDebug() << "alarmViewer create failed!";
                    return;
                }
                // 调试信息:确认 alarmViewer 对象成功创建
                qDebug() << "win:" << objectName << " create successful!";
                win->setAttribute(Qt::WA_DeleteOnClose);
                win->show(); // 显示 AlarmViewer 界面

                connect(win, &analyzeAlarmInfoWin::destroyed, this, [this, win]() {
                    //qDebug() << "Empty! win destroyed!";
                    qDebug() << "Deleting object with name: " << win->objectName();
                    // 在 m_analyzeAlarmInfoWins 中查找要删除的对象
                    for (int i = 0; i < m_analyzeAlarmInfoWins.size(); ++i) {
                        if (m_analyzeAlarmInfoWins.at(i) == win) {
                            m_analyzeAlarmInfoWins.removeAt(i); // 从列表中删除对象
                            //qDebug() << "Object removed from list";
                            break; // 找到并删除对象后退出循环
                        }
                    }
                    qDebug() << "destroyed m_analyzeAlarmInfoWins size = " << m_analyzeAlarmInfoWins.size();
                    });
            }
        }
    }

    else//juleiPoints不为空
    {
        bool foundPoint = false;
        for (AlarmPoint& point : juleiPoints) {
            if (qAbs(point.location - newAlarm.location) < juleiThre) {
                point.time = newAlarm.time;
                foundPoint = true;
                point.count = point.count + 1;
                if (isTanchuang) {
                    //进一步判断该点的弹窗是否关闭,
                    QString objectName = QString::number(floor(point.location));
                    bool foundWin = false;
                    for (auto win : m_analyzeAlarmInfoWins)
                    {
                        if (win->objectName() == objectName)
                        {
                            // 找到匹配的对象名称,更改最近一次报警时间和次数
                            qDebug() << "win:" << objectName << " is not closed ";
                            foundWin = true;
                            QTableWidgetItem* itemTime = new QTableWidgetItem(point.time);
                            QTableWidgetItem* itemCount = new QTableWidgetItem(QString::number(point.count));
                            win->tableWidget->setItem(0, 0, itemTime);
                            win->tableWidget->setItem(0, 4, itemCount);
                        }
                    }
                    //如果关闭,打开弹窗,更改最近一次报警时间和次数
                    if (!foundWin) {
                        //打开弹窗
                        QString objectName = QString::number(floor(point.location));
                        analyzeAlarmInfoWin* win = new analyzeAlarmInfoWin(point);
                        win->setObjectName(objectName);
                        m_analyzeAlarmInfoWins.append(win); // 将弹窗添加到成员变量中
                        if (!win)
                        {
                            // 调试信息:确认 alarmViewer 对象创建失败
                            qDebug() << "alarmViewer create failed!";
                            return;
                        }
                        // 调试信息:确认 alarmViewer 对象成功创建
                        qDebug() << "win:" << objectName << " create successful!";

                        win->setAttribute(Qt::WA_DeleteOnClose);
                        
                        win->show(); // 显示 AlarmViewer 界面
                        connect(win, &analyzeAlarmInfoWin::destroyed, this, [this, win]() {
                            // 在 m_analyzeAlarmInfoWins 中查找要删除的对象
                            for (int i = 0; i < m_analyzeAlarmInfoWins.size(); ++i) {
                                if (m_analyzeAlarmInfoWins.at(i) == win) {
                                    m_analyzeAlarmInfoWins.removeAt(i); // 从列表中删除对象
                                    //qDebug() << "Object removed from list";
                                    break; // 找到并删除对象后退出循环
                                }
                            }
                        });
                    }
                    break;
                }
            }
        }

        if (!foundPoint) {
            juleiPoints.append(newAlarm);
            if (isTanchuang)
            {
                //打开弹窗
                QString objectName = QString::number(floor(newAlarm.location));
                analyzeAlarmInfoWin* win = new analyzeAlarmInfoWin(newAlarm);
                win->setObjectName(objectName);
                m_analyzeAlarmInfoWins.append(win); // 将弹窗添加到成员变量中
                if (!win)
                {
                    // 调试信息:确认 alarmViewer 对象创建失败
                    qDebug() << "alarmViewer create failed!";
                    return;
                }
                // 调试信息:确认 alarmViewer 对象成功创建
                qDebug() << "win:" << objectName << " create successful!";

                win->setAttribute(Qt::WA_DeleteOnClose);
                win->show(); // 显示 AlarmViewer 界面
                connect(win, &analyzeAlarmInfoWin::destroyed, this, [this, win]() {
                    qDebug() << "Deleting object with name: " << win->objectName();
                    // 在 m_analyzeAlarmInfoWins 中查找要删除的对象
                    for (int i = 0; i < m_analyzeAlarmInfoWins.size(); ++i) {
                        if (m_analyzeAlarmInfoWins.at(i) == win) {
                            m_analyzeAlarmInfoWins.removeAt(i); // 从列表中删除对象
                            break; // 找到并删除对象后退出循环
                        }
                    }
                });
            }
        }
    }
    
    //表格中添加新的报警点
    alarmPoints.append(newAlarm);

    // Update the UI to display the alarm points
    tableWidget->setRowCount(alarmPoints.size());
    for (int i = 0; i < alarmPoints.size(); ++i) {
        const AlarmPoint& alarm = alarmPoints[i];
        QTableWidgetItem* itemTime = new QTableWidgetItem(alarm.time);
        QTableWidgetItem* itemLocation = new QTableWidgetItem(QString::number(alarm.location));
        QTableWidgetItem* itemType = new QTableWidgetItem(QString::number(alarm.type));
        QTableWidgetItem* itemChannel = new QTableWidgetItem(QString::number(alarm.channel));

        tableWidget->setItem(i, 0, itemTime);
        tableWidget->setItem(i, 1, itemLocation);
        tableWidget->setItem(i, 2, itemType);
        tableWidget->setItem(i, 3, itemChannel);
    }

    //记录聚类中心的个数
    juleicount++;
    
    // 更新定时器触发次数
    ++timerCount;

    // 如果触发次数达到10次,则关闭定时器
    if (timerCount >= 10) {
        timer->stop();
    }
}

2.3 处理复选框状态变化的槽函数
void alarmMessageTab::popupCheckboxStateChanged(int state)
{
    if (state == Qt::Checked)
    {
        qDebug() << "Pop up function selected";
        if (!isTanchuang) {
            qDebug() << "test";
            isTanchuang = true;
        }
    }
    else
    {
        qDebug() << "Pop up function not selected";
        if (isTanchuang) {
            qDebug() << "test";
            isTanchuang = false;
        }
    }
}

二、弹窗界面的展示

1. analyzeAlarmInfoWin.h

#pragma once

#include "ui_analyzeAlarmInfoWin.h"
#include <qtCommon.h>
class analyzeAlarmInfoWin : public QWidget
{
	Q_OBJECT

public:
	analyzeAlarmInfoWin(AlarmPoint& alarm,QWidget *parent = nullptr);
	~analyzeAlarmInfoWin();

	QPushButton* playButton = nullptr;
	

	// 创建QMediaPlayer对象
	QMediaPlayer* player = nullptr;
	QMediaPlaylist* playList = nullptr; //创建 播放列别

	QTableWidget* tableWidget = nullptr;
	QVBoxLayout* layout = nullptr;
	
	int maxAlarmPoints = 100;	//设置表格最大行数
	
	
	int screenWidth = 0;	//获取屏幕的宽和高
	int screenHeight = 0;
	int stepX = 1;			//两个方向的步进距离
	int stepY = 1;

protected:
	void mouseMoveEvent(QMouseEvent* event) override;
	void mousePressEvent(QMouseEvent* event) override;
private:
	Ui::analyzeAlarmInfoWinClass ui;
	QTimer* flashTimer = nullptr; // 用于控制闪烁的定时器
	QTimer* moveTimer = nullptr; // 用于控制移动的定时器
	bool isPlay = true;			//播放状态
	bool flashState = false; // 闪烁状态
private slots:
	void toggleBackgroundColor();//槽函数:背景颜色相关
	void playButtonClicked();	//槽函数:按钮播放警报声
	void moveWidget();			//槽函数:窗口移动函数

};

2. analyzeAlarmInfoWin.cpp

#include "analyzeAlarmInfoWin.h"

analyzeAlarmInfoWin::analyzeAlarmInfoWin(AlarmPoint& alarm, QWidget *parent)
	: QWidget(parent)
{
	ui.setupUi(this);
    // 调整整体界面大小
    this->resize(350, 260);

    // 获取主屏幕,这里还可以【优化:不用创建一个对象,就执行一次,可以将该类变成有参构造,传参数进来就可以】
    QScreen* screen = QGuiApplication::primaryScreen();
    screenWidth = screen->size().width();                   // 获取屏幕的宽度和高度
    screenHeight = screen->size().height();
    move(0, 0);                                             // 初始化窗口(this)位置,将窗口放在屏幕左上角

    // QMediaPlay相关
    playList = new QMediaPlaylist(this);                    // 创建播放列别
    player = new QMediaPlayer(this);
    QString audioFilePath = WAV_FILE_PATH;                  // 设置音频文件路径
    playList->addMedia(QUrl::fromLocalFile(audioFilePath));
    playList->setPlaybackMode(QMediaPlaylist::Loop);        // 设置播放模式为循环
    player->setPlaylist(playList);                          // 设置 播放列表到播放器
    player->setVolume(90);                                  // 设置播放器
    player->play();                                         // 播放音频

    // 界面闪烁相关
    flashTimer = new QTimer(this);
    connect(flashTimer, &QTimer::timeout, this, &analyzeAlarmInfoWin::toggleBackgroundColor);
    flashTimer->start(1000);                                // 每1000毫秒切换一次颜色
    setMouseTracking(true);                                 // 启用鼠标追踪

    // 界面移动相关
    moveTimer = new QTimer(this);                           // 设置定时器,每隔一段时间移动一次窗口
    connect(moveTimer, &QTimer::timeout, this, &analyzeAlarmInfoWin::moveWidget);
    moveTimer->start(25);                                   // 设置定时器间隔为25毫秒
    
    // 界面按钮相关
    playButton = new QPushButton(QString::fromLocal8Bit("关闭警报声"));
    connect(playButton, &QPushButton::clicked, this, &analyzeAlarmInfoWin::playButtonClicked);// 在初始化函数中连接按钮的clicked信号到槽函数

    // 界面表格相关
    tableWidget = new QTableWidget(this);
    tableWidget->setRowCount(5);
    QStringList headers;
    headers << QString::fromLocal8Bit("报警(本地)") << QString::fromLocal8Bit("位置(米(m))") << QString::fromLocal8Bit("扰动类型") << QString::fromLocal8Bit("通道") << QString::fromLocal8Bit("出现次数")/* << QString::fromLocal8Bit("可能性") << QString::fromLocal8Bit("备注")*/;
    tableWidget->setVerticalHeaderLabels(headers);
    tableWidget->horizontalHeader()->setVisible(false);      // 去除水平标题行
    tableWidget->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); // 将行拉伸以填充表格,去除最后一行留白
    tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); // Stretch columns to fill the table
    tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); // Disable editing of table cells
    tableWidget->setColumnCount(1);
    QTableWidgetItem* itemTime = new QTableWidgetItem(alarm.time);
    QTableWidgetItem* itemLocation = new QTableWidgetItem(QString::number(alarm.location));
    QTableWidgetItem* itemType = new QTableWidgetItem(QString::number(alarm.type));
    QTableWidgetItem* itemChannel = new QTableWidgetItem(QString::number(alarm.channel));
    QTableWidgetItem* itemCount = new QTableWidgetItem(QString::number(alarm.count));
    tableWidget->setItem( 0, 0, itemTime);
    tableWidget->setItem(1, 0, itemLocation);
    tableWidget->setItem(2, 0, itemType);
    tableWidget->setItem(3, 0, itemChannel);
    tableWidget->setItem(4, 0, itemCount);

    // 界面布局相关
    layout = new QVBoxLayout(this);
    layout->addWidget(playButton);
    layout->addWidget(tableWidget);
    setLayout(layout);
}

analyzeAlarmInfoWin::~analyzeAlarmInfoWin()
{}
/*
 * 函数:toggleBackgroundColor
 * -------------------------------
 * 此函数用于在 analyzeAlarmInfoWin 窗口部件的背景颜色之间切换,可切换为白色或红色。
 * 首先获取窗口部件的当前调色板,然后根据 flashState 变量设置背景颜色为白色或红色。
 * 设置颜色后,更新窗口部件的调色板,并切换 flashState 变量以供下一次调用使用。
 */

void analyzeAlarmInfoWin::toggleBackgroundColor() {
    QPalette pal = this->palette();
    if (flashState) {
        pal.setColor(QPalette::Background, Qt::white);
    }
    else {
        pal.setColor(QPalette::Background, Qt::red);
    }
    this->setPalette(pal);
    flashState = !flashState;
}

/*
 * 函数:mouseMoveEvent
 * -------------------------------
 * 此函数重写了 analyzeAlarmInfoWin 窗口部件的鼠标移动事件处理函数。在鼠标移动时,如果 moveTimer 正在运行,则停止它。
 * 如果 flashTimer 也在运行,则停止它,并将背景颜色设置为白色以停止闪烁效果。最后调用父类的鼠标移动事件处理函数。
 */
void analyzeAlarmInfoWin::mouseMoveEvent(QMouseEvent* event) {
    // 停止界面移动
    if (moveTimer->isActive()) {
        moveTimer->stop();
    }
    if (flashTimer->isActive()) {
        flashTimer->stop();
        QPalette pal = this->palette();
        pal.setColor(QPalette::Background, Qt::white); // 停止闪烁后设置为白色背景
        this->setPalette(pal);
    }
    QWidget::mouseMoveEvent(event);
}

/*
 * 函数:mousePressEvent
 * -------------------------------
 * 此函数重写了 analyzeAlarmInfoWin 窗口部件的鼠标按下事件处理函数。在鼠标按下时,如果 moveTimer 正在运行,则停止它。
 * 如果 flashTimer 也在运行,则停止它,并将背景颜色设置为白色以停止闪烁效果。最后调用父类的鼠标按下事件处理函数。
 */
void analyzeAlarmInfoWin::mousePressEvent(QMouseEvent* event) {
    
    // 停止界面移动
    if (moveTimer->isActive()) {
        moveTimer->stop();
    }
    if (flashTimer->isActive()) {
        flashTimer->stop();
        QPalette pal = this->palette();
        pal.setColor(QPalette::Background, Qt::white); // 停止闪烁后设置为白色背景
        this->setPalette(pal);
    }
    QWidget::mousePressEvent(event);
}

/*
 * 函数:playButtonClicked
 * -------------------------------
 * 此函数响应 analyzeAlarmInfoWin 窗口部件中播放按钮的点击事件。根据 isPlay 变量的状态,切换播放按钮的文本内容和播放状态。
 * 如果 isPlay 为 true,则将按钮文本设置为“播放警报声”,停止播放器的播放,并将 isPlay 置为 false。
 * 如果 isPlay 为 false,则将按钮文本设置为“关闭警报声”,开始播放器的播放,并将 isPlay 置为 true。
 */
void analyzeAlarmInfoWin::playButtonClicked()
{
    if (isPlay)
    {
        playButton->setText(QString::fromLocal8Bit("播放警报声"));
        player->stop();
        isPlay = false;
    }
    else
    {
        playButton->setText(QString::fromLocal8Bit("关闭警报声"));
        player->play();
        isPlay = true;
    }
}

/*
 * 函数:moveWidget
 * -------------------------------
 * 此函数用于移动 analyzeAlarmInfoWin 窗口部件的位置。首先获取当前窗口位置的 x 和 y 坐标。
 * 然后计算新的窗口位置,移动方向为右下角,每次移动一个步长(stepX, stepY)。
 * 如果窗口移动到了屏幕边界,则改变移动方向。如果到达左右边界,则水平方向反向移动;如果到达上下边界,则垂直方向反向移动。
 * 最后更新窗口的位置,实现窗口的移动效果。
 */
void analyzeAlarmInfoWin::moveWidget()
{
    // 获取当前窗口位置
    int x = pos().x();
    int y = pos().y();

    // 计算新的窗口位置,移动方向为右下角
    int newX = x + 1;
    int newY = y + 1;

    // 如果窗口移动到了屏幕边界,改变移动方向
    if (newX >= (screenWidth - this->width()) || newX <= 0) {
        // 到达屏幕左右边界,改变水平方向
        stepX = -stepX;
    }
    if (newY >= (screenHeight - this->height()) || newY <= 0) {
        // 到达屏幕上下边界,改变垂直方向
        stepY = -stepY;
    }

    // 更新窗口位置
    move(x + stepX, y + stepY);
}


三、其他头文件修改

qtCommon.h

#pragma once
#include <iostream>

#include <QWidget>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QTableWidget>
#include <QTimer>
#include <QHeaderView>
#include <QDateTime>
#include <QMenu>
#include <qmessagebox.h>

#include <QMediaPlayer>
#include <QMediaPlaylist>
#include <QUrl>
#include <qfile.h>


#include <Eigen/Dense>
#include "fftw3.h"
#include <qcustomplot.h>
#define WAV_FILE_PATH "C:/code_fiOTDR/ALARM.wav"
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixXd_RowMajor;
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor> MatrixXd_ColMajor;

struct AlarmPoint {
    QString time;
    double location;
    int type;
    int channel;
    int count;
};

四、主函数和解决方案资源管理器

#include "alarmMessageTab.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    alarmMessageTab w;
    w.show();
    return a.exec();
}


在这里插入图片描述


其他设置

为了代码能跑起来,在上一篇博客的基础上,增加以下设置请参考。

QMediaPlayer设置

  • 将箭头所指的模块加进去。
    在这里插入图片描述请注意,如果不加会有如下错误:
    在这里插入图片描述

代码和数据下载

我已经将我的代码和音频文件上传,免费下载!支持开源!!放百度网盘的链接,有音频文件方便测试,自取哈!
链接:https://pan.baidu.com/s/1YnKLWN8O3gPBTdMqK19YKg?pwd=yyds
提取码:yyds
–来自百度网盘超级会员V6的分享

总结

敲代码的过程遇到的问题:

  1. 弹窗界面应该怎么设计?
    尽可能要让界面简单,少计算。
  2. 头文件重复包含的问题

Go on!

  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值