调整缩放、多核运行、图标显示

This commit is contained in:
2026-04-09 17:25:52 +08:00
parent 91e36407ae
commit 8025869b76
205 changed files with 295 additions and 1332 deletions

View File

@ -15,7 +15,7 @@ from datetime import datetime
from typing import Dict, Optional, List, Union
import numpy as np
import pandas as pd
import multiprocessing
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QLineEdit, QComboBox, QCheckBox, QSpinBox,
@ -3462,6 +3462,10 @@ class ImageViewerWidget(QWidget):
super().__init__(parent)
self.current_image_path = None
self.scale_factor = 1.0
self._update_timer = QTimer() # 防抖定时器
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._do_update_display)
self._pending_scale = None # 待更新的缩放比例
self.setup_ui()
def setup_ui(self):
@ -3564,28 +3568,62 @@ class ImageViewerWidget(QWidget):
self.status_label.setText(f"{pixmap.width()}x{pixmap.height()} | {size_mb:.2f} MB | {Path(image_path).name} | 适应窗口")
def update_image_display(self):
"""更新图像显示"""
"""更新图像显示 - 使用防抖避免频繁重绘卡顿"""
# 取消之前的待执行更新,重新计时
self._update_timer.stop()
self._pending_scale = self.scale_factor
self._update_timer.start(50) # 50ms后执行实际更新
def _do_update_display(self):
"""实际执行图像更新"""
if not hasattr(self, 'original_pixmap') or self.original_pixmap.isNull():
return
if self._pending_scale is None:
return
# 根据缩放比例选择变换模式大幅度缩放用Fast模式提升性能
if self._pending_scale > 2.0 or self._pending_scale < 0.5:
transform = Qt.FastTransformation
else:
transform = Qt.SmoothTransformation
scaled_pixmap = self.original_pixmap.scaled(
int(self.original_pixmap.width() * self.scale_factor),
int(self.original_pixmap.height() * self.scale_factor),
int(self.original_pixmap.width() * self._pending_scale),
int(self.original_pixmap.height() * self._pending_scale),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
transform
)
self.image_label.setPixmap(scaled_pixmap)
self._pending_scale = None
def wheelEvent(self, event):
"""鼠标滚轮缩放 - 实时响应"""
delta = event.angleDelta().y()
if delta > 0:
# 向上滚动 - 放大
if self.scale_factor < 5.0:
self.scale_factor = min(self.scale_factor * 1.1, 5.0)
self.update_image_display()
else:
# 向下滚动 - 缩小
if self.scale_factor > 0.1:
self.scale_factor = max(self.scale_factor / 1.1, 0.1)
self.update_image_display()
event.accept()
def zoom_in(self):
"""放大"""
if self.scale_factor < 5.0:
self.scale_factor *= 1.25
self.scale_factor = min(self.scale_factor * 1.25, 5.0)
self.update_image_display()
def zoom_out(self):
"""缩小"""
if self.scale_factor > 0.1:
self.scale_factor /= 1.25
self.scale_factor = max(self.scale_factor / 1.25, 0.1)
self.update_image_display()
def fit_to_window(self):
@ -3599,14 +3637,20 @@ class ImageViewerWidget(QWidget):
scale_w = view_size.width() / img_size.width()
scale_h = view_size.height() / img_size.height()
self.scale_factor = min(scale_w, scale_h, 1.0) # 不超过原始大小
# 记录适应前的比例(用于后续恢复参考)
self._fit_scale = min(scale_w, scale_h)
self.scale_factor = self._fit_scale
self.update_image_display()
self.status_label.setText(f"适应窗口 | 缩放: {self.scale_factor:.1%}")
def original_size(self):
"""原始大小"""
self.scale_factor = 1.0
self._fit_scale = None # 清除适应记录
self.update_image_display()
self.status_label.setText("原始大小 | 缩放: 100%")
def save_image(self):
"""保存图像"""
@ -5229,6 +5273,7 @@ class WaterQualityGUI(QMainWindow):
self.init_ui()
self.apply_stylesheet()
self._disable_wheel_for_all_spinboxes()
def get_icon_path(self, icon_filename):
"""
@ -5245,10 +5290,48 @@ class WaterQualityGUI(QMainWindow):
return os.path.join(icon_dir, icon_filename)
def _disable_wheel_for_all_spinboxes(self):
"""
遍历所有子控件,为 QSpinBox/QDoubleSpinBox/QComboBox 禁用滚轮事件
防止滚动页面时意外改变数值
"""
from PyQt5.QtCore import Qt
# 找到所有数值输入控件
for spinbox in self.findChildren(QSpinBox):
spinbox.setFocusPolicy(Qt.StrongFocus) # 只有聚焦时才响应滚轮
spinbox.wheelEvent = lambda event, sb=spinbox: None # 完全禁用滚轮
for spinbox in self.findChildren(QDoubleSpinBox):
spinbox.setFocusPolicy(Qt.StrongFocus)
spinbox.wheelEvent = lambda event, sb=spinbox: None
for combobox in self.findChildren(QComboBox):
combobox.setFocusPolicy(Qt.StrongFocus)
combobox.wheelEvent = lambda event, cb=combobox: None
def init_ui(self):
"""初始化UI"""
self.setWindowTitle("水质参数反演分析系统 v1.0")
self.setGeometry(100, 100, 1200, 800)
# 获取屏幕可用区域(排除任务栏)
screen_geometry = QApplication.primaryScreen().availableGeometry()
screen_width = screen_geometry.width()
screen_height = screen_geometry.height()
# 初始尺寸:宽度固定 800高度占满屏幕
window_width = 1200
window_height = screen_height
# 仅设置初始大小,不锁定
self.resize(window_width, window_height)
# 计算水平居中、垂直贴顶的位置
x = (screen_width - window_width) // 2
y = 0
self.move(x, y)
# 可选:设置最小尺寸,防止用户缩得太小
self.setMinimumSize(600, 400)
# 创建自定义标题栏包含Logo和菜单栏
self.create_title_bar()
@ -5297,8 +5380,12 @@ class WaterQualityGUI(QMainWindow):
""")
# 设置Logo图片路径 - 使用相对路径(打包兼容)
logo_path = r"E:\code\WQ\GUI_v1\fengzhuang-ui2V3\data\icons\logo.png"
logo_pixmap = QPixmap(str(logo_path))
from pathlib import Path
if hasattr(sys, '_MEIPASS'):
logo_path = os.path.join(sys._MEIPASS, 'data', 'icons', 'logo.png')
else:
logo_path = str(Path(__file__).parent.parent.parent / "data" / "icons" / "logo.png")
logo_pixmap = QPixmap(logo_path)
if not logo_pixmap.isNull():
# 按高度缩放图片保持宽高比让Logo更显眼
@ -5406,17 +5493,23 @@ class WaterQualityGUI(QMainWindow):
banner_layout = QHBoxLayout()
banner_layout.setContentsMargins(0, 0, 0, 0)
banner_layout.setSpacing(0)
# 不设置居中对齐,让横幅填满整个容器
# 创建横幅标签
# 创建横幅标签 - 完全跟随窗口等比缩放,填满整个区域
self.banner_label = QLabel()
self.banner_label.setMinimumHeight(65)
self.banner_label.setMaximumHeight(110)
self.banner_label.setAlignment(Qt.AlignCenter)
# 最小高度保证:当窗口很小时至少显示 38px 高 (200px 宽 / 5.25)
self.banner_label.setMinimumHeight(int(200 / 5.25)) # ≈ 38px
# 使用 Expanding 策略让标签填满可用空间
self.banner_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.banner_label.setScaledContents(False)
# 清除 QLabel 默认的 margin 和 padding消除右侧空白
self.banner_label.setStyleSheet("margin: 0px; padding: 0px; border: none;")
# 保存原始pixmap用于后续缩放
banner_path = r"E:\code\WQ\GUI_v1\fengzhuang-ui2\data\icons\Mega Water 1.0.png"
if hasattr(sys, '_MEIPASS'):
banner_path = os.path.join(sys._MEIPASS, 'data', 'icons', 'Mega Water 1.0.png')
else:
banner_path = str(Path(__file__).parent.parent.parent / "data" / "icons" / "Mega Water 1.0.png")
self.banner_pixmap = QPixmap(banner_path)
if not self.banner_pixmap.isNull():
@ -5444,13 +5537,19 @@ class WaterQualityGUI(QMainWindow):
banner_toolbar.setMovable(False)
banner_toolbar.setFloatable(False)
banner_toolbar.addWidget(banner_widget)
banner_toolbar.setContentsMargins(0, 0, 0, 0) # 清除工具栏布局的边距
banner_toolbar.setStyleSheet("""
QToolBar {
background-color: white;
border: none;
border-bottom: 1px solid #ddd;
padding: 2px 0px;
padding: 0px;
margin: 0px;
spacing: 0px;
}
QToolBar QWidget {
margin: 0px;
padding: 0px;
}
""")
@ -6203,44 +6302,42 @@ class WaterQualityGUI(QMainWindow):
self.training_mode_action.setText("有训练数据模式" if checked else "无训练数据模式")
def update_banner_image(self):
"""更新横幅图片 - 等比自适应缩放"""
"""更新横幅图片 - 完全跟随窗口等比缩放,填满可用宽度"""
if not hasattr(self, 'banner_pixmap') or self.banner_pixmap.isNull():
return
# 获取可用宽度(考虑工具栏边距)
available_width = max(200, self.width() - 60) # 最小宽度保护
# 获取可用宽度(考虑工具栏边距),跟随窗口实时变化
available_width = max(200, self.width() - 60)
# 先根据可用宽度计算目标高度(严格 5.25:1
target_height = int(available_width / 5.25)
# 限制最小高度
if target_height < 38:
target_height = 38
available_width = int(38 * 5.25)
# 第一步:按宽度缩放,保持比例
# 计算图片目标尺寸(保持 5.25:1 比例
target_width = available_width
# 设置固定尺寸,确保标签严格填满整个区域
self.banner_label.setFixedSize(target_width, target_height)
# 等比缩放到目标尺寸,填满整个区域(允许轻微裁剪)
scaled_pixmap = self.banner_pixmap.scaled(
available_width,
120, # 最大允许高度
Qt.KeepAspectRatio, # 关键:等比缩放
Qt.SmoothTransformation # 平滑缩放
target_width,
target_height,
Qt.KeepAspectRatioByExpanding, # 保持比例,填满区域,允许裁剪超出部分
Qt.SmoothTransformation
)
# 如果高度仍然过大,则按高度限制缩放
if scaled_pixmap.height() > 110:
scaled_pixmap = self.banner_pixmap.scaled(
int(available_width * 0.9),
110,
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
self.banner_label.setPixmap(scaled_pixmap)
def resizeEvent(self, event):
"""窗口大小改变事件 - 实时更新横幅图片等比缩放"""
super().resizeEvent(event)
# 使用定时器避免频繁调用
if hasattr(self, '_banner_timer'):
self._banner_timer.stop()
else:
self._banner_timer = QTimer()
self._banner_timer.setSingleShot(True)
self._banner_timer.timeout.connect(self.update_banner_image)
self._banner_timer.start(50) # 50ms后更新
# 直接调用,不使用定时器延迟(或缩短到 10ms
self.update_banner_image()
def update_ui_for_training_mode(self):
"""根据训练数据模式更新UI状态"""
@ -6296,5 +6393,7 @@ def main():
if __name__ == "__main__":
#冻结只显示1个exe
# multiprocessing.freeze_support()
main()