2_menus.h

本文介绍了一个简单的Windows应用程序中菜单项ID的定义及窗口过程函数和关于对话框过程函数的声明。通过宏定义设置了不同的菜单命令ID,并声明了处理消息的回调函数WndProc以及关于对话框的回调函数About。

  name="google_ads_frame" marginwidth="0" marginheight="0" src="http://pagead2.googlesyndication.com/pagead/ads?client=ca-pub-5572165936844014&dt=1194442938015&lmt=1194190197&format=336x280_as&output=html&correlator=1194442937843&url=file%3A%2F%2F%2FC%3A%2FDocuments%2520and%2520Settings%2Flhh1%2F%E6%A1%8C%E9%9D%A2%2FCLanguage.htm&color_bg=FFFFFF&color_text=000000&color_link=000000&color_url=FFFFFF&color_border=FFFFFF&ad_type=text&ga_vid=583001034.1194442938&ga_sid=1194442938&ga_hid=1942779085&flash=9&u_h=768&u_w=1024&u_ah=740&u_aw=1024&u_cd=32&u_tz=480&u_java=true" frameborder="0" width="336" scrolling="no" height="280" allowtransparency="allowtransparency"> #define IDM_EXIT           100
#define IDM_OLD            200
#define IDM_NEW            201
#define IDM_ABOUT          301

