QGroupBox边框设置 2011-11-09 19:28:59

本文介绍如何在Linux系统中使用样式表为QGroupBox组件添加边框,并详细解释了各个样式属性的作用。
linux系统中QGroupBox没有边框,所以要对其进行设置。用样式表来设置边框
点击 change stylesheet,输入
 
QGroupBox {
    border-width:1px;   //线的粗细
   border-style:solid;
  border-color:lightGray;   //颜色,
  margin-top: 0.5ex;  //文字在方框中位置的偏离度
}
QGroupBox::title {
     subcontrol-origin: margin;
     subcontrol-position: top left;
   left:25px;       //线的偏离度
     margin-left: 0px;
     padding:0 1px;   //文字在方框中位置的偏离度
}
import sys import os import numpy as np import pandas as pd from datetime import datetime from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel, QLineEdit, QPushButton, QTableWidget, QTableWidgetItem, QFileDialog, QMessageBox, QDialog, QScrollArea, QFormLayout, QHeaderView, QSizePolicy) from PyQt5.QtCore import Qt from PyQt5.QtGui import QFont, QPixmap, QBrush, QColor import matplotlib.pyplot as plt from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure from matplotlib.lines import Line2D import openpyxl from openpyxl.styles import PatternFill, Font, Alignment, Border, Side # 定义列颜色映射 COLOR_MAP = { 1: 'blue', # 第1列 2: 'red', # 第2列 3: 'green', # 第3列 4: 'orange', # 第4列 5: 'purple', # 第5列 6: 'brown', # 第6列 7: 'pink', # 第7列 8: 'gray', # 第8列 9: 'olive', # 第9列 10: 'cyan' # 第10列 } # 定义列配置 COLUMN_CONFIG = { 'chart1': [2, 3, 9], # 第一张图的列 'chart2': [4, 5, 9], # 第二张图的列 'chart3': [6, 7, 10], # 第三张图的列 'chart4': [8] # 第四张图的列 } # 设置中文字体支持 plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签 plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 class PreviewWindow(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("预览 - 四张误差图表") self.setGeometry(100, 100, 1000, 1200) # 增加窗口宽度 # 设置窗口标志,去掉问号,添加最小化和最大化按钮 self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint | Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint) layout = QVBoxLayout() self.setLayout(layout) # 创建滚动区域 scroll_area = QScrollArea() scroll_widget = QWidget() scroll_layout = QVBoxLayout() scroll_widget.setLayout(scroll_layout) # 添加四张图表 self.fig = Figure(figsize=(20/2.54, 25/2.54)) # 增加图形宽度 self.canvas = FigureCanvas(self.fig) # 创建四个子图 self.ax1 = self.fig.add_subplot(4, 1, 1) self.ax2 = self.fig.add_subplot(4, 1, 2) self.ax3 = self.fig.add_subplot(4, 1, 3) self.ax4 = self.fig.add_subplot(4, 1, 4) # 设置子图间距和边距,确保图表不被遮挡 self.fig.subplots_adjust(hspace=0.3, left=0.08, right=0.85, top=0.95, bottom=0.05) scroll_layout.addWidget(self.canvas) # 添加按钮 btn_layout = QHBoxLayout() self.export_img_btn = QPushButton('导出图片') self.export_img_btn.setFont(QFont("Arial", 12)) self.export_img_btn.setMinimumHeight(40) btn_layout.addWidget(self.export_img_btn) self.close_btn = QPushButton('关闭') self.close_btn.setFont(QFont("Arial", 12)) self.close_btn.setMinimumHeight(40) self.close_btn.clicked.connect(self.close) btn_layout.addWidget(self.close_btn) scroll_layout.addLayout(btn_layout) scroll_area.setWidget(scroll_widget) layout.addWidget(scroll_area) class ErrorChartTool(QMainWindow): def __init__(self): super().__init__() # 修正数据维度:8个方位,10列数据(1基准+9实测) self.data = np.zeros((8, 10)) self.preview_window = None self.initUI() def initUI(self): self.setWindowTitle('误差表单绘制工具') self.setGeometry(100, 100, 1500, 850) # 增加窗口大小 # 设置全局字体 font = QFont("Arial", 10) self.setFont(font) # 主布局 main_widget = QWidget() self.setCentralWidget(main_widget) layout = QHBoxLayout() main_widget.setLayout(layout) # 左侧信息输入区 info_group = QGroupBox("信息输入") info_group.setFont(QFont("Arial", 12, QFont.Bold)) info_layout = QFormLayout() info_layout.setLabelAlignment(Qt.AlignRight) info_layout.setFormAlignment(Qt.AlignLeft) info_layout.setHorizontalSpacing(10) info_layout.setVerticalSpacing(15) # 添加信息输入字段 self.fields = {} labels = ['产品编码', '制表人', '检查人', '制表时机'] for label in labels: field_label = QLabel(label + ":") field_label.setFont(QFont("Arial", 12)) field = QLineEdit() field.setFont(QFont("Arial", 12)) field.setMinimumHeight(35) info_layout.addRow(field_label, field) self.fields[label] = field # 添加日期字段 date_label = QLabel("制表日期:") date_label.setFont(QFont("Arial", 12)) self.date_field = QLineEdit(datetime.now().strftime('%Y%m%d')) self.date_field.setFont(QFont("Arial", 12)) self.date_field.setMinimumHeight(35) info_layout.addRow(date_label, self.date_field) info_group.setLayout(info_layout) info_group.setFixedWidth(350) layout.addWidget(info_group) # 中央区域 - 数据输入和按钮 center_widget = QWidget() center_layout = QVBoxLayout() center_layout.setAlignment(Qt.AlignTop) center_layout.setContentsMargins(20, 20, 20, 20) # 数据输入区 data_group = QGroupBox("数据输入") data_group.setFont(QFont("Arial", 12, QFont.Bold)) data_layout = QVBoxLayout() data_layout.setContentsMargins(10, 10, 10, 10) # 创建表格 self.table = QTableWidget(8, 11) # 8行11列(方位+10列数据) self.table.setFont(QFont("Arial", 10)) # 设置表头 headings = ['方位'] + [f'第{i}列' for i in range(1, 11)] self.table.setHorizontalHeaderLabels(headings) # 设置行标题(方位) angles = ['0°', '45°', '90°', '135°', '180°', '225°', '270°', '315°'] self.table.setVerticalHeaderLabels(angles) # 设置表格尺寸策略 self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.table.verticalHeader().setSectionResizeMode(QHeaderView.Stretch) # 设置表格样式 self.table.setMinimumHeight(400) self.table.setMinimumWidth(1000) # 设置表格边框和单元格对齐方式 self.table.setStyleSheet(""" QTableWidget { gridline-color: rgb(100, 100, 100); } QTableWidget::item { text-align: center; } """) # 设置表头样式 header_font = QFont("Arial", 11, QFont.Bold) self.table.horizontalHeader().setFont(header_font) self.table.verticalHeader().setFont(header_font) # 设置表头对齐方式 self.table.horizontalHeader().setDefaultAlignment(Qt.AlignCenter) self.table.verticalHeader().setDefaultAlignment(Qt.AlignCenter) data_layout.addWidget(self.table) data_group.setLayout(data_layout) center_layout.addWidget(data_group) # 按钮区域 button_layout = QHBoxLayout() button_layout.setSpacing(20) button_layout.setAlignment(Qt.AlignCenter) self.plot_btn = QPushButton('绘制图表') self.plot_btn.setFont(QFont("Arial", 12)) self.plot_btn.setMinimumSize(120, 40) button_layout.addWidget(self.plot_btn) self.export_img_btn = QPushButton('导出图片') self.export_img_btn.setFont(QFont("Arial", 12)) self.export_img_btn.setMinimumSize(120, 40) button_layout.addWidget(self.export_img_btn) self.export_data_btn = QPushButton('导出数据') self.export_data_btn.setFont(QFont("Arial", 12)) self.export_data_btn.setMinimumSize(120, 40) button_layout.addWidget(self.export_data_btn) self.import_data_btn = QPushButton('导入数据') self.import_data_btn.setFont(QFont("Arial", 12)) self.import_data_btn.setMinimumSize(120, 40) button_layout.addWidget(self.import_data_btn) center_layout.addLayout(button_layout) center_widget.setLayout(center_layout) layout.addWidget(center_widget, 1) # 连接信号和槽 self.plot_btn.clicked.connect(self.plot_charts) self.export_img_btn.clicked.connect(self.export_image) self.export_data_btn.clicked.connect(self.export_excel) self.import_data_btn.clicked.connect(self.import_excel) self.table.cellChanged.connect(self.calculate_errors) def calculate_errors(self, row, col): # 只有当基准值或实测值改变时才计算误差 if col >= 1 and col <= 10: try: # 获取基准值(第1列) base_item = self.table.item(row, 0) if not base_item or base_item.text() == '': return base_value = float(base_item.text()) # 计算所有实测列的误差 for c in range(1, 11): # 从第2列到第11列 measure_item = self.table.item(row, c) if measure_item and measure_item.text() != '': measure_value = float(measure_item.text()) # 对于所有方位,误差计算都是:实测值 - 基准值 # 特殊处理0°方位(实测值可能大于360°) if row == 0: # 0°方位 # 判断实测值相对于基准值的位置 diff = measure_value - base_value # 如果角度差大于180°,说明实测值在基准值左侧 if diff > 180: adjusted_base = base_value + 360 error = measure_value - adjusted_base # 如果角度差小于-180°,说明实测值在基准值右侧(但经过0°) elif diff < -180: adjusted_measure = measure_value + 360 error = adjusted_measure - base_value else: error = diff else: # 其他方位直接计算差值 error = measure_value - base_value # 更新数据数组(注意:数据数组的列索引从0开始,对应第2列到第11列) self.data[row, c-1] = error except ValueError: pass def plot_charts(self): if not self.preview_window: self.preview_window = PreviewWindow(self) self.preview_window.export_img_btn.clicked.connect(self.export_image) self.update_chart(self.preview_window.ax1, 1) self.update_chart(self.preview_window.ax2, 2) self.update_chart(self.preview_window.ax3, 3) self.update_chart(self.preview_window.ax4, 4) self.preview_window.canvas.draw() self.preview_window.show() def update_chart(self, ax, chart_num): ax.clear() # 角度数据,包括360°(即0°) angles = [0, 45, 90, 135, 180, 225, 270, 315, 360] # 根据图表编号获取要绘制的列 if chart_num == 1: columns = COLUMN_CONFIG['chart1'] elif chart_num == 2: columns = COLUMN_CONFIG['chart2'] elif chart_num == 3: columns = COLUMN_CONFIG['chart3'] else: columns = COLUMN_CONFIG['chart4'] # 绘制折线 for col in columns: # 数据数组的列索引从0开始,对应第2列到第11列 data_col = col - 2 color = COLOR_MAP.get(col, 'black') # 获取当前列的数据,并在末尾添加第一个数据点以形成闭环 data = list(self.data[:, data_col]) data.append(self.data[0, data_col]) # 将0°的数据加到末尾 ax.plot(angles, data, color=color, linewidth=2) # 只显示折线 ax.set_xlim(0, 360) ax.set_ylim(-5, 5) # 设置坐标轴刻度 ax.set_xticks([0, 45, 90, 135, 180, 225, 270, 315, 360]) ax.set_yticks(range(-5, 6, 1)) # 显示坐标轴刻度标签,但隐藏坐标轴标题 ax.set_xlabel('') ax.set_ylabel('') ax.set_title('') ax.grid(True) for spine in ax.spines.values(): spine.set_color('black') spine.set_linewidth(2) # 创建自定义图例 - 使用彩色短线 legend_elements = [] for col in columns: color = COLOR_MAP.get(col, 'black') legend_elements.append(Line2D([0], [0], color=color, lw=2, label='')) # 空标签 # 添加图例,放在右侧 legend = ax.legend(handles=legend_elements, loc='center left', bbox_to_anchor=(1, 0.5), fontsize=8) # 添加信息区 - 使用文本框格式 info_text = f"位置:第{chart_num}张\n\n" info_text += f"制表人:{self.fields['制表人'].text()}\n" info_text += f"制表日期:{self.date_field.text()}" # 在图表右侧添加信息文本框 ax.text(1.05, 0.5, info_text, fontsize=10, transform=ax.transAxes, verticalalignment='center', bbox=dict(boxstyle="round,pad=0.5", facecolor="lightblue", edgecolor="black", alpha=0.8)) def export_image(self): # 自动生成文件名 product_code = self.fields['产品编码'].text() date_str = self.date_field.text() default_name = f"{product_code}-{date_str}" if product_code else "error_chart" options = QFileDialog.Options() file_name, _ = QFileDialog.getSaveFileName( self, "导出图片", default_name, "PNG Images (*.png);;All Files (*)", options=options) if file_name: try: # 确保文件扩展名 if not file_name.endswith('.png'): file_name += '.png' fig = plt.figure(figsize=(21/2.54, 29.7/2.54), dpi=300) ax1 = fig.add_subplot(4, 1, 1) ax2 = fig.add_subplot(4, 1, 2) ax3 = fig.add_subplot(4, 1, 3) ax4 = fig.add_subplot(4, 1, 4) # 设置子图间距和边距,确保图表不被遮挡 fig.subplots_adjust(hspace=0.3, left=0.08, right=0.85, top=0.95, bottom=0.05) # 角度数据,包括360°(即0°) angles = [0, 45, 90, 135, 180, 225, 270, 315, 360] chart_configs = [ (1, COLUMN_CONFIG['chart1']), (2, COLUMN_CONFIG['chart2']), (3, COLUMN_CONFIG['chart3']), (4, COLUMN_CONFIG['chart4']) ] axes = [ax1, ax2, ax3, ax4] for i, (chart_num, columns) in enumerate(chart_configs): ax = axes[i] for col in columns: # 数据数组的列索引从0开始,对应第2列到第11列 data_col = col - 2 color = COLOR_MAP.get(col, 'black') # 获取当前列的数据,并在末尾添加第一个数据点以形成闭环 data = list(self.data[:, data_col]) data.append(self.data[0, data_col]) # 将0°的数据加到末尾 ax.plot(angles, data, color=color, linewidth=2) # 只显示折线 ax.set_xlim(0, 360) ax.set_ylim(-5, 5) # 设置坐标轴刻度 ax.set_xticks([0, 45, 90, 135, 180, 225, 270, 315, 360]) ax.set_yticks(range(-5, 6, 1)) # 显示坐标轴刻度标签,但隐藏坐标轴标题 ax.set_xlabel('') ax.set_ylabel('') ax.set_title('') ax.grid(True) for spine in ax.spines.values(): spine.set_color('black') spine.set_linewidth(2) # 创建自定义图例 - 使用彩色短线 legend_elements = [] for col in columns: color = COLOR_MAP.get(col, 'black') legend_elements.append(Line2D([0], [0], color=color, lw=2, label='')) # 空标签 # 添加图例,放在右侧 legend = ax.legend(handles=legend_elements, loc='center left', bbox_to_anchor=(1, 0.5), fontsize=8) # 添加信息区 - 使用文本框格式 info_text = f"位置:第{chart_num}张\n\n" info_text += f"制表人:{self.fields['制表人'].text()}\n" info_text += f"制表日期:{self.date_field.text()}" # 在图表右侧添加信息文本框 ax.text(1.05, 0.5, info_text, fontsize=10, transform=ax.transAxes, verticalalignment='center', bbox=dict(boxstyle="round,pad=0.5", facecolor="lightblue", edgecolor="black", alpha=0.8)) plt.savefig(file_name, dpi=300, bbox_inches='tight') plt.close(fig) QMessageBox.information(self, "成功", "图片已导出") except Exception as e: QMessageBox.critical(self, "错误", f"导出失败: {str(e)}") def export_excel(self): # 自动生成文件名 product_code = self.fields['产品编码'].text() date_str = self.date_field.text() default_name = f"{product_code}-{date_str}" if product_code else "error_data" options = QFileDialog.Options() file_name, _ = QFileDialog.getSaveFileName( self, "导出数据", default_name, "Excel Files (*.xlsx);;All Files (*)", options=options) if file_name: try: # 确保文件扩展名 if not file_name.endswith('.xlsx'): file_name += '.xlsx' # 创建Excel工作簿 wb = openpyxl.Workbook() ws = wb.active ws.title = "测量数据" # 设置标题样式 title_font = Font(bold=True, size=12) align_center = Alignment(horizontal='center', vertical='center') # 添加元数据 meta_data = [ ["产品编码", self.fields['产品编码'].text()], ["制表日期", self.date_field.text()], ["制表人", self.fields['制表人'].text()], ["检查人", self.fields['检查人'].text()], ["制表时机", self.fields['制表时机'].text()] ] for i, (key, value) in enumerate(meta_data, 1): ws.cell(row=i, column=1, value=key).font = title_font ws.cell(row=i, column=2, value=value) # 空一行 empty_row = len(meta_data) + 2 # 添加表头 angles = ['0°', '45°', '90°', '135°', '180°', '225°', '270°', '315°'] columns = ['方位'] + [f'第{i}列' for i in range(1, 11)] + [f'误差{i}' for i in range(2, 11)] for col_idx, col_name in enumerate(columns, 1): cell = ws.cell(row=empty_row, column=col_idx, value=col_name) cell.font = title_font cell.alignment = align_center # 添加数据 for row_idx, angle in enumerate(angles, empty_row+1): ws.cell(row=row_idx, column=1, value=angle) # 添加原始数据 for col_idx in range(2, 12): # 第2-12列是原始数据 item = self.table.item(row_idx-empty_row-1, col_idx-1) # 注意索引调整 value = item.text() if item and item.text() != '' else '0' ws.cell(row=row_idx, column=col_idx, value=float(value)) # 添加误差数据 for col_idx in range(12, 20): # 第12-20列是误差数据 error_value = self.data[row_idx-empty_row-1, col_idx-12] cell = ws.cell(row=row_idx, column=col_idx, value=error_value) # 条件格式:误差绝对值大于1,单元格标黄 if abs(error_value) > 1: cell.fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid") # 设置列宽 for col in ws.columns: ws.column_dimensions[col[0].column_letter].width = 12 # 保存文件 wb.save(file_name) QMessageBox.information(self, "成功", "数据已导出到Excel") except Exception as e: QMessageBox.critical(self, "错误", f"导出失败: {str(e)}") def import_excel(self): options = QFileDialog.Options() file_name, _ = QFileDialog.getOpenFileName(self, "导入数据", "", "Excel Files (*.xlsx);;All Files (*)", options=options) if file_name: try: # 读取元数据 meta_df = pd.read_excel(file_name, sheet_name='测量数据') if not meta_df.empty: # 查找元数据行 for i in range(len(meta_df)): if i < len(meta_df) and meta_df.iloc[i, 0] == '产品编码': self.fields['产品编码'].setText(str(meta_df.iloc[i, 1])) elif i < len(meta_df) and meta_df.iloc[i, 0] == '制表日期': self.date_field.setText(str(meta_df.iloc[i, 1])) elif i < len(meta_df) and meta_df.iloc[i, 0] == '制表人': self.fields['制表人'].setText(str(meta_df.iloc[i, 1])) elif i < len(meta_df) and meta_df.iloc[i, 0] == '检查人': self.fields['检查人'].setText(str(meta_df.iloc[i, 1])) elif i < len(meta_df) and meta_df.iloc[i, 0] == '制表时机': self.fields['制表时机'].setText(str(meta_df.iloc[i, 1])) # 读取测量数据 data_df = pd.read_excel(file_name, sheet_name='测量数据') # 找到数据开始的行(跳过元数据和表头) data_start_row = 0 for i in range(len(data_df)): if i < len(data_df) and data_df.iloc[i, 0] in ['0°', '45°', '90°', '135°', '180°', '225°', '270°', '315°']: data_start_row = i break # 导入数据到表格 for i in range(8): for j in range(11): # 11列数据(方位+10列数据) if data_start_row + i < len(data_df) and j < len(data_df.columns): value = str(data_df.iloc[data_start_row + i, j]) self.table.setItem(i, j, QTableWidgetItem(value)) # 重新计算误差 for i in range(8): self.calculate_errors(i, 1) QMessageBox.information(self, "成功", "数据已从Excel导入") except Exception as e: QMessageBox.critical(self, "错误", f"导入失败: {str(e)}") def main(): app = QApplication(sys.argv) ex = ErrorChartTool() ex.show() sys.exit(app.exec_()) if __name__ == '__main__': main()找出上述代码的问题并解决
09-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值