Files
Feeding_control_system/view/widgets/system_diagnostics_dialog.py

387 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QVBoxLayout,
QGridLayout,
QLabel,
QHBoxLayout,
QListWidget,
QListWidgetItem,
QSpacerItem,
QSizePolicy,
QLineEdit,
QDialog,
)
from PySide6.QtGui import QPixmap, QFont, QColor, QTransform, QPainter
from PySide6.QtCore import (
Qt,
QPoint,
QEvent,
QPropertyAnimation,
QEasingCurve,
QRect,
QParallelAnimationGroup,
)
import sys
from utils.image_paths import ImagePaths
"""
系统诊断按钮的弹窗: 可以显示设备的状态
"""
class CustomDropdown(QWidget):
"""自定义下拉框组件"""
def __init__(self, options, arrow_img_path, parent=None):
super().__init__(parent)
self.options = options
self.arrow_img_path = arrow_img_path
self.is_expanded = False
# 主布局(标签 + 箭头)
self.main_layout = QHBoxLayout(self)
self.main_layout.setContentsMargins(0, 0, 0, 0)
self.main_layout.setSpacing(0)
self.main_layout.setAlignment(Qt.AlignLeft)
self.setFixedSize(63, 19) # 需要根据下拉框需要显示的文字来修改
# self.setFixedHeight(19)
# 1. 结果显示标签QLabel无clicked信号
self.result_label = QLabel(options[0])
self.result_label.setStyleSheet(
"""
background-image: url("");
color: #16ffff;
background-color: transparent;
border: none;
padding: 0px;
font-size: 18px;
"""
)
# self.result_label.setCursor(Qt.PointingHandCursor) # 手型光标提示可点击
self.main_layout.addWidget(
self.result_label, alignment=Qt.AlignVCenter | Qt.AlignLeft
)
# 2. 可点击的箭头标签QLabel
self.arrow_label = QLabel()
self.arrow_pixmap = QPixmap(arrow_img_path)
self.arrow_label.setStyleSheet("background-image: url(" ");")
self.arrow_label.setPixmap(
self.arrow_pixmap.scaled(12, 9, Qt.KeepAspectRatio, Qt.SmoothTransformation)
)
self.arrow_label.setCursor(Qt.PointingHandCursor)
self.main_layout.addWidget(self.arrow_label, alignment=Qt.AlignTop)
# 3. 下拉选项列表(默认选中第一个)
self.list_widget = QListWidget()
self.list_widget.setWindowFlags(Qt.Popup)
# 设置选项字体
font = QFont()
font.setPixelSize(16)
# 添加所有的下拉选项
for option in options:
item = QListWidgetItem(option)
item.setTextAlignment(Qt.AlignLeft)
item.setFont(font)
self.list_widget.addItem(item)
self.list_widget.setCurrentRow(0) # 默认选中第一项
self.list_widget.itemClicked.connect(self.select_option)
# 双保险监听:全局焦点变化 + 事件过滤
self.app = QApplication.instance()
self.app.focusChanged.connect(self.on_focus_changed)
self.list_widget.installEventFilter(self)
def mousePressEvent(self, event):
"""重写鼠标点击事件实现QLabel点击功能"""
# 判断点击是否在result_label或arrow_label区域内
# if self.result_label.underMouse() or self.arrow_label.underMouse():
# self.toggle_expand()
if self.arrow_label.underMouse():
self.toggle_expand()
super().mousePressEvent(event) # 传递事件,不影响其他组件
def toggle_expand(self):
"""切换下拉框展开/收起 + 箭头旋转"""
if self.is_expanded:
self.list_widget.hide()
# 箭头恢复向下
self.arrow_label.setPixmap(self.arrow_pixmap)
else:
# 计算下拉框位置(在标签下方对齐)
label_pos = self.result_label.mapToGlobal(
QPoint(0, self.result_label.height())
)
self.list_widget.setGeometry(
label_pos.x(), label_pos.y(), self.result_label.width() + 10, 80
)
self.list_widget.show()
self.list_widget.setFocus()
# 箭头旋转180度向上
transform = QTransform().rotate(180)
rotated_pixmap = self.arrow_pixmap.transformed(
transform, Qt.SmoothTransformation
)
self.arrow_label.setPixmap(rotated_pixmap)
self.is_expanded = not self.is_expanded
def select_option(self, item):
"""选择选项后更新标签 + 收起下拉框"""
self.result_label.setText(item.text())
self.list_widget.hide()
self.arrow_label.setPixmap(self.arrow_pixmap)
self.is_expanded = False
def on_focus_changed(self, old_widget, new_widget):
"""焦点变化时关闭下拉框"""
if self.is_expanded:
is_focus_on_self = (
new_widget == self
or new_widget == self.result_label
or new_widget == self.arrow_label
or (self.list_widget.isAncestorOf(new_widget) if new_widget else False)
)
if not is_focus_on_self:
self.list_widget.hide()
self.arrow_label.setPixmap(self.arrow_pixmap)
self.is_expanded = False
def eventFilter(self, obj, event):
"""点击外部关闭下拉框"""
if obj == self.list_widget and event.type() == QEvent.MouseButtonPress:
self.list_widget.hide()
self.arrow_label.setPixmap(
self.arrow_pixmap.scaled(
12, 9, Qt.KeepAspectRatio, Qt.SmoothTransformation
)
)
self.is_expanded = False
return True
return super().eventFilter(obj, event)
def setFont(self, font):
"""设置字体"""
self.result_label.setFont(font)
for i in range(self.list_widget.count()):
self.list_widget.item(i).setFont(font)
# 获取当前选中的设备名
def get_selected_device(self):
return self.result_label.text()
class SystemDiagnosticsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setWindowOpacity(0.0)
self._init_ui()
self.init_animations()
def _init_ui(self):
# 无边框模式
self.setWindowFlags(Qt.FramelessWindowHint)
self.hide() # 初始状态为隐藏 (必须)
# 加载系统诊断弹窗的背景图片
self.bg_pixmap = QPixmap(ImagePaths.SYSTEM_DIAGNOSTICS_POPUP_BG)
if self.bg_pixmap.isNull():
print("错误: 系统诊断弹窗背景图加载失败!请检查路径是否正确")
else:
# 窗口尺寸与图片尺寸完全一致
self.setFixedSize(self.bg_pixmap.size())
# 网格布局8行4列小框
grid_layout = QGridLayout(self)
grid_layout.setContentsMargins(24, 28, 20, 24)
# 图片路径(替换为实际路径)
box_image_path = ImagePaths.SYSTEM_DIAGNOSTICS_BOX
circle_normal_path = ImagePaths.SYSTEM_DIAGNOSTICS_STATUS_GREEN # 正常状态
circle_warning_path = ImagePaths.SYSTEM_DIAGNOSTICS_STATUS_YELLOW # 警告状态
circle_error_path = ImagePaths.SYSTEM_DIAGNOSTICS_STATUS_RED # 异常状态
ms_box_path = ImagePaths.SYSTEM_DIAGNOSTICS_MS_BG
dropdown_arrow_path = ImagePaths.SYSTEM_DIAGNOSTICS_DROPDOWN_ARROW
# 字体设置
ms_font = QFont()
ms_font.setPixelSize(14)
ms_color = QColor("#14abea")
# 生成小框
for row in range(8):
for col in range(4):
box_container = QWidget()
box_container.setObjectName(f"box_{row}_{col}")
box_container.setStyleSheet(
f"""
background-image: url("{box_image_path}");
background-repeat: no-repeat;
"""
)
box_layout = QHBoxLayout(box_container)
box_layout.setSpacing(0)
# ========== 状态圆圈(支持状态切换) ==========
circle_label = QLabel()
circle_label.status = "normal"
circle_label.pixmaps = {
"normal": QPixmap(circle_normal_path),
"warning": QPixmap(circle_warning_path),
"error": QPixmap(circle_error_path),
}
circle_label.setPixmap(circle_label.pixmaps["normal"])
circle_label.setStyleSheet("background: none;")
# ========== 自定义下拉框(支持获取设备名) ==========
led_dropdown = CustomDropdown(
options=["LED1", "LED2", "LED3"], arrow_img_path=dropdown_arrow_path
)
# ========== 秒数输入框(获取毫秒值) ==========
ms_container = QWidget()
ms_layout = QHBoxLayout(ms_container)
ms_layout.setContentsMargins(6, 0, 0, 0)
ms_edit = QLineEdit("5ms")
ms_edit.setFont(ms_font)
ms_edit.setStyleSheet(
f"""
background: none;
color: {ms_color.name()};
border: none;
outline: none;
background-color: transparent;
"""
)
ms_container.setStyleSheet(
f"""
background-image: url("{ms_box_path}");
background-repeat: no-repeat;
"""
)
ms_layout.addWidget(ms_edit)
# 保存组件引用 (动态增加)
box_container.circle = circle_label
box_container.dropdown = led_dropdown
box_container.ms_edit = ms_edit
# 间距调整
spacer1 = QSpacerItem(5, 1, QSizePolicy.Fixed, QSizePolicy.Minimum)
spacer2 = QSpacerItem(5, 1, QSizePolicy.Fixed, QSizePolicy.Minimum)
spacer3 = QSpacerItem(8, 1, QSizePolicy.Fixed, QSizePolicy.Minimum)
# box_layout.addItem(spacer1)
box_layout.addWidget(circle_label)
box_layout.addItem(spacer2)
box_layout.addWidget(led_dropdown)
# box_layout.addItem(spacer3)
box_layout.addWidget(ms_container)
grid_layout.addWidget(box_container, row, col)
def init_animations(self):
"""初始化显示动画:从下方滑入 + 淡入"""
# 1. 透明度动画从0→1与系统中心一致但时长不同
self.opacity_anim = QPropertyAnimation(self, b"windowOpacity")
self.opacity_anim.setDuration(400)
self.opacity_anim.setStartValue(0.0)
self.opacity_anim.setEndValue(1.0)
self.opacity_anim.setEasingCurve(QEasingCurve.OutCubic) # 缓动曲线不同
# 2. 位置动画从下方100px滑入目标位置核心差异点
self.pos_anim = QPropertyAnimation(self, b"geometry")
self.pos_anim.setDuration(400)
self.pos_anim.setEasingCurve(QEasingCurve.OutQuart) # 滑入效果更自然
# 3. 组合动画(同时执行滑入和淡入)
self.anim_group = QParallelAnimationGroup(self)
self.anim_group.addAnimation(self.opacity_anim)
self.anim_group.addAnimation(self.pos_anim)
def showEvent(self, event):
super().showEvent(event) # 先调用父类方法
# 动态计算动画起点在当前位置下方100px保持宽度和高度不变
current_geometry = self.geometry() # 当前位置和尺寸需提前用move设置
# 起点y坐标增加100px从下方滑入x和尺寸不变
start_rect = QRect(
current_geometry.x(),
current_geometry.y() + 100, # 下方100px
current_geometry.width(),
current_geometry.height()
)
# 设置动画起点和终点
self.pos_anim.setStartValue(start_rect)
self.pos_anim.setEndValue(current_geometry) # 终点:目标位置
# 启动动画
self.anim_group.start()
def paintEvent(self, event):
"""重写绘制事件,手动在透明背景上绘制图片"""
if not self.bg_pixmap.isNull():
painter = QPainter(self)
# 绘制背景图(完全覆盖窗口,无间隙)
painter.drawPixmap(self.rect(), self.bg_pixmap)
# 必须调用父类方法,确保子控件正常绘制
super().paintEvent(event)
"""
注意: row表示行号、col表示列号。都是从 0开始, 比如: 0行0列
"""
# ========== 对外接口:设置设备状态 ==========
def set_circle_status(self, row, col, status):
"""设置指定行列的状态(绿-黄-红) (normal/warning/error)"""
box = self.findChild(QWidget, f"box_{row}_{col}")
if box and hasattr(box, "circle"):
box.circle.setPixmap(box.circle.pixmaps[status])
box.circle.status = status
# ========== 对外接口:获取选中的设备名 ==========
def get_selected_device(self, row, col):
"""获取指定行列的选中设备名"""
box = self.findChild(QWidget, f"box_{row}_{col}")
if box and hasattr(box, "dropdown"):
return box.dropdown.get_selected_device()
return None
# ========== 对外接口:获取毫秒值 ==========
def get_ms_value(self, row, col):
"""获取指定行列的毫秒值如“5ms”"""
box = self.findChild(QWidget, f"box_{row}_{col}")
if box and hasattr(box, "ms_edit"):
# return box.ms_edit.text()
text = box.ms_edit.text().strip()
# 用正则提取数字(支持整数/小数,如"5"、"3.8"、"10.2ms"
import re
number_match = re.search(r"(\d+(?:\.\d+)?)", text)
if number_match:
return number_match.group(1)
return None
if __name__ == "__main__":
app = QApplication(sys.argv)
dialog = SystemDiagnosticsDialog()
dialog.show()
# 1. 设置0行0列的状态为“警告”状态
dialog.set_circle_status(0, 0, "warning")
# 2. 获取1行2列的选中设备名
device = dialog.get_selected_device(1, 2)
print(f"选中设备:{device}")
# 3. 获取3行1列的毫秒值
ms = dialog.get_ms_value(3, 1)
print(f"毫秒值:{ms}")
sys.exit(app.exec())