上传文件至 'WeighUI'

Signed-off-by: 邵朱铭 <shaozm@auseft.com>
main
邵朱铭 3 days ago
parent 71b42aa414
commit ba8847d7b4

@ -0,0 +1,43 @@
#include "weighui.h"
#include <QApplication>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QDebug>
#include <QDir>
int main(int argc, char *argv[])
{
// 使用 linuxfb 平台
qputenv("QT_QPA_PLATFORM", "linuxfb");
qputenv("QT_PLUGIN_PATH", "/userdata/lib/qt/plugins");
QApplication a(argc, argv);
// ── 解析命令行参数 ──
QCommandLineParser parser;
parser.setApplicationDescription("WeighUI - 称重系统 UI");
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption domainOpt(QStringList() << "d" << "domain",
"DDS domain id (default 0).",
"id", "0");
parser.addOption(domainOpt);
parser.process(a);
bool ok = false;
int domainId = parser.value(domainOpt).toInt(&ok);
if (!ok || domainId < 0) {
qWarning() << "Invalid --domain value:" << parser.value(domainOpt)
<< ", fallback to 0";
domainId = 0;
}
qDebug() << "WeighUI 启动, DDS domain =" << domainId;
WeighUI w(domainId);
w.show();
return a.exec();
}

@ -0,0 +1,51 @@
import QtQuick 2.15
Rectangle {
id: root
width: 800
height: 1280
color: "white"
Image {
id: backgroundImage
anchors.fill: parent
source: "qrc:/123.png"
fillMode: Image.PreserveAspectFit
smooth: true
Component.onCompleted: {
console.log("QML: 图片加载中...")
}
onStatusChanged: {
if (status === Image.Error) {
console.log("QML: 图片加载失败")
placeholderText.visible = true
} else if (status === Image.Ready) {
console.log("QML: 图片加载成功")
}
}
}
Text {
id: placeholderText
anchors.centerIn: parent
text: "背景图片加载失败"
color: "red"
font.pixelSize: 24
visible: false
}
Text {
anchors.centerIn: parent
text: "QML 界面已显示"
font.pixelSize: 40
color: "blue"
visible: false
}
Component.onCompleted: {
console.log("QML: 界面初始化完成")
placeholderText.visible = true
}
}

