文章目录
前言
基于VS2019、Qt5.15.5,纯代码实现报警信息弹窗界面,在我的上一篇基础上,增加代码即可,主要知识点:QMediaPlayer相关,还有鼠标事件,窗体移动等。
效果
原视频很清晰,CSDN上传的后太模糊,估计处理了。可以移步至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的分享
总结
敲代码的过程遇到的问题:
- 弹窗界面应该怎么设计?
尽可能要让界面简单,少计算。 - 头文件重复包含的问题
Go on!