LRESULT CALLBACK WndProc  (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About    (HWND, UINT, WPARAM, LPARAM);

templat_editor.py文件已修改,但现在我的main_window.py的完整代码如下:#主窗口 - PyQt6 版本 import os import cv2 import numpy as np import sys import logging import faulthandler # 用于捕获崩溃信息 import ctypes # Windows API 调用支持 from typing import Optional from pyzbar.pyzbar import decode from PyQt6.QtGui import QPainter, QPolygonF, QPixmap from PyQt6.QtCore import QPointF from PyQt6.QtCore import PYQT_VERSION_STR, QRectF from PyQt6.QtGui import QAction from PyQt6.QtWidgets import QDockWidget, QWidget,QMainWindow, QStatusBar, \ QTabWidget, QToolBar, QApplication, QMessageBox, QFileDialog, QDoubleSpinBox from PyQt6.QtWidgets import ( QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox, QGroupBox, QLineEdit, QFormLayout, QSpinBox, QColorDialog, QRadioButton, QButtonGroup, QCheckBox, QTextEdit, QFontComboBox ) from PyQt6.QtCore import Qt, pyqtSignal from PyQt6.QtGui import QColor, QFont, QTextCharFormat from ui.template_editor import TemplateCanvas # 启用故障处理程序,帮助调试崩溃问题 faulthandler.enable() # 设置日志系统,记录运行时信息 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("app_debug.log"), # 输出到文件 logging.StreamHandler() # 输出到控制台 ] ) logger = logging.getLogger(__name__) logger.info("应用程序启动") logger.info(f"Python 路径: {sys.executable}") logger.info(f"参数: {sys.argv}") logger.info(f"系统路径: {sys.path}") logger.info(f"工作目录: {os.getcwd()}") logger.info(f"PyQt6 版本: {'未导入' if 'PYQT_VERSION_STR' not in globals() else PYQT_VERSION_STR}") # 检查是否运行在虚拟环境中 if not hasattr(sys, 'real_prefix') and not hasattr(sys, 'base_prefix'): logging.warning("警告: 未在虚拟环境中运行,建议使用虚拟环境") # 设置调试标志和环境变量 os.environ['QT_DEBUG_PLUGINS'] = '1' os.environ['QTWEBENGINE_CHROMIUM_FLAGS'] = '--disable-gpu --no-sandbox' # Windows 错误报告设置 if sys.platform == 'win32': ctypes.windll.kernel32.SetErrorMode.argtypes = [ctypes.c_uint] ctypes.windll.kernel32.SetErrorMode(ctypes.c_uint(0x0001 | 0x0002)) # 设置堆栈保护 os.environ['QT_FATAL_CRITICALS'] = '1' os.environ['QT_FATAL_WARNINGS'] = '1' class BasePropertiesPanel(QWidget): """属性面板基类,定义通用信号与接口""" propertyChanged = pyqtSignal(str, object) # 属性变化信号 def __init__(self, parent=None): super().__init__(parent) self.region = None self.setup_ui() def setup_ui(self): pass def set_region(self, region): """绑定当前编辑区域对象""" self.region = region self.update_ui_from_region() def update_ui_from_region(self): """根据区域数据更新 UI""" pass def update_region_from_ui(self): """根据 UI 数据更新区域属性""" pass class TextPropertiesPanel(BasePropertiesPanel): """文本属性面板 UI 实现 """ # 定义信号用于与画布通信 textChanged = pyqtSignal(str) fontChanged = pyqtSignal(QFont) colorChanged = pyqtSignal(QColor) alignmentChanged = pyqtSignal(Qt.AlignmentFlag) positionChanged = pyqtSignal(float, float) sizeChanged = pyqtSignal(float, float) def __init__(self, parent=None): super().__init__(parent) self.current_font = QFont() self.current_color = QColor(0, 0, 0) # 默认黑色 self.current_alignment = Qt.AlignmentFlag.AlignLeft def setup_ui(self): layout = QVBoxLayout(self) layout.setSpacing(10) # 文本内容编辑区域 content_group = QGroupBox("文本内容") content_layout = QVBoxLayout(content_group) self.text_edit = QTextEdit() self.text_edit.setPlaceholderText("输入文本内容...") self.text_edit.setMinimumHeight(80) self.text_edit.textChanged.connect(self.on_text_changed) content_layout.addWidget(self.text_edit) layout.addWidget(content_group) # 字体属性区域 font_group = QGroupBox("字体属性") font_layout = QFormLayout(font_group) # 字体选择 self.font_combo = QFontComboBox() self.font_combo.currentFontChanged.connect(self.on_font_changed) # 字号选择 self.font_size_spin = QSpinBox() self.font_size_spin.setRange(6, 120) self.font_size_spin.setValue(12) self.font_size_spin.valueChanged.connect(self.on_font_size_changed) # 字体样式 style_layout = QHBoxLayout() self.bold_check = QCheckBox("粗体") self.italic_check = QCheckBox("斜体") self.underline_check = QCheckBox("下划线") self.bold_check.toggled.connect(self.on_style_changed) self.italic_check.toggled.connect(self.on_style_changed) self.underline_check.toggled.connect(self.on_style_changed) style_layout.addWidget(self.bold_check) style_layout.addWidget(self.italic_check) style_layout.addWidget(self.underline_check) # 字体颜色 color_layout = QHBoxLayout() self.color_label = QLabel() self.color_label.setFixedSize(20, 20) self.color_label.setStyleSheet("background-color: black; border: 1px solid gray;") self.color_btn = QPushButton("选择颜色...") self.color_btn.clicked.connect(self.choose_color) color_layout.addWidget(QLabel("颜色:")) color_layout.addWidget(self.color_label) color_layout.addWidget(self.color_btn) color_layout.addStretch() font_layout.addRow("字体:", self.font_combo) font_layout.addRow("字号:", self.font_size_spin) font_layout.addRow("样式:", style_layout) font_layout.addRow(color_layout) layout.addWidget(font_group) # 对齐方式 align_group = QGroupBox("对齐方式") align_layout = QHBoxLayout(align_group) self.align_group = QButtonGroup(self) self.align_left = QRadioButton("左对齐") self.align_center = QRadioButton("居中对齐") self.align_right = QRadioButton("右对齐") self.align_justify = QRadioButton("两端对齐") self.align_group.addButton(self.align_left, 1) self.align_group.addButton(self.align_center, 2) self.align_group.addButton(self.align_right, 3) self.align_group.addButton(self.align_justify, 4) self.align_left.setChecked(True) self.align_group.buttonToggled.connect(self.on_alignment_changed) align_layout.addWidget(self.align_left) align_layout.addWidget(self.align_center) align_layout.addWidget(self.align_right) align_layout.addWidget(self.align_justify) layout.addWidget(align_group) # 位置和尺寸 position_group = QGroupBox("位置和尺寸") position_layout = QFormLayout(position_group) self.pos_x_spin = QDoubleSpinBox() self.pos_x_spin.setRange(0, 1000) self.pos_x_spin.setSuffix(" mm") self.pos_x_spin.valueChanged.connect(self.on_position_changed) self.pos_y_spin = QDoubleSpinBox() self.pos_y_spin.setRange(0, 1000) self.pos_y_spin.setSuffix(" mm") self.pos_y_spin.valueChanged.connect(self.on_position_changed) self.width_spin = QDoubleSpinBox() self.width_spin.setRange(10, 500) self.width_spin.setSuffix(" mm") self.width_spin.setValue(100) self.width_spin.valueChanged.connect(self.on_size_changed) self.height_spin = QDoubleSpinBox() self.height_spin.setRange(10, 500) self.height_spin.setSuffix(" mm") self.height_spin.setValue(30) self.height_spin.valueChanged.connect(self.on_size_changed) position_layout.addRow("X 位置:", self.pos_x_spin) position_layout.addRow("Y 位置:", self.pos_y_spin) position_layout.addRow("宽度:", self.width_spin) position_layout.addRow("高度:", self.height_spin) layout.addWidget(position_group) self.setLayout(layout) def set_text_properties(self, text, font, color, alignment, x, y, width, height): """设置文本属性值""" # 文本内容 self.text_edit.setPlainText(text) # 字体 self.current_font = font self.font_combo.setCurrentFont(font) self.font_size_spin.setValue(font.pointSize()) self.bold_check.setChecked(font.bold()) self.italic_check.setChecked(font.italic()) self.underline_check.setChecked(font.underline()) # 颜色 self.current_color = color self.color_label.setStyleSheet(f"background-color: {color.name()}; border: 1px solid gray;") # 对齐方式 self.current_alignment = alignment if alignment == Qt.AlignmentFlag.AlignLeft: self.align_left.setChecked(True) elif alignment == Qt.AlignmentFlag.AlignHCenter: self.align_center.setChecked(True) elif alignment == Qt.AlignmentFlag.AlignRight: self.align_right.setChecked(True) elif alignment == Qt.AlignmentFlag.AlignJustify: self.align_justify.setChecked(True) # 位置和尺寸 self.pos_x_spin.setValue(x) self.pos_y_spin.setValue(y) self.width_spin.setValue(width) self.height_spin.setValue(height) def on_text_changed(self): """文本内容变化处理""" text = self.text_edit.toPlainText() self.textChanged.emit(text) def on_font_changed(self, font): """字体变化处理""" self.current_font.setFamily(font.family()) self.fontChanged.emit(self.current_font) def on_font_size_changed(self, size): """字号变化处理""" self.current_font.setPointSize(size) self.fontChanged.emit(self.current_font) def on_style_changed(self, checked): """字体样式变化处理""" self.current_font.setBold(self.bold_check.isChecked()) self.current_font.setItalic(self.italic_check.isChecked()) self.current_font.setUnderline(self.underline_check.isChecked()) self.fontChanged.emit(self.current_font) def choose_color(self): """选择字体颜色""" color = QColorDialog.getColor() if color.isValid(): self.current_color = color self.color_label.setStyleSheet(f"background-color: {color.name()}; border: 1px solid gray;") self.colorChanged.emit(color) def on_alignment_changed(self, button, checked): """对齐方式变化处理""" if not checked: return align_map = { 1: Qt.AlignmentFlag.AlignLeft, 2: Qt.AlignmentFlag.AlignHCenter, 3: Qt.AlignmentFlag.AlignRight, 4: Qt.AlignmentFlag.AlignJustify } align = align_map.get(self.align_group.checkedId(), Qt.AlignmentFlag.AlignLeft) self.current_alignment = align self.alignmentChanged.emit(align) def on_position_changed(self): """位置变化处理""" x = self.pos_x_spin.value() y = self.pos_y_spin.value() self.positionChanged.emit(x, y) def on_size_changed(self): """尺寸变化处理""" width = self.width_spin.value() height = self.height_spin.value() self.sizeChanged.emit(width, height) class QRPropertiesPanel(BasePropertiesPanel): """二维码属性面板 UI 实现,包含自动识别功能""" def __init__(self, parent=None): super().__init__(parent) self.qr_position_x = None self.qr_correction_combo = None self.qr_content_edit = None self.qr_position_y = None self.qr_fixed_check = None self.apply_btn = None self.qr_size_spin = None self.qr_type_combo = None self.qr_data_label = None self.detection_status = None self.qr_selector = None self.qr_preview = None self.detect_btn = None self.detected_qr_codes = [] # 存储检测到的二维码信息 self.current_qr_index = -1 # 当前选中的二维码索引 def setup_ui(self): layout = QVBoxLayout(self) layout.setSpacing(10) # 自动检测区域 auto_detect_group = QGroupBox("二维码自动检测") auto_layout = QVBoxLayout(auto_detect_group) self.detect_btn = QPushButton("自动识别二维码") self.detect_btn.clicked.connect(self.auto_detect_qr_codes) self.detect_btn.setStyleSheet("background-color: #4CAF50; color: white;") self.detection_status = QLabel("就绪") self.detection_status.setAlignment(Qt.AlignmentFlag.AlignCenter) auto_layout.addWidget(self.detect_btn) auto_layout.addWidget(self.detection_status) # 二维码选择器 self.qr_selector = QComboBox() self.qr_selector.currentIndexChanged.connect(self.select_qr_code) auto_layout.addWidget(QLabel("检测到的二维码:")) auto_layout.addWidget(self.qr_selector) # 预览区域 preview_layout = QHBoxLayout() self.qr_preview = QLabel() self.qr_preview.setFixedSize(150, 150) self.qr_preview.setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;") self.qr_preview.setAlignment(Qt.AlignmentFlag.AlignCenter) self.qr_data_label = QLabel("二维码数据将显示在这里") self.qr_data_label.setWordWrap(True) self.qr_data_label.setStyleSheet("background-color: #f8f8f8; padding: 5px;") preview_layout.addWidget(self.qr_preview) preview_layout.addWidget(self.qr_data_label, 1) auto_layout.addLayout(preview_layout) layout.addWidget(auto_detect_group) # 二维码属性编辑区域 props_group = QGroupBox("二维码属性") form_layout = QFormLayout(props_group) self.qr_type_combo = QComboBox() self.qr_type_combo.addItems(["QR Code", "Data Matrix", "PDF417", "Aztec"]) self.qr_content_edit = QLineEdit() self.qr_content_edit.setPlaceholderText("输入二维码内容") self.qr_size_spin = QDoubleSpinBox() self.qr_size_spin.setRange(5, 100) self.qr_size_spin.setValue(30) self.qr_size_spin.setSuffix(" mm") self.qr_correction_combo = QComboBox() self.qr_correction_combo.addItems(["L (低)", "M (中)", "Q (高)", "H (最高)"]) self.qr_correction_combo.setCurrentIndex(1) self.qr_position_x = QDoubleSpinBox() self.qr_position_x.setRange(0, 1000) self.qr_position_x.setSuffix(" mm") self.qr_position_y = QDoubleSpinBox() self.qr_position_y.setRange(0, 1000) self.qr_position_y.setSuffix(" mm") self.qr_fixed_check = QCheckBox("固定位置") self.qr_fixed_check.setChecked(True) form_layout.addRow("类型:", self.qr_type_combo) form_layout.addRow("内容:", self.qr_content_edit) form_layout.addRow("尺寸:", self.qr_size_spin) form_layout.addRow("纠错级别:", self.qr_correction_combo) form_layout.addRow("位置 X:", self.qr_position_x) form_layout.addRow("位置 Y:", self.qr_position_y) form_layout.addRow(self.qr_fixed_check) # 应用按钮 self.apply_btn = QPushButton("应用更改") self.apply_btn.clicked.connect(self.apply_qr_properties) form_layout.addRow(self.apply_btn) layout.addWidget(props_group) layout.addStretch(1) self.setLayout(layout) # 连接信号 self.qr_content_edit.textChanged.connect(self.update_qr_preview) self.qr_size_spin.valueChanged.connect(self.update_qr_preview) self.qr_correction_combo.currentIndexChanged.connect(self.update_qr_preview) def auto_detect_qr_codes(self): """自动检测二维码""" if not self.region or not self.region.template_image: self.detection_status.setText("错误: 没有可用的模板图像") self.detection_status.setStyleSheet("color: red;") return try: # 转换图像为OpenCV格式 qimage = self.region.template_image width, height = qimage.width(), qimage.height() ptr = qimage.bits() ptr.setsize(qimage.sizeInBytes()) img_np = np.array(ptr).reshape(height, width, 4) # RGBA格式 # 转换为BGR格式并转为灰度图 img_bgr = cv2.cvtColor(img_np, cv2.COLOR_RGBA2BGR) gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) # 使用pyzbar检测二维码 self.detected_qr_codes = decode(gray) if not self.detected_qr_codes: self.detection_status.setText("未检测到二维码") self.detection_status.setStyleSheet("color: orange;") return # 更新UI显示结果 self.detection_status.setText(f"检测到 {len(self.detected_qr_codes)} 个二维码") self.detection_status.setStyleSheet("color: green;") # 更新二维码选择器 self.qr_selector.clear() for i, qr in enumerate(self.detected_qr_codes): self.qr_selector.addItem(f"二维码 {i + 1}") # 自动选择第一个二维码 if self.detected_qr_codes: self.qr_selector.setCurrentIndex(0) except Exception as e: self.detection_status.setText(f"检测失败: {str(e)}") self.detection_status.setStyleSheet("color: red;") logger.error(f"二维码检测失败: {str(e)}", exc_info=True) def select_qr_code(self, index): """选择检测到的二维码""" if index < 0 or index >= len(self.detected_qr_codes): return self.current_qr_index = index qr = self.detected_qr_codes[index] # 显示二维码数据 qr_data = qr.data.decode('utf-8') self.qr_data_label.setText(f"类型: {qr.type}\n\n数据:\n{qr_data}") # 更新属性编辑器 self.qr_content_edit.setText(qr_data) # 计算二维码位置 (转换为毫米,假设300dpi) points = qr.polygon if len(points) >= 4: # 计算中心点 center_x = sum([p.x for p in points]) / 4 center_y = sum([p.y for p in points]) / 4 # 转换为毫米 (300dpi: 1英寸=25.4mm, 300像素=25.4mm) mm_per_pixel = 25.4 / 300 self.qr_position_x.setValue(center_x * mm_per_pixel) self.qr_position_y.setValue(center_y * mm_per_pixel) # 计算尺寸 width = max(p.x for p in points) - min(p.x for p in points) height = max(p.y for p in points) - min(p.y for p in points) size_mm = max(width, height) * mm_per_pixel self.qr_size_spin.setValue(size_mm) # 更新预览 self.update_qr_preview() # 在模板画布上高亮显示选中的二维码 self.highlight_selected_qr(qr) def highlight_selected_qr(self, qr): """在模板画布上高亮显示选中的二维码""" if not self.region or not self.region.template_image: return # 创建原始图像的副本 image = self.region.template_image.copy() # 创建QPainter绘制高亮框 painter = QPainter(image) painter.setPen(QColor(255, 0, 0, 200)) # 半透明红色 painter.setBrush(Qt.BrushStyle.NoBrush) # 绘制二维码边界框 points = qr.polygon if len(points) == 4: polygon = QPolygonF() for point in points: polygon.append(QPointF(point.x, point.y)) painter.drawPolygon(polygon) # 绘制中心点 center_x = sum([p.x for p in points]) / 4 center_y = sum([p.y for p in points]) / 4 painter.setBrush(QColor(255, 0, 0, 150)) painter.drawEllipse(QPointF(center_x, center_y), 5, 5) painter.end() # 更新画布显示 self.region.setPixmap(QPixmap.fromImage(image)) def update_qr_preview(self): """更新二维码预览图像""" # 在实际应用中,这里应该生成二维码预览图 # 为了简化,我们只显示一个占位符 pixmap = QPixmap(150, 150) pixmap.fill(QColor(240, 240, 240)) painter = QPainter(pixmap) painter.setPen(Qt.GlobalColor.darkGray) painter.drawRect(10, 10, 130, 130) # 显示二维码类型和内容摘要 qr_type = self.qr_type_combo.currentText() content = self.qr_content_edit.text()[:15] + "..." if len( self.qr_content_edit.text()) > 15 else self.qr_content_edit.text() painter.drawText(QRectF(0, 0, 150, 150), Qt.AlignmentFlag.AlignCenter, f"{qr_type}\n\n{content}") painter.end() self.qr_preview.setPixmap(pixmap) def apply_qr_properties(self): """应用二维码属性更改""" if not self.region: return # 在实际应用中,这里应该更新二维码对象的属性 qr_type = self.qr_type_combo.currentText() content = self.qr_content_edit.text() size = self.qr_size_spin.value() position_x = self.qr_position_x.value() position_y = self.qr_position_y.value() logger.info( f"更新二维码属性: 类型={qr_type}, 内容={content[:20]}..., 尺寸={size}mm, 位置=({position_x},{position_y})mm") # 这里应该调用画布更新二维码显示 # self.region.update_qr_display() def update_ui_from_region(self): """根据区域数据更新 UI""" if not self.region: return # 在实际应用中,这里应该从区域对象加载二维码属性 # 现在只是设置一些示例值 self.qr_content_edit.setText("https://example.com/product/12345") self.qr_size_spin.setValue(25.0) self.qr_position_x.setValue(50.0) self.qr_position_y.setValue(30.0) self.update_qr_preview() def update_region_from_ui(self): """根据 UI 数据更新区域属性""" if not self.region: return # 在实际应用中,这里应该更新二维码对象的属性 content = self.qr_content_edit.text() size = self.qr_size_spin.value() position_x = self.qr_position_x.value() position_y = self.qr_position_y.value() self.region.set_content(content) self.region.set_size(size) self.region.set_position(position_x, position_y) logger.info(f"二维码区域属性已更新") class LogoPropertiesPanel(BasePropertiesPanel): """Logo属性面板 UI 实现""" def setup_ui(self): layout = QVBoxLayout(self) layout.addWidget(QLabel("Logo属性面板")) self.setLayout(layout) class MainWindow(QMainWindow): def __init__(self, data_path: Optional[str] = None): super().__init__() self.run_test_add_regions = None self.logger = logging.getLogger(__name__) self.logger.info("主窗口初始化开始") self.setWindowTitle("自动打印系统") self.resize(800, 600) self.data_path = data_path or os.path.join(os.path.dirname(__file__), "../../../data") # 初始化所有实例属性为 None self.left_layout = None self.btn_upload = None self.btn_add_text = None self.btn_add_qr = None self.template_canvas = None self.props_tabs = None self.text_props_panel = None self.qr_props_panel = None self.logo_props_panel = None self.right_layout = None self.btn_import_data = None self.btn_save_config = None self.data_mapper = None self.field_mapping_widget = None self.history_combo = None self.btn_history = None self.btn_generate = None self.btn_rerun = None self.project_manager = None self.font_manager = None self.compositor = None # 初始化控件 self.init_components() try: # 创建项目管理器 from src.auto_print_system.core.project_manager import ProjectManager self.project_manager = ProjectManager(self.data_path) # 创建字体管理器 from src.auto_print_system.core.font_manager import FontManager self.font_manager = FontManager(self.data_path) # 创建合成引擎 from src.auto_print_system.core.file_compositor import FileCompositor self.compositor = FileCompositor(self.data_path, self.font_manager) except ImportError as e: self.logger.error(f"初始化核心组件失败: {str(e)}", exc_info=True) QMessageBox.critical(self, "错误", f"初始化核心组件失败:\n{str(e)}") # 设置状态栏 self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage("就绪") self.current_project = None self.logo_path = None self.spot_color_path = None # 加载样式 self.apply_styles() def init_components(self) -> None: """初始化所有界面组件""" self.logger.info("开始初始化组件") central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 左侧模板编辑区 left_dock = QDockWidget("模板编辑区", self) left_widget = QWidget() self.left_layout = QVBoxLayout(left_widget) btn_layout = QHBoxLayout() self.btn_upload = QPushButton("上传模板") self.btn_add_text = QPushButton("添加文本框") self.btn_add_qr = QPushButton("添加二维码区") self.btn_upload.clicked.connect(self.upload_template) self.btn_add_text.clicked.connect(self.add_text_region) self.btn_add_qr.clicked.connect(self.add_qr_region) btn_layout.addWidget(self.btn_upload) btn_layout.addWidget(self.btn_add_text) btn_layout.addWidget(self.btn_add_qr) self.left_layout.addLayout(btn_layout) try: from src.auto_print_system.ui.template_editor import TemplateCanvas self.template_canvas = TemplateCanvas(data_path=self.data_path) self.template_canvas.setAlignment(Qt.AlignmentFlag.AlignCenter) self.template_canvas.setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;") self.left_layout.addWidget(self.template_canvas) except ImportError as e: self.logger.error(f"初始化模板画布失败: {str(e)}", exc_info=True) self.template_canvas = None # 属性面板 self.props_tabs = QTabWidget() self.text_props_panel = TextPropertiesPanel() self.qr_props_panel = QRPropertiesPanel() self.logo_props_panel = LogoPropertiesPanel() self.props_tabs.addTab(self.text_props_panel, "文本属性") self.props_tabs.addTab(self.qr_props_panel, "二维码属性") self.props_tabs.addTab(self.logo_props_panel, "Logo属性") self.left_layout.addWidget(self.props_tabs) left_dock.setWidget(left_widget) self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, left_dock) # 右侧数据映射区 right_dock = QDockWidget("数据映射区", self) right_widget = QWidget() self.right_layout = QVBoxLayout(right_widget) data_btn_layout = QHBoxLayout() self.btn_import_data = QPushButton("导入数据") self.btn_save_config = QPushButton("保存配置") self.btn_import_data.clicked.connect(self.import_data) self.btn_save_config.clicked.connect(self.save_config) data_btn_layout.addWidget(self.btn_import_data) data_btn_layout.addWidget(self.btn_save_config) self.right_layout.addLayout(data_btn_layout) try: from src.auto_print_system.ui.data_mapper import DataMapperWidget self.data_mapper = DataMapperWidget(self) self.right_layout.addWidget(self.data_mapper, 1) except ImportError as e: self.logger.error(f"初始化数据映射器失败: {str(e)}", exc_info=True) self.field_mapping_widget = QLabel("拖拽字段到模板区域进行映射") self.field_mapping_widget.setStyleSheet("min-height: 100px; background-color: #fff; border: 1px dashed #999;") self.right_layout.addWidget(self.field_mapping_widget) right_dock.setWidget(right_widget) self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, right_dock) # 底部操作区 bottom_widget = QWidget() bottom_layout = QHBoxLayout(bottom_widget) self.history_combo = QComboBox() self.btn_history = QPushButton("管理") self.btn_generate = QPushButton("开始合成") self.btn_rerun = QPushButton("执行翻单") self.btn_rerun.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold; padding: 8px 16px;") self.btn_rerun.clicked.connect(self.rerun_project) self.btn_rerun.setEnabled(False) # 设置按钮初始状态为不可点击 bottom_layout.addWidget(QLabel("翻单历史:")) bottom_layout.addWidget(self.history_combo) bottom_layout.addWidget(self.btn_history) bottom_layout.addSpacing(20) bottom_layout.addWidget(self.btn_generate) bottom_layout.addWidget(self.btn_rerun) main_layout.addWidget(bottom_widget) # 翻单按钮状态初始化 self.create_menus() self.create_toolbar() self.update_rerun_button_state() # 连接文本属性面板信号到画布 if self.template_canvas: # 文本内容变化 self.text_props_panel.textChanged.connect( self.template_canvas.update_selected_text_region_text ) # 字体变化 self.text_props_panel.fontChanged.connect( self.template_canvas.update_selected_text_region_font ) # 颜色变化 self.text_props_panel.colorChanged.connect( self.template_canvas.update_selected_text_region_color ) # 对齐方式变化 self.text_props_panel.alignmentChanged.connect( self.template_canvas.update_selected_text_region_alignment ) # 位置变化 self.text_props_panel.positionChanged.connect( self.template_canvas.update_selected_text_region_position ) # 尺寸变化 self.text_props_panel.sizeChanged.connect( self.template_canvas.update_selected_text_region_size ) # ===== 功能槽函数 ===== def update_rerun_button_state(self) -> None: """根据项目状态更新翻单按钮的可用性""" if not hasattr(self, 'template_canvas') or not hasattr(self, 'data_mapper'): self.logger.warning("模板画布或数据映射器未初始化") return can_rerun = ( self.template_canvas is not None and self.template_canvas.template_item is not None and self.data_mapper is not None ) if self.btn_rerun: self.btn_rerun.setEnabled(can_rerun) else: self.logger.warning("翻单按钮未初始化,无法更新状态") def rerun_project(self) -> None: """执行翻单操作""" if self.btn_rerun is not None: self.btn_rerun.setEnabled(False) # 防止多次点击 else: logging.error("btn_rerun 控件未正确初始化") return # ... 其他业务逻辑 ... def create_menus(self) -> None: """创建菜单栏""" menubar = self.menuBar() file_menu = menubar.addMenu('文件') new_action = QAction('新建项目', self) open_action = QAction('打开项目', self) save_action = QAction('保存项目', self) exit_action = QAction('退出', self) exit_action.triggered.connect(self.close) file_menu.addAction(new_action) file_menu.addAction(open_action) file_menu.addAction(save_action) file_menu.addSeparator() file_menu.addAction(exit_action) edit_menu = menubar.addMenu('编辑') edit_menu.addAction('撤销') edit_menu.addAction('重做') def create_toolbar(self) -> None: """创建工具栏""" toolbar = QToolBar("主工具栏") self.addToolBar(toolbar) toolbar.addAction("打开模板", self.upload_template) toolbar.addAction("导入数据", self.import_data) toolbar.addSeparator() toolbar.addAction("添加文本", self.add_text_region) toolbar.addAction("添加二维码", self.add_qr_region) toolbar.addSeparator() toolbar.addAction("开始合成", self.generate_output) def apply_styles(self) -> None: """应用基本样式表""" self.setStyleSheet(""" QMainWindow { background-color: #f5f5f5; } QPushButton { padding: 5px 10px; border: 1px solid #ccc; border-radius: 4px; } QPushButton:hover { background-color: #e9e9e9; } """) def upload_template(self) -> None: """上传模板文件""" self.logger.debug("上传模板操作被触发") file_dialog = QFileDialog(self) file_dialog.setNameFilter("模板文件 (*.pdf *.png *.jpg *.jpeg)") file_dialog.setWindowTitle("选择模板文件") if file_dialog.exec(): template_path = file_dialog.selectedFiles()[0] self.logger.info(f"选择了模板文件: {template_path}") try: if self.template_canvas: success = self.template_canvas.load_template(template_path) if success: self.status_bar.showMessage(f"模板加载成功: {os.path.basename(template_path)}") else: self.status_bar.showMessage("模板加载失败,请检查文件格式") self.logger.error(f"模板加载失败: {template_path}") else: self.logger.error("模板画布未初始化") self.status_bar.showMessage("模板画布未初始化,无法加载模板") except Exception as e: self.logger.error(f"模板加载失败: {str(e)}", exc_info=True) self.status_bar.showMessage("模板加载失败,请检查文件格式或权限") def add_text_region(self) -> None: """添加文本区域""" self.logger.debug("添加文本区域操作被触发") if self.template_canvas: try: self.template_canvas.set_mode(TemplateCanvas.MODE_TEXT) self.status_bar.showMessage("已切换到【添加文本区域】模式") except Exception as e: self.logger.error(f"设置文本区域模式失败: {str(e)}", exc_info=True) self.status_bar.showMessage("设置模式失败,请查看日志") else: self.logger.error("模板画布未初始化") self.status_bar.showMessage("模板画布未初始化,无法添加文本区域") def add_qr_region(self) -> None: """添加二维码区域""" self.logger.debug("添加二维码区域操作被触发") if self.template_canvas: try: self.template_canvas.set_mode(TemplateCanvas.MODE_QR) self.status_bar.showMessage("添加二维码区域模式激活") except Exception as e: self.logger.error(f"设置二维码区域模式失败: {str(e)}", exc_info=True) self.status_bar.showMessage("设置二维码区域模式失败,请重试") else: self.logger.error("模板画布未初始化") self.status_bar.showMessage("模板画布未初始化,无法添加二维码区域") def import_data(self) -> None: """导入数据文件""" self.logger.debug("导入数据操作被触发") file_dialog = QFileDialog(self) file_dialog.setNameFilter("数据文件 (*.csv *.xlsx *.xls)") file_dialog.setWindowTitle("选择数据文件") if file_dialog.exec(): data_path = file_dialog.selectedFiles()[0] self.logger.info(f"选择了数据文件: {data_path}") try: if self.data_mapper: self.data_mapper.import_data() self.status_bar.showMessage(f"数据文件已导入: {os.path.basename(data_path)}") else: self.logger.error("数据映射器未初始化") self.status_bar.showMessage("数据映射器未初始化,无法导入数据文件") except Exception as e: self.logger.error(f"导入数据失败: {str(e)}", exc_info=True) self.status_bar.showMessage("导入数据失败,请检查文件格式或权限") if self.data_mapper: self.data_mapper.import_data() self.update_rerun_button_state() #更新按钮状态 def save_config(self) -> None: """保存配置文件""" self.logger.debug("保存配置操作被触发") try: if self.data_mapper: mapping_config = self.data_mapper.get_mapping() if mapping_config: # 假设配置文件保存到 JSON 文件 config_path = os.path.join(self.data_path, "config.json") with open(config_path, "w", encoding="utf-8") as config_file: import json json.dump(mapping_config, config_file, ensure_ascii=False, indent=4) self.logger.info(f"配置文件已保存至: {config_path}") self.status_bar.showMessage("配置文件保存成功") else: self.logger.warning("无映射配置可保存") self.status_bar.showMessage("无映射配置可保存") else: self.logger.error("数据映射器未初始化") self.status_bar.showMessage("数据映射器未初始化,无法保存配置") except Exception as e: self.logger.error(f"保存配置失败: {str(e)}", exc_info=True) self.status_bar.showMessage("保存配置失败,请检查文件权限") def generate_output(self) -> None: """生成输出文件""" self.logger.debug("开始合成操作被触发") if not self.current_project: self.logger.error("未选择项目,无法生成输出文件") self.status_bar.showMessage("未选择项目,无法生成输出文件") return try: # 检查是否有有效的模板和数据映射 if not self.template_canvas or not self.template_canvas.template_item: self.logger.error("无有效模板,无法生成输出文件") self.status_bar.showMessage("无有效模板,无法生成输出文件") return if not self.data_mapper or not self.data_mapper.data_frame: self.logger.error("无有效数据,无法生成输出文件") self.status_bar.showMessage("无有效数据,无法生成输出文件") return # 开始合成操作 self.status_bar.showMessage("正在生成输出文件...") self.logger.info("开始合成输出文件") output_path = os.path.join(self.data_path, "output.pdf") self.compositor.compose(output_path) self.status_bar.showMessage("输出文件生成成功") self.logger.info(f"输出文件已生成至: {output_path}") except Exception as e: self.logger.error(f"生成输出文件失败: {str(e)}", exc_info=True) self.status_bar.showMessage("生成输出文件失败,请检查设置") def rerun_project(self) -> None: """执行翻单操作""" self.logger.debug("执行翻单操作被触发") if self.btn_rerun is not None: self.btn_rerun.setEnabled(False) # 防止多次点击 else: self.logger.error("btn_rerun 控件未正确初始化") return if not self.current_project: self.logger.error("未选择项目,无法执行翻单") self.status_bar.showMessage("未选择项目,无法执行翻单") return try: # 检查是否有有效的模板和数据映射 if not self.template_canvas or not self.template_canvas.template_item: self.logger.error("无有效模板,无法执行翻单") self.status_bar.showMessage("无有效模板,无法执行翻单") return if not self.data_mapper or not self.data_mapper.data_frame: self.logger.error("无有效数据,无法执行翻单") self.status_bar.showMessage("无有效数据,无法执行翻单") return # 执行翻单操作 self.status_bar.showMessage("正在执行翻单...") self.logger.info("开始执行翻单") output_path = os.path.join(self.data_path, "rerun_output.pdf") self.compositor.compose(output_path) self.status_bar.showMessage("翻单执行成功") self.logger.info(f"翻单输出文件已生成至: {output_path}") # 恢复按钮状态 self.btn_rerun.setEnabled(True) except Exception as e: self.logger.error(f"翻单执行失败: {str(e)}", exc_info=True) self.status_bar.showMessage("翻单执行失败,请检查设置") # 恢复按钮状态 self.btn_rerun.setEnabled(True) # 测试运行 if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec()) 根据以上修改建议,请帮我输出完整的修改后的main_window.py的代码
07-16
以下是main_window.py的完整代码: # 主窗口 - PyQt6 版本 import os import sys import datetime import logging import faulthandler # 启用详细日志和调试模式 import tracemalloc # 内存监控 import ctypes # 启用故障处理程序 faulthandler.enable() # 添加虚拟环境检查 if not hasattr(sys, 'real_prefix') and not hasattr(sys, 'base_prefix'): logging.warning("警告: 未在虚拟环境中运行,建议使用虚拟环境") # 配置详细日志 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("app_debug.log"), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) logger.info("应用程序启动") logger.info(f"Python 路径: {sys.executable}") logger.info(f"参数: {sys.argv}") # 添加更多诊断信息 logger.info(f"系统路径: {sys.path}") logger.info(f"工作目录: {os.getcwd()}") # 启用详细错误报告 faulthandler.enable() os.environ['QT_DEBUG_PLUGINS'] = '1' # 启用 Qt 插件调试 os.environ['QTWEBENGINE_CHROMIUM_FLAGS'] = '--disable-gpu --no-sandbox' # 禁用 GPU 加速 # 设置 Windows 错误报告 if sys.platform == 'win32': ctypes.windll.kernel32.SetErrorMode(0x0001 | 0x0002) # SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX # 设置堆栈保护 os.environ['QT_FATAL_CRITICALS'] = '1' os.environ['QT_FATAL_WARNINGS'] = '1' # 确保先导入PyQt6核心模块 try: from PyQt6.QtCore import QObject, pyqtSignal, PYQT_VERSION_STR, Qt, QRectF, QTimer from PyQt6.QtGui import QIcon, QPixmap, QColor, QFont, QAction from PyQt6.QtWidgets import ( QApplication, QMainWindow, QFileDialog, QSplitter, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QComboBox, QLabel, QStatusBar, QToolBar, QDockWidget, QTabWidget, QMessageBox, QInputDialog, QDialog, QMenu ) logger.info(f"PyQt6 版本: {PYQT_VERSION_STR}") except ImportError as e: logger.error(f"导入 PyQt6 失败: {str(e)}") # 添加更详细的错误信息 import traceback logger.error(traceback.format_exc()) # 无法继续运行,退出程序 sys.exit(1) # 然后导入其他模块 from src.auto_print_system.ui.template_editor import TemplateCanvas, TextRegionItem, QRRegionItem, LogoRegionItem from src.auto_print_system.ui.data_mapper import DataMapperWidget from src.auto_print_system.ui.preview_dialog import PreviewDialog from src.auto_print_system.ui.history_dialog import HistoryDialog from src.auto_print_system.core.project_manager import ProjectManager from src.auto_print_system.core.file_compositor import FileCompositor from src.auto_print_system.core.font_manager import FontManager logger = logging.getLogger(__name__) # 定义属性面板类 class BasePropertiesPanel(QWidget): propertyChanged = pyqtSignal(str, object) # 定义信号 def __init__(self, parent=None): super().__init__(parent) self.region = None self.setup_ui() def setup_ui(self): """子类实现具体UI布局""" pass def set_region(self, region): """设置当前区域""" self.region = region self.update_ui_from_region() def update_ui_from_region(self): """根据区域更新UI""" pass def update_region_from_ui(self): """根据UI更新区域""" pass class TextPropertiesPanel(BasePropertiesPanel): def setup_ui(self): layout = QVBoxLayout(self) layout.addWidget(QLabel("文本属性面板 - 实际实现需要添加更多控件")) # 这里添加实际的文本属性控件 self.setLayout(layout) def update_ui_from_region(self): if self.region and isinstance(self.region, TextRegionItem): # 根据区域更新UI控件 pass def update_region_from_ui(self): if self.region: # 根据UI更新区域属性 pass class QRPropertiesPanel(BasePropertiesPanel): def setup_ui(self): layout = QVBoxLayout(self) layout.addWidget(QLabel("二维码属性面板 - 实际实现需要添加更多控件")) # 这里添加实际的二维码属性控件 self.setLayout(layout) def update_ui_from_region(self): if self.region and isinstance(self.region, QRRegionItem): # 根据区域更新UI控件 pass class LogoPropertiesPanel(BasePropertiesPanel): def setup_ui(self): layout = QVBoxLayout(self) layout.addWidget(QLabel("Logo属性面板 - 实际实现需要添加更多控件")) # 这里添加实际的Logo属性控件 self.setLayout(layout) def update_ui_from_region(self): if self.region and isinstance(self.region, LogoRegionItem): # 根据区域更新UI控件 pass class MainWindow(QMainWindow): def __init__(self, data_path=None): super().__init__() #简化初始化 self.central_widget = None self.logo_path = None self.project_manager = None self.btn_generate = None self.field_mapping_widget = None self.btn_add_text = None self.btn_add_qr = None self.current_project = None self.spot_color_path = None self.compositor = None self.font_manager = None self.left_layout = None self.btn_upload = None self.btn_import_data = None self.template_canvas = None self.status_bar = None self.btn_rerun = None self.btn_history = None self.data_mapper = None self.btn_save_config = None self.right_layout = None self.logo_props_panel = None self.text_props_panel = None self.props_tabs = None self.qr_props_panel = None self.history_combo = None self.logger = logging.getLogger(__name__) self.logger.info("主窗口初始化开始") self.data_path = data_path try: # 尝试简化初始化 self.logger.info("尝试基本初始化...") self.setWindowTitle("自动打印系统") self.resize(800, 600) # 延迟加载复杂组件 self.logger.info("延迟加载复杂组件") QTimer.singleShot(100, self.delayed_init) except Exception as e: self.logger.exception("主窗口初始化期间发生错误") raise def delayed_init(self): try: self.logger.info("开始延迟初始化") self.init_components() self.logger.info("组件初始化完成") # 其他初始化... except Exception as e: self.logger.exception("延迟初始化期间发生错误") # 显示错误消息 QMessageBox.critical( self, "初始化错误", f"初始化过程中发生错误:\n{str(e)}", QMessageBox.StandardButton.Ok ) self.close() self.data_path = self.data_path or os.path.join(os.path.dirname(__file__), "../../../data") self.setWindowTitle("自动拼版系统") self.setGeometry(100, 100, 1200, 800) # 声明所有实例变量 self.project_manager = None self.font_manager = None self.compositor = None self.status_bar = None self.current_project = None self.logo_path = None self.spot_color_path = None # 布局和控件变量 self.left_layout = None self.btn_upload = None self.btn_add_text = None self.btn_add_qr = None self.template_canvas = None self.props_tabs = None self.text_props_panel = None self.qr_props_panel = None self.logo_props_panel = None self.right_layout = None self.btn_import_data = None self.btn_save_config = None self.data_mapper = None self.field_mapping_widget = None self.history_combo = None self.btn_history = None self.btn_generate = None self.btn_rerun = None # 初始化核心组件 self.init_components() # 创建项目管理器 self.project_manager = ProjectManager(self.data_path) # 创建字体管理器 self.font_manager = FontManager(self.data_path) # 创建合成引擎 self.compositor = FileCompositor(self.data_path, self.font_manager) # 设置状态栏 self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage("就绪") # 当前项目 self.current_project = None self.logo_path = None self.spot_color_path = None # 加载样式 self.apply_styles() def init_components(self): """初始化主界面组件""" self.logger.info("开始初始化组件") # 逐步初始化各个组件,添加日志 self.logger.info("创建中央部件...") self.central_widget = QWidget() self.setCentralWidget(self.central_widget) self.logger.info("创建布局...") self.layout = QVBoxLayout(self.central_widget) # 逐步添加其他组件,每一步都添加日志 # 例如先注释掉停靠窗口的创建 # self.logger.info("创建左侧停靠窗口...") # self.create_left_dock() # 只添加一个简单的标签用于测试 test_label = QLabel("应用程序启动成功,基本UI已加载") self.layout.addWidget(test_label) self.logger.info("基本组件初始化完成") # def create_left_dock(self): # # 暂时注释掉停靠窗口创建 # pass # 创建菜单栏 self.create_menus() # 创建工具栏 self.create_toolbar() # 创建主中心区域 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 左侧模板编辑区(占40%) left_dock = QDockWidget("模板编辑区", self) left_dock.setFeatures(QDockWidget.DockWidgetFeature.DockWidgetMovable | QDockWidget.DockWidgetFeature.DockWidgetFloatable) left_widget = QWidget() self.left_layout = QVBoxLayout(left_widget) # 模板操作按钮 btn_layout = QHBoxLayout() self.btn_upload = QPushButton("上传模板") self.btn_upload.clicked.connect(self.upload_template) self.btn_add_text = QPushButton("添加文本框") self.btn_add_text.clicked.connect(self.add_text_region) self.btn_add_qr = QPushButton("添加二维码区") self.btn_add_qr.clicked.connect(self.add_qr_region) btn_layout.addWidget(self.btn_upload) btn_layout.addWidget(self.btn_add_text) btn_layout.addWidget(self.btn_add_qr) self.left_layout.addLayout(btn_layout) # 模板编辑画布 self.template_canvas = TemplateCanvas(data_path=self.data_path) self.template_canvas.setAlignment(Qt.AlignmentFlag.AlignCenter) self.template_canvas.setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;") self.left_layout.addWidget(self.template_canvas) # 属性面板(标签页) self.props_tabs = QTabWidget() self.text_props_panel = TextPropertiesPanel() self.qr_props_panel = QRPropertiesPanel() self.logo_props_panel = LogoPropertiesPanel() self.props_tabs.addTab(self.text_props_panel, "文本属性") self.props_tabs.addTab(self.qr_props_panel, "二维码属性") self.props_tabs.addTab(self.logo_props_panel, "Logo属性") # 连接信号 if hasattr(self.text_props_panel, 'propertyChanged'): self.text_props_panel.propertyChanged.connect(self.handle_property_change) if hasattr(self.qr_props_panel, 'propertyChanged'): self.qr_props_panel.propertyChanged.connect(self.handle_property_change) if hasattr(self.logo_props_panel, 'propertyChanged'): self.logo_props_panel.propertyChanged.connect(self.handle_property_change) self.left_layout.addWidget(self.props_tabs) left_dock.setWidget(left_widget) self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, left_dock) # 右侧数据区域(占60%) right_dock = QDockWidget("数据映射区", self) right_widget = QWidget() self.right_layout = QVBoxLayout(right_widget) # 数据操作按钮 data_btn_layout = QHBoxLayout() self.btn_import_data = QPushButton("导入数据") self.btn_import_data.clicked.connect(self.import_data) self.btn_save_config = QPushButton("保存配置") self.btn_save_config.clicked.connect(self.save_config) data_btn_layout.addWidget(self.btn_import_data) data_btn_layout.addWidget(self.btn_save_config) self.right_layout.addLayout(data_btn_layout) # 数据映射表格 self.data_mapper = DataMapperWidget(self) self.right_layout.addWidget(self.data_mapper, 1) # 字段映射区域 self.right_layout.addWidget(QLabel("字段映射配置:")) self.field_mapping_widget = QLabel("拖拽字段到模板区域进行映射") self.field_mapping_widget.setStyleSheet("min-height: 100px; background-color: #fff; border: 1px dashed #999;") self.right_layout.addWidget(self.field_mapping_widget) right_dock.setWidget(right_widget) self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, right_dock) # 底部操作区 bottom_widget = QWidget() bottom_layout = QHBoxLayout(bottom_widget) # 翻单历史选择 bottom_layout.addWidget(QLabel("翻单历史:")) self.history_combo = QComboBox() self.history_combo.setMinimumWidth(200) self.history_combo.currentIndexChanged.connect(self.on_history_selected) self.refresh_history_combo() # 历史管理按钮 self.btn_history = QPushButton("管理") self.btn_history.setToolTip("管理历史记录") self.btn_history.clicked.connect(self.manage_history) bottom_layout.addWidget(self.history_combo) bottom_layout.addWidget(self.btn_history) bottom_layout.addSpacing(20) # 合成按钮 self.btn_generate = QPushButton("开始合成") self.btn_generate.setStyleSheet( "background-color: #4CAF50; color: white; font-weight: bold; padding: 8px 16px;") self.btn_generate.clicked.connect(self.generate_output) bottom_layout.addWidget(self.btn_generate) # 翻单按钮 self.btn_rerun = QPushButton("执行翻单") self.btn_rerun.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold; padding: 8px 16px;") self.btn_rerun.clicked.connect(self.rerun_project) self.btn_rerun.setEnabled(False) bottom_layout.addWidget(self.btn_rerun) main_layout.addWidget(bottom_widget) def create_menus(self): """创建菜单栏""" menubar = self.menuBar() # 文件菜单 file_menu = menubar.addMenu('文件') # 使用 QAction 创建菜单项 new_action = QAction('新建项目', self) open_action = QAction('打开项目', self) save_action = QAction('保存项目', self) exit_action = QAction('退出', self) exit_action.triggered.connect(self.close) save_project_action = QAction('保存项目', self) save_project_action.triggered.connect(self.save_project) file_menu.addAction(save_project_action) open_project_action = QAction('打开项目', self) open_project_action.triggered.connect(self.open_project) file_menu.addAction(open_project_action) file_menu.addAction(new_action) file_menu.addAction(open_action) file_menu.addAction(save_action) file_menu.addSeparator() file_menu.addAction(exit_action) # 编辑菜单 edit_menu = menubar.addMenu('编辑') edit_menu.addAction('撤销') edit_menu.addAction('重做') edit_menu.addSeparator() font_action = QAction('字体管理', self) font_action.triggered.connect(self.open_font_manager) edit_menu.addAction(font_action) edit_menu.addAction('专色设置') # 视图菜单 view_menu = menubar.addMenu('视图') view_menu.addAction('放大') view_menu.addAction('缩小') view_menu.addAction('实际大小') # 帮助菜单 help_menu = menubar.addMenu('帮助') help_menu.addAction('用户手册') about_action = QAction('关于', self) about_action.triggered.connect(self.show_about) help_menu.addAction(about_action) def create_toolbar(self): """创建工具栏""" toolbar = QToolBar("主工具栏") self.addToolBar(toolbar) # 添加工具按钮 toolbar.addAction("打开模板", self.upload_template) toolbar.addAction("导入数据", self.import_data) toolbar.addSeparator() toolbar.addAction("添加文本", self.add_text_region) toolbar.addAction("添加二维码", self.add_qr_region) toolbar.addSeparator() toolbar.addAction("开始合成", self.generate_output) def apply_styles(self): """应用基本样式""" self.setStyleSheet(""" QMainWindow { background-color: #f5f5f5; } QPushButton { padding: 5px 10px; border: 1px solid #ccc; border-radius: 4px; } QPushButton:hover { background-color: #e9e9e9; } QDockWidget { background-color: #f0f0f0; border: 1px solid #ddd; titlebar-close-icon: url(resources/icons/close.png); titlebar-normal-icon: url(resources/icons/restore.png); } QDockWidget::title { background: #e0e0e0; padding: 4px; text-align: center; font-weight: bold; } QTabWidget::pane { border: 1px solid #ddd; top: 1px; background: white; } QTabBar::tab { background: #e0e0e0; border: 1px solid #ccc; padding: 6px 12px; border-top-left-radius: 4px; border-top-right-radius: 4px; } QTabBar::tab:selected { background: white; border-bottom-color: white; } """) # ===== 功能槽函数 ===== def upload_template(self): """上传模板图片""" file_path, _ = QFileDialog.getOpenFileName( self, "选择模板图片", self.data_path, "图像文件 (*.png *.jpg *.jpeg *.bmp);;PDF文件 (*.pdf)" ) if file_path: # 加载模板到画布 if self.template_canvas.load_template(file_path): self.status_bar.showMessage(f"已加载模板: {os.path.basename(file_path)}") # 更新区域信息 self.update_regions() def import_data(self): """导入数据文件""" file_path, _ = QFileDialog.getOpenFileName( self, "选择数据文件", self.data_path, "CSV文件 (*.csv);;Excel文件 (*.xlsx *.xls)" ) if file_path: self.status_bar.showMessage(f"已导入数据: {file_path}") # 解析并显示数据 self.data_mapper.import_data(file_path) def add_text_region(self): """添加文本区域""" self.template_canvas.set_mode(TemplateCanvas.MODE_TEXT) self.status_bar.showMessage("请在模板上框选文本区域") def add_qr_region(self): """添加二维码区域""" self.template_canvas.set_mode(TemplateCanvas.MODE_QR) self.status_bar.showMessage("请在模板上框选二维码区域") def save_config(self): """保存当前配置到项目文件""" if not self.template_canvas or not self.template_canvas.template_path: self.status_bar.showMessage("请先加载模板") return # 获取所有区域数据 regions_data = self.template_canvas.get_regions() # 获取映射关系 mapping = self.data_mapper.get_mapping() # 创建项目数据 project_data = { 'template_path': self.template_canvas.template_path, 'regions': regions_data, 'mapping': mapping } # 保存到文件 project_name = os.path.basename(self.template_canvas.template_path).split('.')[0] self.status_bar.showMessage(f"配置已保存: {project_name}") # 实际保存到文件 config_path = self.project_manager.save_project(project_data, project_name) if config_path: QMessageBox.information(self, "保存成功", f"项目已保存:\n{config_path}") self.refresh_history_combo() def generate_output(self): """开始合成""" self.status_bar.showMessage("合成中...") # 检查必要组件 if not self.template_canvas or not self.template_canvas.template_path: self.status_bar.showMessage("请先加载模板") QMessageBox.warning(self, "错误", "请先加载模板") return if not self.data_mapper.data_frame: self.status_bar.showMessage("请先导入数据") QMessageBox.warning(self, "错误", "请先导入数据") return # 获取区域和映射 regions = self.template_canvas.get_regions() mapping = self.data_mapper.get_mapping() # 生成输出文件名 timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") output_dir = os.path.join(self.data_path, "outputs") os.makedirs(output_dir, exist_ok=True) output_path = os.path.join(output_dir, f"output_{timestamp}.pdf") # 执行合成 success, logs = self.compositor.composite( template_path=self.template_canvas.template_path, regions=regions, mapping=mapping, data_source=self.data_mapper.data_frame, output_path=output_path, logo_path=self.logo_path if hasattr(self, 'logo_path') else "", spot_color_path=self.spot_color_path if hasattr(self, 'spot_color_path') else "" ) # 显示结果 if success: self.status_bar.showMessage(f"合成完成! 输出文件: {output_path}") # 显示预览对话框 preview_dialog = PreviewDialog(output_path, self) preview_dialog.exec() else: # 显示错误日志 error_msg = "\n".join(logs[-5:]) if logs else "未知错误" QMessageBox.critical(self, "合成错误", f"合成过程中发生错误:\n{error_msg}") def preview_current(self): """预览当前配置""" if not self.template_canvas or not self.template_canvas.template_path: QMessageBox.warning(self, "预览", "请先加载模板") return if not self.data_mapper.data_frame or len(self.data_mapper.data_frame) == 0: QMessageBox.warning(self, "预览", "请先导入数据") return # 获取第一行数据 data_row = self.data_mapper.data_frame.iloc[0].to_dict() # 获取区域和映射 regions = self.template_canvas.get_regions() mapping = self.data_mapper.get_mapping() # 创建临时预览文件 preview_path = os.path.join(self.compositor.temp_dir, "preview.png") # 执行合成 if hasattr(self.compositor, 'composite_single'): success, logs = self.compositor.composite_single( template_path=self.template_canvas.template_path, regions=regions, mapping=mapping, data_row=data_row, output_path=preview_path, logo_path=self.logo_path if hasattr(self, 'logo_path') else "", spot_color_path=self.spot_color_path if hasattr(self, 'spot_color_path') else "" ) else: QMessageBox.warning(self, "预览错误", "composite_single方法未实现") return if success: # 显示预览对话框 preview_dialog = PreviewDialog(preview_path, self) preview_dialog.exec() else: error_msg = "\n".join(logs) if logs else "未知错误" QMessageBox.critical(self, "预览错误", f"预览生成失败:\n{error_msg}") def open_font_manager(self): """打开字体管理器""" self.status_bar.showMessage("打开字体管理器") QMessageBox.information(self, "字体管理", "字体管理功能将在后续版本中实现") def update_regions(self): """更新模板区域信息""" if not self.template_canvas: return # 获取所有区域 regions = [region.get_name() for region in self.template_canvas.regions] # 更新数据映射模块 self.data_mapper.update_regions(regions) def update_property_panel(self, region): """更新属性面板显示""" if isinstance(region, TextRegionItem): self.props_tabs.setCurrentIndex(0) self.text_props_panel.set_region(region) elif isinstance(region, QRRegionItem): self.props_tabs.setCurrentIndex(1) self.qr_props_panel.set_region(region) elif isinstance(region, LogoRegionItem): self.props_tabs.setCurrentIndex(2) self.logo_props_panel.set_region(region) def handle_property_change(self, key, value): """处理属性变化""" self.template_canvas.set_region_property(key, value) def refresh_history_combo(self): """刷新历史记录下拉框""" self.history_combo.clear() self.history_combo.addItem("选择历史项目...", None) history = self.project_manager.get_history() for item in history: self.history_combo.addItem(item['name'], item['path']) def on_history_selected(self, index): """历史项目选择事件""" if index <= 0: self.current_project = None self.btn_rerun.setEnabled(False) return project_path = self.history_combo.itemData(index) if project_path: self.current_project = self.project_manager.load_project(project_path) self.btn_rerun.setEnabled(True) def manage_history(self): """打开历史记录管理对话框""" dialog = HistoryDialog(self.project_manager, self) if dialog.exec() == QDialog.DialogCode.Accepted: self.refresh_history_combo() def save_project(self): """保存当前项目配置""" if not self.template_canvas or not self.template_canvas.template_path: QMessageBox.warning(self, "保存项目", "请先加载模板") return if not self.data_mapper.data_frame: QMessageBox.warning(self, "保存项目", "请先导入数据") return # 获取项目数据 project_data = { 'template_path': self.template_canvas.template_path, 'regions': self.template_canvas.get_regions(), 'mapping': self.data_mapper.get_mapping(), 'data_source': os.path.basename(self.data_mapper.data_path) if hasattr(self.data_mapper, 'data_path') else "", 'logo_path': self.logo_path if hasattr(self, 'logo_path') else "", 'spot_color_path': self.spot_color_path if hasattr(self, 'spot_color_path') else "", 'timestamp': datetime.datetime.now().isoformat(), 'description': "" } # 输入项目名称 name, ok = QInputDialog.getText( self, "保存项目", "输入项目名称:", text=f"项目_{datetime.datetime.now().strftime('%Y%m%d')}" ) if ok and name: # 保存项目 config_path = self.project_manager.save_project(project_data, name) if config_path: QMessageBox.information(self, "保存成功", f"项目已保存:\n{config_path}") self.refresh_history_combo() def open_project(self): """打开项目""" # 使用历史记录对话框选择项目 dialog = HistoryDialog(self.project_manager, self) if dialog.exec() == QDialog.DialogCode.Accepted: selected_project = dialog.get_selected_project() if selected_project: self.load_project(selected_project['path']) def load_project(self, project_dir): """加载项目到界面""" project_data = self.project_manager.load_project(project_dir) if not project_data: QMessageBox.warning(self, "加载项目", "项目加载失败") return # 加载模板 if self.template_canvas.load_template(project_data['template_path']): self.status_bar.showMessage(f"已加载模板: {os.path.basename(project_data['template_path'])}") # 清除现有区域 self.template_canvas.clear_regions() # 添加区域 for region_data in project_data['regions']: self._create_region_from_data(region_data) # 更新区域显示 self.update_regions() # 加载Logo和专色路径 if 'logo_path' in project_data and os.path.exists(project_data['logo_path']): self.logo_path = project_data['logo_path'] if 'spot_color_path' in project_data and os.path.exists(project_data['spot_color_path']): self.spot_color_path = project_data['spot_color_path'] # 加载映射关系 self.data_mapper.mapping = project_data['mapping'] self.data_mapper.update_mapping_list() # 提示加载数据源 data_source = project_data.get('data_source', '') if data_source: data_path = self._get_data_source_path(project_data) if data_path: self.data_mapper.import_data(data_path) # 设置当前项目 self.current_project = project_data self.btn_rerun.setEnabled(True) # 更新历史记录下拉框 self.refresh_history_combo() # 选中当前项目 for i in range(self.history_combo.count()): if self.history_combo.itemData(i) == project_dir: self.history_combo.setCurrentIndex(i) break def _create_region_from_data(self, region_data): """根据区域数据创建区域项""" rect = QRectF( region_data['rect']['x'], region_data['rect']['y'], region_data['rect']['width'], region_data['rect']['height'] ) region = None if region_data['type'] == 'text': region = TextRegionItem(rect) region.set_name(region_data.get('name', '文本区域')) if 'font' in region_data: font = QFont() font.fromString(region_data['font']) region.set_font(font) if 'align' in region_data: region.set_align(region_data['align']) if 'color' in region_data: region.set_color(QColor(region_data['color'])) elif region_data['type'] == 'qr': region = QRRegionItem(rect) region.set_name(region_data.get('name', '二维码区域')) if 'code_type' in region_data: region.code_type = region_data['code_type'] elif region_data['type'] == 'logo': # 创建空区域,实际Logo在合成时加载 region = LogoRegionItem(rect, QPixmap()) region.set_name(region_data.get('name', 'Logo区域')) if 'opacity' in region_data: region.set_opacity(region_data['opacity']) # 添加到场景 if region: self.template_canvas.scene.addItem(region) self.template_canvas.regions.append(region) def rerun_project(self): """执行翻单操作""" if not self.current_project: QMessageBox.warning(self, "翻单", "请先选择项目") return # 确认数据源 data_source_path = self._get_data_source_path(self.current_project) if not data_source_path: return # 生成输出文件名 timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") output_dir = os.path.join(self.data_path, "outputs") os.makedirs(output_dir, exist_ok=True) output_path = os.path.join(output_dir, f"翻单_{self.current_project.get('name', 'project')}_{timestamp}.pdf") # 执行合成 success, logs = self.compositor.composite( template_path=self.current_project['template_path'], regions=self.current_project['regions'], mapping=self.current_project['mapping'], data_source=data_source_path, output_path=output_path, logo_path=self.current_project.get('logo_path', ''), spot_color_path=self.current_project.get('spot_color_path', '') ) # 显示结果 if success: self.status_bar.showMessage(f"翻单完成! 输出文件: {output_path}") # 显示预览对话框 preview_dialog = PreviewDialog(output_path, self) preview_dialog.exec() else: # 显示错误日志 error_msg = "\n".join(logs[-5:]) if logs else "未知错误" QMessageBox.critical(self, "翻单错误", f"翻单过程中发生错误:\n{error_msg}") def _get_data_source_path(self, project): """获取数据源文件路径""" # 1. 检查项目是否指定了数据源 data_source_name = project.get('data_source', '') if data_source_name: # 尝试在项目目录中查找 project_dir = os.path.dirname(project['template_path']) project_data_path = os.path.join(project_dir, data_source_name) if os.path.exists(project_data_path): return project_data_path # 尝试在数据目录中查找 data_dir = os.path.join(self.data_path, "data") data_path = os.path.join(data_dir, data_source_name) if os.path.exists(data_path): return data_path # 2. 提示用户选择数据文件 file_path, _ = QFileDialog.getOpenFileName( self, "选择数据文件", self.data_path, "CSV文件 (*.csv);;Excel文件 (*.xlsx *.xls)" ) return file_path if file_path else None def show_about(self): """显示关于对话框""" about_text = ( "<h2>自动拼版系统</h2>" "<p>版本: 1.0.0</p>" "<p>基于 PyQt6 构建</p>" "<p>© 2023 自动拼版系统团队</p>" ) QMessageBox.about(self, "关于", about_text) # 测试运行 if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec()) # 检查内存模块 def log_memory(): try: snapshot = tracemalloc.take_snapshot() top_stats = snapshot.statistics('lineno') logger.info("内存使用统计:") for stat in top_stats[:10]: logger.info(stat) except Exception as e: logger.error(f"内存监控失败: {str(e)}") import atexit atexit.register(log_memory) 请帮我分析该代码块,并修改调整不合理的地方,以及排查出刚才分析的AttributeError: ‘NoneType’ object has no attribute ‘setEnabled’问题,并输出完整的修改后的main_window.py的代码
07-11
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值