@ -0,0 +1,611 @@
#include "weighui.h"
#include "./ui_weighui.h"
#include <QTimer>
#include <QPixmap>
#include <QDebug>
#include <QCloseEvent>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QTransform>
#include <QLabel>
#include <QFrame>
#include <thread>
#include <mutex>
#include <queue>
#include <utility>
#include "dds/Subscriber.hpp"
#include "dds/msg.hpp"
// DdsMsgData 静态成员定义
std::queue<WeighingSystem::WeightInfoOk> DdsMsgData::WeightInfoOk_queue_;
std::queue<WeighingSystem::WeightInfoError> DdsMsgData::WeightInfoError_queue_;
std::queue<WeighingSystem::SummaryUpdate> DdsMsgData::SummaryUpdate_queue_;
std::queue<WeighingSystem::ScaleInfo> DdsMsgData::ScaleInfo_queue_;
std::mutex DdsMsgData::queue_cv_mtx_;
// 实时弹窗重量阈值: weigh 程序中 ScaleInfo.Value 单位为吨, 10kg = 0.01 吨
static const float kWeightThresholdTon = 0.01f; // 10 kg
WeighUI::WeighUI(int domainId, QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::WeighUI)
, m_imageLabel(nullptr)
, m_popupWidget(nullptr)
, m_ddsSubscriber(nullptr)
, m_ddsThread(nullptr)
, m_soundEffect(nullptr)
, m_soundReadCardStart(nullptr)
, m_soundError(nullptr)
// 实时重量显示(嵌入主窗口内的子控件, 不进窗口管理器; 视觉与 Warning 弹窗一致: 离屏渲染 -> rotate(90))
, m_realTimeWeightContainer(nullptr)
, m_realTimeWeightImage(nullptr)
, m_realTimePopupSource(nullptr)
, m_realTimeSourceWeight(nullptr)
, m_realTimeLastWeightText()
, m_realTimePopupEnabled(false)
, m_realTimePopupVisible(false)
, m_domainId(domainId)
{
qDebug() << "=== WeighUI 构造函数开始 ===";
ui->setupUi(this);
qDebug() << "setupUi 完成, 窗口大小:" << width() << "x" << height();
this->setAttribute(Qt::WA_DeleteOnClose);
// 设置窗口为全屏无边框(适应 800x1280 竖屏)
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
this->resize(800, 1280);
this->showFullScreen();
this->raise();
this->activateWindow();
qDebug() << "窗口大小已调整为:" << width() << "x" << height();
// 隐藏菜单栏和状态栏
ui->menubar->hide();
ui->statusbar->hide();
// 设置 centralwidget 透明并隐藏
ui->centralwidget->setStyleSheet("background-color: transparent;");
ui->centralwidget->hide();
// 创建图片显示Label
m_imageLabel = new QLabel(this);
m_imageLabel->setAlignment(Qt::AlignCenter);
m_imageLabel->setScaledContents(true);
m_imageLabel->setGeometry(0, 0, width(), height());
m_imageLabel->show();
// 加载图片并旋转90度
QString imagePath = QCoreApplication::applicationDirPath() + "/123.png";
qDebug() << "尝试加载图片:" << imagePath;
QPixmap pixmap(imagePath);
if (!pixmap.isNull()) {
QTransform transform;
transform.rotate(90);
QPixmap rotatedPixmap = pixmap.transformed(transform);
m_imageLabel->setPixmap(rotatedPixmap.scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
qDebug() << "图片加载并旋转成功!";
} else {
qDebug() << "图片加载失败!";
}
// 创建定时器
m_closeTimer = new QTimer(this);
m_pollTimer = new QTimer(this);
m_errorPollTimer = new QTimer(this);
m_summaryPollTimer = new QTimer(this);
m_realTimePollTimer = new QTimer(this);
m_enablePopupTimer = new QTimer(this);
connect(m_closeTimer, &QTimer::timeout, this, &WeighUI::onPopupClosed);
connect(m_pollTimer, &QTimer::timeout, this, &WeighUI::onCheckWeightInfoOk);
connect(m_errorPollTimer, &QTimer::timeout, this, &WeighUI::onCheckWeightInfoError);
connect(m_summaryPollTimer, &QTimer::timeout, this, &WeighUI::onCheckSummaryUpdate);
connect(m_realTimePollTimer, &QTimer::timeout, this, &WeighUI::onCheckRealTimeWeight);
connect(m_enablePopupTimer, &QTimer::timeout, this, &WeighUI::onEnableRealTimePopup);
// 加载称重完成提示音
QString soundPath = QCoreApplication::applicationDirPath() + "/voice2.0/sound19.wav";
qDebug() << "加载提示音:" << soundPath;
m_soundEffect = new QSoundEffect(this);
m_soundEffect->setSource(QUrl::fromLocalFile(soundPath));
m_soundEffect->setVolume(1.0f);
// 加载读卡成功提示音
QString readCardSoundPath = QCoreApplication::applicationDirPath() + "/voice2.0/readcardstart.wav";
qDebug() << "加载读卡提示音:" << readCardSoundPath;
m_soundReadCardStart = new QSoundEffect(this);
m_soundReadCardStart->setSource(QUrl::fromLocalFile(readCardSoundPath));
m_soundReadCardStart->setVolume(1.0f);
// 加载称重异常提示音
QString errorSoundPath = QCoreApplication::applicationDirPath() + "/voice2.0/sound51.wav";
qDebug() << "加载称重异常提示音:" << errorSoundPath;
m_soundError = new QSoundEffect(this);
m_soundError->setSource(QUrl::fromLocalFile(errorSoundPath));
m_soundError->setVolume(1.0f);
// 构造实时重量显示控件(嵌入主窗口内的子控件, 默认隐藏)
buildRealTimeWeightDisplay();
// 启动 DDS 订阅 WeightInfoOk
startDdsSubscriber();
// 启动轮询定时器(每 200ms 检查队列)
m_pollTimer->start(200);
m_errorPollTimer->start(200);
m_summaryPollTimer->start(200);
m_realTimePollTimer->start(200);
// 启动5秒后启用实时弹窗的定时器
m_enablePopupTimer->setSingleShot(true);
m_enablePopupTimer->start(5000);
qDebug() << "=== WeighUI 构造函数结束(等待 WeightInfoOk 触发弹窗)===";
}
WeighUI::~WeighUI()
{
stopDdsSubscriber();
destroyRealTimeWeightDisplay();
delete ui;
}
void WeighUI::startDdsSubscriber()
{
try {
qDebug() << "DDS Subscriber 启动, domainId=" << m_domainId;
m_ddsSubscriber = std::make_shared<DdsSubscriberApp>(m_domainId);
m_ddsThread = new std::thread([this]() { m_ddsSubscriber->run(); });
qDebug() << "DDS Subscriber 启动成功";
} catch (const std::exception& e) {
qDebug() << "DDS Subscriber 启动失败:" << e.what();
m_ddsSubscriber = nullptr;
m_ddsThread = nullptr;
}
}
void WeighUI::stopDdsSubscriber()
{
if (m_ddsSubscriber) {
m_ddsSubscriber->stop();
}
if (m_ddsThread) {
if (m_ddsThread->joinable()) {
m_ddsThread->join();
}
delete m_ddsThread;
m_ddsThread = nullptr;
}
m_ddsSubscriber.reset();
}
void WeighUI::closeEvent(QCloseEvent *event)
{
stopDdsSubscriber();
if (m_pollTimer) m_pollTimer->stop();
if (m_errorPollTimer) m_errorPollTimer->stop();
if (m_summaryPollTimer) m_summaryPollTimer->stop();
if (m_realTimePollTimer) m_realTimePollTimer->stop();
if (m_enablePopupTimer) m_enablePopupTimer->stop();
if (m_closeTimer) m_closeTimer->stop();
destroyRealTimeWeightDisplay();
hide();
QTimer::singleShot(100, [this]() {
deleteLater();
});
event->accept();
}
void WeighUI::onCheckSummaryUpdate()
{
bool hasData = false;
{
std::unique_lock<std::mutex> lock(DdsMsgData::queue_cv_mtx_);
while (!DdsMsgData::SummaryUpdate_queue_.empty())
{
// 全部清空,只关心 "有一次 SummaryUpdate" 即可触发声音(避免重复堆积)
DdsMsgData::SummaryUpdate_queue_.pop();
hasData = true;
}
}
if (hasData) {
if (m_soundReadCardStart && m_soundReadCardStart->isLoaded()) {
m_soundReadCardStart->stop();
m_soundReadCardStart->play();
}
}
}
void WeighUI::onCheckWeightInfoOk()
{
QString warningText;
bool hasData = false;
{
std::unique_lock<std::mutex> lock(DdsMsgData::queue_cv_mtx_);
if (!DdsMsgData::WeightInfoOk_queue_.empty()) {
WeighingSystem::WeightInfoOk info = std::move(DdsMsgData::WeightInfoOk_queue_.front());
DdsMsgData::WeightInfoOk_queue_.pop();
lock.unlock();
warningText = QString::fromStdString(info.Warning());
if (warningText.isEmpty()) {
warningText = "称重完成";
}
hasData = true;
qDebug() << "[WeighUI] 收到 WeightInfoOk, Warning =" << warningText;
}
}
if (hasData) {
// 播放提示音
if (m_soundEffect && m_soundEffect->isLoaded()) {
m_soundEffect->stop();
m_soundEffect->play();
}
onShowPopup(warningText);
}
}
void WeighUI::onCheckWeightInfoError()
{
QString warningText;
bool hasData = false;
{
std::unique_lock<std::mutex> lock(DdsMsgData::queue_cv_mtx_);
if (!DdsMsgData::WeightInfoError_queue_.empty()) {
WeighingSystem::WeightInfoError info = std::move(DdsMsgData::WeightInfoError_queue_.front());
DdsMsgData::WeightInfoError_queue_.pop();
lock.unlock();
warningText = QString::fromStdString(info.Warning());
if (warningText.isEmpty()) {
warningText = "称重异常";
}
hasData = true;
qDebug() << "[WeighUI] 收到 WeightInfoError, Warning =" << warningText;
}
}
if (hasData) {
// 弹窗样式与 WeightInfoOk 完全一致: 复用 onShowPopup
if (m_soundError && m_soundError->isLoaded()) {
m_soundError->stop();
m_soundError->play();
}
onShowPopup(warningText);
}
}
void WeighUI::onShowPopup(const QString& warningText)
{
if (m_popupWidget) {
m_popupWidget->close();
m_popupWidget->deleteLater();
m_popupWidget = nullptr;
}
// 创建弹窗容器横屏尺寸宽800高500
QWidget *popupSource = new QWidget(this);
popupSource->setWindowFlags(Qt::FramelessWindowHint | Qt::Window);
popupSource->resize(800, 500);
// 内部布局:上方标题 + 下方信息Warning 文本)
QVBoxLayout *mainLayout = new QVBoxLayout(popupSource);
mainLayout->setContentsMargins(20, 20, 20, 20);
mainLayout->setSpacing(20);
mainLayout->setAlignment(Qt::AlignCenter);
// 上方:标题
QLabel *titleLabel = new QLabel("称重提示", popupSource);
titleLabel->setAlignment(Qt::AlignCenter);
titleLabel->setStyleSheet("font-size: 48px; font-weight: bold; color: #4169E1; background: transparent;");
QFrame *line = new QFrame(popupSource);
line->setFrameShape(QFrame::HLine);
line->setStyleSheet("background-color: #4169E1;");
// 下方信息Warning 文本)
QLabel *warningLabel = new QLabel(warningText, popupSource);
warningLabel->setAlignment(Qt::AlignCenter);
warningLabel->setWordWrap(true);
warningLabel->setStyleSheet("font-size: 42px; font-weight: bold; color: #FF4500; background: transparent;");
mainLayout->addWidget(titleLabel);
mainLayout->addWidget(line);
mainLayout->addWidget(warningLabel);
// 把弹窗渲染成图片
popupSource->show();
popupSource->repaint();
QPixmap popupPixmap = popupSource->grab();
// 关闭原弹窗
popupSource->close();
popupSource->deleteLater();
// 旋转90度跟图片一致
QTransform transform;
transform.rotate(90);
QPixmap rotatedPixmap = popupPixmap.transformed(transform, Qt::SmoothTransformation);
// 创建新弹窗显示旋转后的图片
m_popupWidget = new QWidget(this);
m_popupWidget->setWindowFlags(Qt::FramelessWindowHint | Qt::Window);
m_popupWidget->setStyleSheet(
"background-color: rgba(240, 248, 255, 240);"
"border: 3px solid #4169E1;"
"border-radius: 15px;"
);
m_popupWidget->resize(rotatedPixmap.width(), rotatedPixmap.height());
QLabel *imageLabel = new QLabel(m_popupWidget);
imageLabel->setPixmap(rotatedPixmap);
imageLabel->setScaledContents(true);
imageLabel->setGeometry(0, 0, rotatedPixmap.width(), rotatedPixmap.height());
m_popupWidget->move((width() - rotatedPixmap.width()) / 2, (height() - rotatedPixmap.height()) / 2);
m_popupWidget->show();
// 5秒后关闭弹窗
m_closeTimer->setSingleShot(true);
m_closeTimer->start(5000);
}
void WeighUI::onPopupClosed()
{
if (m_popupWidget) {
m_popupWidget->close();
m_popupWidget->deleteLater();
m_popupWidget = nullptr;
}
}
// ----------------------------------------------------------------------------
// 实时重量显示: 在主窗口内嵌入一个 500x800 的子控件,
// 不再是独立的顶层窗口, 完全不经过 X11/Wayland 窗口管理器,
// 避免 show/hide 与 Warning 弹窗抢焦点、避免被窗口管理器反复隐藏.
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// 实时重量显示: 完全复用 Warning 弹窗的视觉风格
// 离屏源 800x500 横屏 -> 离屏渲染 -> 旋转 90° -> 嵌入主窗口的 500x800 容器内显示
// 容器直接挂在 m_imageLabel 下, 不是顶层窗口, 不会被 X11/Wayland 窗口管理器反复隐藏
// ----------------------------------------------------------------------------
void WeighUI::buildRealTimeWeightDisplay()
{
if (m_realTimeWeightContainer) {
return;
}
// ---------- 1. 离屏渲染源 (800x500 横屏, 持久, 不 show) ----------
// 与 Warning 弹窗的 popupSource 结构完全一致: 标题 + 分隔线 + 文本
m_realTimePopupSource = new QWidget(); // 不挂任何 parent, 避免被 WeighUI 路由
m_realTimePopupSource->setAttribute(Qt::WA_DontShowOnScreen, true);
// 关键: 用 setFixedSize 锁定 800x500, 不要让 layout/adjustSize 把它缩成 251x211
m_realTimePopupSource->setFixedSize(800, 500);
QVBoxLayout *srcLayout = new QVBoxLayout(m_realTimePopupSource);
srcLayout->setContentsMargins(20, 20, 20, 20);
srcLayout->setSpacing(20);
srcLayout->setAlignment(Qt::AlignCenter);
// 标题 (与 Warning 弹窗标题样式一致)
QLabel *srcTitle = new QLabel(QStringLiteral("实时称重"), m_realTimePopupSource);
srcTitle->setAlignment(Qt::AlignCenter);
srcTitle->setStyleSheet(
"font-size: 48px;"
"font-weight: bold;"
"color: #4169E1;"
"background: transparent;"
);
srcLayout->addWidget(srcTitle);
// 分隔线 (与 Warning 弹窗一致)
QFrame *srcLine = new QFrame(m_realTimePopupSource);
srcLine->setFrameShape(QFrame::HLine);
srcLine->setStyleSheet("background-color: #4169E1;");
srcLayout->addWidget(srcLine);
// 重量 Label (与 Warning 弹窗中 warning 文本一致)
m_realTimeSourceWeight = new QLabel(QStringLiteral("重量: -- kg"), m_realTimePopupSource);
m_realTimeSourceWeight->setAlignment(Qt::AlignCenter);
m_realTimeSourceWeight->setWordWrap(true);
m_realTimeSourceWeight->setStyleSheet(
"font-size: 42px;"
"font-weight: bold;"
"color: #FF4500;"
"background: transparent;"
);
srcLayout->addWidget(m_realTimeSourceWeight);
// 强制布局完成, 这样 render() 能拿到正确尺寸
m_realTimePopupSource->adjustSize();
// ---------- 2. 显示容器 (500x800, 嵌入 m_imageLabel) ----------
if (m_imageLabel) {
m_realTimeWeightContainer = new QWidget(m_imageLabel);
} else {
m_realTimeWeightContainer = new QWidget(this);
}
m_realTimeWeightContainer->setFixedSize(500, 800);
m_realTimeWeightContainer->setStyleSheet(
"QWidget {"
" background-color: rgb(240, 248, 255);"
" border: 3px solid #4169E1;"
" border-radius: 15px;"
"}"
);
m_realTimeWeightContainer->setAutoFillBackground(true);
// 容器内只有一个 QLabel, 显示旋转后的 pixmap
m_realTimeWeightImage = new QLabel(m_realTimeWeightContainer);
m_realTimeWeightImage->setScaledContents(true);
m_realTimeWeightImage->setAlignment(Qt::AlignCenter);
m_realTimeWeightImage->setGeometry(0, 0, 500, 800);
// 把容器放到 m_imageLabel 中央
int cx = 0, cy = 0;
if (m_imageLabel) {
cx = (m_imageLabel->width() - 500) / 2;
cy = (m_imageLabel->height() - 800) / 2;
} else {
cx = (width() - 500) / 2;
cy = (height() - 800) / 2;
}
m_realTimeWeightContainer->setGeometry(cx, cy, 500, 800);
// 初始渲染一次 (空文本), 让首帧就有图
{
QPixmap pixmap = renderRealTimePopupContent(QStringLiteral("-- kg"));
m_realTimeWeightImage->setPixmap(pixmap);
}
// 初始隐藏, 等 onCheckRealTimeWeight 触发显示
m_realTimeWeightContainer->hide();
m_realTimePopupVisible = false;
qDebug() << "[WeighUI] buildRealTimeWeightDisplay: container"
<< m_realTimeWeightContainer
<< "parent=" << m_realTimeWeightContainer->parent()
<< "size=" << m_realTimeWeightContainer->size()
<< "source=" << m_realTimePopupSource
<< "sourceSize=" << m_realTimePopupSource->size();
}
void WeighUI::destroyRealTimeWeightDisplay()
{
if (m_realTimeWeightContainer) {
m_realTimeWeightContainer->deleteLater();
m_realTimeWeightContainer = nullptr;
}
m_realTimeWeightImage = nullptr;
if (m_realTimePopupSource) {
m_realTimePopupSource->deleteLater();
m_realTimePopupSource = nullptr;
}
m_realTimeSourceWeight = nullptr;
m_realTimeLastWeightText.clear();
m_realTimePopupVisible = false;
}
void WeighUI::onEnableRealTimePopup()
{
m_realTimePopupEnabled = true;
qDebug() << "[WeighUI] 实时重量显示已启用 (启动 5 秒后)";
// 立即触发一次轮询, 根据当前最新重量决定是否显示
onCheckRealTimeWeight();
}
// 渲染一次实时弹窗内容, 返回旋转 90 度后的 QPixmap (与 Warning 弹窗同款)
// 注意: 使用 m_realTimePopupSource(持久离屏源)避免每帧 new/delete 控件;
// 文本未变化时复用上次缓存的 pixmap, 不重复渲染.
QPixmap WeighUI::renderRealTimePopupContent(const QString& weightText)
{
if (!m_realTimePopupSource || !m_realTimeSourceWeight) {
return QPixmap();
}
QString fullText = QStringLiteral("重量: ") + weightText;
if (fullText == m_realTimeLastWeightText && !m_realTimeLastWeightText.isEmpty()) {
// 文本未变: 直接复用上次的 pixmap, 不重新 render/rotate
// (避免每帧无意义的渲染开销, 也避免 setPixmap 重复触发 paintEvent 造成闪烁)
return m_realTimeLastPixmap;
}
// 1) 更新离屏源上的文本
m_realTimeSourceWeight->setText(fullText);
// 2) 离屏源渲染到 pixmap
QPixmap sourcePixmap(m_realTimePopupSource->size());
sourcePixmap.fill(QColor(240, 248, 255)); // 与 Warning 弹窗背景一致
m_realTimePopupSource->render(&sourcePixmap);
// 3) 旋转 90 度 (与 Warning 弹窗一致)
QTransform transform;
transform.rotate(90);
QPixmap rotatedPixmap = sourcePixmap.transformed(transform, Qt::SmoothTransformation);
m_realTimeLastWeightText = fullText;
m_realTimeLastPixmap = rotatedPixmap;
return rotatedPixmap;
}
void WeighUI::onCheckRealTimeWeight()
{
// 消费队列中所有 ScaleInfo, 只保留最新一条
float latestWeightTon = 0.0f;
bool hasWeight = false;
{
std::unique_lock<std::mutex> lock(DdsMsgData::queue_cv_mtx_);
while (!DdsMsgData::ScaleInfo_queue_.empty()) {
WeighingSystem::ScaleInfo info = std::move(DdsMsgData::ScaleInfo_queue_.front());
DdsMsgData::ScaleInfo_queue_.pop();
latestWeightTon = info.Value();
hasWeight = true;
}
}
// 启动未满5秒: 直接返回, 显示控件保持隐藏
if (!m_realTimePopupEnabled) {
return;
}
// 控件未构造: 防御性返回
if (!m_realTimeWeightContainer || !m_realTimeWeightImage) {
return;
}
// 还没有收到重量数据: 保持当前状态(不要 hide, 这是上一次闪烁的根因)
// DDS ScaleInfo 的发布频率和我们的轮询频率不一定同步,
// 如果每 200ms 轮询时 DDS 还没发, 队列就会是空的, 之前的代码会 hide + 重置 flag,
// 下一次 DDS 推到数据就又 show 一次, 形成 show/hide/show/hide 的闪烁.
// 正确做法: 没有新数据时维持上一次状态不变, 直到明确收到 "<10kg" 才 hide.
if (!hasWeight) {
return;
}
// DDS ScaleInfo.Value 的单位本身就是"吨" (见 kWeightThresholdTon 的注释)
// 显示也用吨, 保留 2 位小数: 1.42 吨
// - 不要 ×1000, 否则会把"1.42 吨"显示成"1420 kg"
QString weightText = QString::number(latestWeightTon, 'f', 2) + QStringLiteral("");
// 重量 < 10 kg (即 < 0.01 吨) -> 隐藏控件 (这是唯一会真正 hide 的路径)
if (latestWeightTon < kWeightThresholdTon) {
if (m_realTimeWeightContainer->isVisible()) {
m_realTimeWeightContainer->hide();
m_realTimePopupVisible = false;
qDebug() << "[WeighUI] 实时重量 < 10kg, 界面隐藏, weight=" << latestWeightTon << "";
}
return;
}
// 重量 >= 0.01 吨 (10 kg) -> 在主窗口内显示实时重量控件
// 关键: 离屏渲染 + 旋转 90° 与 Warning 弹窗完全一致, 视觉风格统一;
// 容器嵌入 m_imageLabel, 不是顶层窗口, 不进 X11/Wayland 窗口管理器;
// show() 只在"从无到有"时调一次, 后续只 setPixmap 不打日志, 避免闪烁.
QPixmap pixmap = renderRealTimePopupContent(weightText);
if (!pixmap.isNull() && m_realTimeWeightImage) {
m_realTimeWeightImage->setPixmap(pixmap);
}
if (!m_realTimeWeightContainer->isVisible()) {
m_realTimeWeightContainer->show();
m_realTimeWeightContainer->raise(); // 提到 m_imageLabel 内最前
m_realTimeWeightContainer->update(); // 强制重绘, 防止某些嵌入式合成器延迟
m_realTimePopupVisible = true;
qDebug() << "[WeighUI] 实时重量 >= 10kg, 界面显示, weight=" << latestWeightTon << ""
<< " container.isVisible=" << m_realTimeWeightContainer->isVisible()
<< " pos=" << m_realTimeWeightContainer->pos()
<< " size=" << m_realTimeWeightContainer->size()
<< " pixmap.size=" << pixmap.size();
}
// 已经在显示: 仅 setPixmap 更新, 不调 show, 不打日志, 避免闪烁
}

@ -0,0 +1,79 @@
#ifndef WEIGHUI_H
#define WEIGHUI_H
#include <QMainWindow>
#include <QTimer>
#include <QLabel>
#include <QPixmap>
#include <QSoundEffect>
#include <memory>
#include <thread>
class DdsSubscriberApp;
QT_BEGIN_NAMESPACE
namespace Ui {
class WeighUI;
}
QT_END_NAMESPACE
class WeighUI : public QMainWindow
{
Q_OBJECT
public:
explicit WeighUI(int domainId = 0, QWidget *parent = nullptr);
~WeighUI();
private slots:
void onCheckWeightInfoOk(); // 定时检查队列,有 WeightInfoOk 数据就弹窗+播放
void onCheckWeightInfoError(); // 定时检查队列,有 WeightInfoError 数据就弹窗(样式与 WeightInfoOk 完全一致)
void onShowPopup(const QString& warningText); // 显示弹窗
void onPopupClosed(); // 弹窗关闭后处理
void onCheckSummaryUpdate(); // 定时检查 SummaryUpdate 队列,播放 readcardstart 提示音
void onCheckRealTimeWeight(); // 实时重量轮询: 根据重量显示/隐藏实时弹窗
void onEnableRealTimePopup(); // 启动5秒后启用实时弹窗
private:
// 实时重量显示: 与 onShowPopup 中 Warning 弹窗使用完全相同的手法
// 离屏 800x500 横屏 -> 离屏渲染 -> 旋转 90° -> 嵌入主窗口的 500x800 容器内显示
void startDdsSubscriber(); // 启动 DDS 订阅线程
void stopDdsSubscriber(); // 停止 DDS 订阅线程
void buildRealTimeWeightDisplay(); // 构造实时重量显示控件
void destroyRealTimeWeightDisplay(); // 销毁实时重量显示控件
QPixmap renderRealTimePopupContent(const QString& weightText); // 渲染并旋转一次实时弹窗内容
Ui::WeighUI *ui;
QTimer *m_closeTimer; // 关闭弹窗定时器
QTimer *m_pollTimer; // 轮询 WeightInfoOk 队列的定时器
QTimer *m_errorPollTimer; // 轮询 WeightInfoError 队列的定时器
QTimer *m_summaryPollTimer; // 轮询 SummaryUpdate 队列的定时器
QTimer *m_realTimePollTimer; // 轮询 ScaleInfo 队列的定时器(实时重量)
QTimer *m_enablePopupTimer; // 启动5秒后启用实时弹窗的定时器
QLabel *m_imageLabel; // 显示图片的Label
QWidget *m_popupWidget; // 弹窗Widget (Warning 弹窗, 单独顶层窗口, 5秒后自动关闭)
QSoundEffect *m_soundEffect; // 称重完成提示音 (sound19.wav)
QSoundEffect *m_soundReadCardStart; // 读卡成功提示音 (readcardstart.wav)
QSoundEffect *m_soundError; // 称重异常提示音 (sound51.wav)
// 实时重量显示控件(嵌入主窗口内的子控件, 不是顶层窗口, 不受 X11/Wayland 窗口管理器干预)
// 视觉风格与 Warning 弹窗保持一致: 离屏渲染 -> rotate(90) -> 嵌入容器显示
QWidget *m_realTimeWeightContainer; // 500x800 显示容器(嵌入到 m_imageLabel 之上)
QLabel *m_realTimeWeightImage; // 容器内的 QLabel, 显示旋转后的 pixmap
QWidget *m_realTimePopupSource; // 离屏渲染源(800x500 横屏, WA_DontShowOnScreen)
QLabel *m_realTimeSourceWeight; // 离屏源内的重量 Label
QString m_realTimeLastWeightText; // 上次渲染的文本, 文本未变时不重新渲染
QPixmap m_realTimeLastPixmap; // 缓存的上次渲染结果(旋转后的), 文本未变时直接复用
bool m_realTimePopupEnabled; // 是否已过5秒、可以显示实时弹窗
bool m_realTimePopupVisible; // 实时重量显示控件当前是否可见
std::shared_ptr<DdsSubscriberApp> m_ddsSubscriber;
std::thread *m_ddsThread; // DDS 订阅线程
int m_domainId = 0; // DDS domain id (可由 --domain 覆盖)
protected:
void closeEvent(QCloseEvent *event) override; // 关闭事件
};
#endif // WEIGHUI_H

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WeighUI</class>
<widget class="QMainWindow" name="WeighUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>WeighUI</string>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>27</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
Loading…
Cancel
Save