PyQt5:带样式代理的单元格下方行分隔线不显示问题求助
问题分析
你的代码里偶数行下方的分隔线无法显示在合并单元格和带背景色单元格下方,核心原因有两个:
- 绘制顺序错误:先调用父类
paint绘制基础内容,再画分隔线,之后又绘制背景色覆盖了分隔线 - 合并单元格的行判断逻辑缺失:跨多行的合并单元格(比如7、8、9列的单元格跨两行),它们的索引行是奇数行(i),但分隔线需要画在这个合并单元格的底部(也就是偶数行i+1的位置),原来的判断只针对偶数行的单元格,没处理合并单元格的底部绘制
解决方案
- 调整绘制顺序:先绘制背景色和文本,最后画分隔线,避免被覆盖
- 修正分隔线绘制逻辑:对于合并单元格,判断当前单元格的底部是否对应偶数行的底部;对于普通单元格,保持原偶数行判断
- 移除冗余代码:删除重复的
drawText调用 - 优化样式代理的
initStyleOption:避免不必要的样式重置影响绘制
修改后的完整代码
from PyQt5 import QtCore from PyQt5.QtWidgets import ( QWidget, QTableWidget, QHBoxLayout, QApplication, QTableWidgetItem, QStyledItemDelegate, QSizePolicy, QStyle) from PyQt5.QtGui import QColor from PyQt5.QtCore import Qt import random import sys class BottomLineDelegate(QStyledItemDelegate): def paint(self, painter, option, index): value = index.data() # 处理带背景色的单元格(7、8、9列) if index.column() in (7, 8, 9) and value: painter.save() # 绘制背景色 color = Qt.blue painter.fillRect(option.rect.adjusted(10, 10, -10, -10), color) # 绘制白色居中文本 painter.setPen(Qt.white) painter.drawText(option.rect, Qt.AlignCenter | Qt.AlignVCenter, value) painter.restore() else: # 普通单元格调用父类绘制 super().paint(painter, option, index) # 最后绘制分隔线,避免被覆盖 painter.save() painter.setPen(QtCore.Qt.black) # 获取表格实例,判断单元格的跨行范围 table = index.model().parent() if not isinstance(table, QTableWidget): painter.restore() return row_span = table.rowSpan(index.row(), index.column()) cell_bottom_row = index.row() + row_span - 1 # 原需求:偶数行(行号+1为偶数,即行号是奇数)下方绘制分隔线 if (cell_bottom_row + 1) % 2 == 0: painter.drawLine( option.rect.bottomLeft(), option.rect.bottomRight(), ) painter.restore() def initStyleOption(self, option, index): super().initStyleOption(option, index) columns_blue_bg = [7, 8, 9] value = index.data() if value is None: option.backgroundBrush = QColor(Qt.transparent) elif index.column() in columns_blue_bg: if value: option.displayAlignment = (Qt.AlignCenter | Qt.AlignVCenter) # 保留必要的状态重置,移除不必要的焦点状态重置 option.state &= ~(QStyle.State_Selected | QStyle.State_MouseOver) class Table(QWidget): def __init__(self, data): super().__init__() self.data = data self.initUI() self.tableWidget.setItemDelegate( BottomLineDelegate(self.tableWidget)) def initUI(self): self.setWindowTitle("QTableWidget - Repeating Merged Cells") self.resize(660, 300) conLayout = QHBoxLayout() self.tableWidget = QTableWidget() required_rows = len(self.data) * 2 self.tableWidget.setRowCount(required_rows) self.tableWidget.setColumnCount(15) conLayout.addWidget(self.tableWidget) # 隐藏表头 self.tableWidget.horizontalHeader().setVisible(False) self.tableWidget.verticalHeader().setVisible(False) # 设置列宽 column_width = 70 for col in range(self.tableWidget.columnCount()): self.tableWidget.setColumnWidth(col, column_width) # 插入数据和合并单元格 self.insert_data_into_table(self.data) # 表格属性设置 self.tableWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.tableWidget.setShowGrid(False) self.tableWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # 点击事件绑定 self.tableWidget.cellClicked.connect(self.on_cell_clicked) self.setLayout(conLayout) def on_cell_clicked(self, row, col): item = self.tableWidget.item(row, col) if item is not None: text = item.text().split('\n')[0] print(f"Clicked Cell Value: {text}") first_column_item = self.tableWidget.item(row+1, 0) if first_column_item is not None: first_column_text = first_column_item.text() print(f"First Column Value: {first_column_text}") def insert_data_into_table(self, new_data): print(new_data) self.data = new_data data_offset = 0 required_rows = len(self.data) * 2 total_rows = required_rows self.tableWidget.setRowCount(total_rows) for i in range(0, required_rows, 2): self.tableWidget.setSpan(i + 0, 2, 1, 2) self.tableWidget.setSpan(i + 0, 4, 1, 2) self.tableWidget.setSpan(i + 0, 7, 2, 1) self.tableWidget.setSpan(i + 0, 8, 2, 1) self.tableWidget.setSpan(i + 0, 9, 2, 1) self.tableWidget.setSpan(i + 0, 10, 2, 1) self.tableWidget.setSpan(i + 0, 11, 2, 1) self.tableWidget.setSpan(i + 0, 12, 2, 1) self.tableWidget.setSpan(i + 0, 14, 2, 1) self.tableWidget.setItem(i + 0, 2, QTableWidgetItem(self.data[data_offset][0])) self.tableWidget.setItem(i + 1, 0, QTableWidgetItem(str(self.data[data_offset][1]))) try: value = str(self.data[data_offset][2]) except IndexError: value = None self.tableWidget.setItem(i + 1, 4, QTableWidgetItem(value)) try: value = str(self.data[data_offset][3]) except IndexError: value = None self.tableWidget.setItem(i + 1, 5, QTableWidgetItem(value)) try: value = f"{self.data[data_offset][4][0]}\n{self.data[data_offset][5][0]}" except IndexError: value = None self.tableWidget.setItem(i + 0, 9, QTableWidgetItem(value)) try: value = f"{self.data[data_offset][4][1]}\n{self.data[data_offset][5][1]}" except IndexError: value = None self.tableWidget.setItem(i + 0, 8, QTableWidgetItem(value)) try: value = f"{self.data[data_offset][4][2]}\n{self.data[data_offset][5][2]}" except IndexError: value = None self.tableWidget.setItem(i + 0, 7, QTableWidgetItem(value)) try: value = f"{self.data[data_offset][6][0]}\n{self.data[data_offset][7][0]}" except IndexError: value = None self.tableWidget.setItem(i + 0, 10, QTableWidgetItem(value)) try: value = f"{self.data[data_offset][6][1]}\n{self.data[data_offset][7][1]}" except IndexError: value = None self.tableWidget.setItem(i + 0, 11, QTableWidgetItem(value)) try: value = f"{self.data[data_offset][6][2]}\n{self.data[data_offset][7][2]}" except IndexError: value = None self.tableWidget.setItem(i + 0, 12, QTableWidgetItem(value)) data_offset += 1 def generate_random_data(): random_data = [] for _ in range(10): row_data = [ f"Name_{random.randint(100, 999)}", random.randint(1000, 9999), random.randint(0, 100), [random.randint(1, 10) for _ in range(3)], [random.randint(1, 10) for _ in range(3)], [random.randint(1, 10) for _ in range(3)], [random.randint(1, 10) for _ in range(3)] ] random_data.append(row_data) return random_data def run_app(): app = QApplication(sys.argv) window = QWidget() window.setWindowTitle("Test Table") window.resize(800, 600) layout = QHBoxLayout() window.setLayout(layout) random_table_data = generate_random_data() table = Table(data=random_table_data) layout.addWidget(table) window.show() sys.exit(app.exec_()) if __name__ == '__main__': run_app()
关键修改说明
- 绘制顺序调整:先处理背景色和文本内容,最后绘制分隔线,确保分隔线不会被背景色覆盖
- 合并单元格适配:通过
rowSpan获取单元格的跨行数,计算出单元格的实际底部行号,只要底部行符合原需求的偶数行条件,就绘制分隔线 - 冗余代码清理:删除了重复的文本绘制调用
- 样式优化:保留了必要的样式状态重置,移除了可能影响正常交互的不必要设置
内容的提问来源于stack exchange,提问作者Kankan2000
相关产品推荐
相关产品推荐

