PySide2自定义QTableView插入数据后隐藏列复现问题求助
问题解决:插入数据后隐藏列重新显示的处理
问题背景
使用QAbstractTableModel与QStyledItemDelegate实现QTableView,模型数据包含name、age、sex、phone number四个维度,希望表格以3列展示:将name和age合并到同一列,其余两列单独显示。已通过setColumnHidden(1, True)隐藏age对应的列1,委托的paint方法也实现了列0同时显示name和age的效果,但插入新数据后,隐藏的列1会重新显示。
解决方案
方案1:快速修复(视图层面)
问题根源是模型更新数据后,视图会重新同步列状态,之前的隐藏设置被覆盖。可以通过监听模型的rowsInserted信号,每次插入新行后重新隐藏列1。
修改MyTableView类,添加信号监听逻辑:
class MyTableView(QTableView): def __init__(self): super().__init__() self.verticalHeader().setDefaultSectionSize(40) self.horizontalHeader().setVisible(False) self.verticalHeader().setVisible(False) self.setFixedWidth(300) def setModel(self, model): super().setModel(model) # 初始隐藏列1 self.setColumnHidden(1, True) # 监听模型行插入信号,重新隐藏列1 model.rowsInserted.connect(lambda: self.setColumnHidden(1, True))
方案2:更优解(模型层面,符合MVC设计)
让模型直接提供视图需要的3列数据结构,避免视图层面的隐藏操作,更符合模型-视图-委托的职责划分:
- 模型的
columnCount返回3 - 在
data方法中映射原数据的字段到视图的3列,同时通过自定义角色给委托提供age数据
修改后的MyTableModel类:
class MyTableModel(QAbstractTableModel): def __init__(self, mydata): super().__init__() self._tabledata = mydata def rowCount(self, parent: QModelIndex = ...) -> int: return len(self._tabledata) def columnCount(self, parent: QModelIndex = ...) -> int: # 直接返回视图需要的3列 return 3 def data(self, index: QModelIndex, role: int = ...): if not index.isValid(): return None if index.row() >= self.rowCount(): return None row_data = self._tabledata[index.row()] if role == Qt.DisplayRole or role == Qt.EditRole: if index.column() == 0: return row_data[0] # 返回name elif index.column() == 1: return row_data[2] # 对应sex elif index.column() == 2: return row_data[3] # 对应phone number elif role == Qt.UserRole + 1: # 自定义角色返回age,供委托获取 if index.column() == 0: return row_data[1] return None def insertNewRow(self): self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount()) self._tabledata.append(['test','20','female', 234]) self.endInsertRows()
同时修改委托中获取age的逻辑,从自定义角色获取:
# 替换原代码中获取age的部分 elidedstr = fm.elidedText(index.model().data(index, Qt.UserRole + 1), Qt.ElideRight, colWid -10)
这样视图不需要隐藏任何列,模型直接提供3列数据,彻底避免列状态重置的问题。
完整修改后的代码(方案1快速修复版)
import sys from PySide2.QtWidgets import QApplication,QTableView, QStyledItemDelegate,QStyleOptionViewItem,QStyle, QPushButton,QWidget,QVBoxLayout from PySide2.QtCore import Qt, QAbstractTableModel, QModelIndex from PySide2.QtGui import QColor, QTextDocument, QPainter,QFont,QAbstractTextDocumentLayout, QPalette,QTextCursor,QTextCharFormat, QTextBlockFormat,QFontMetrics class MyTableModel(QAbstractTableModel): def __init__(self, mydata): super().__init__() self._tabledata = mydata def rowCount(self, parent: QModelIndex = ...) -> int: return len(self._tabledata) def columnCount(self, parent: QModelIndex = ...) -> int: return len(self._tabledata[0] if self._tabledata else []) def data(self, index: QModelIndex, role: int = ...): if not index.isValid(): return None if index.row() >= self.rowCount(): return None if role == Qt.DisplayRole or role == Qt.EditRole: return self._tabledata[index.row()][index.column()] else: return None def insertNewRow(self): self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount()) self._tabledata.append(['test','20','female', 234]) self.endInsertRows() class TableDelegate(QStyledItemDelegate): def paint(self, painter, option, index): if index.column() in [0, 3, 2]: painter.save() painter.setRenderHint(QPainter.Antialiasing, True) widget = option.widget style = widget.style() style.drawControl(style.CE_ItemViewItem, option, painter, widget) painter.restore() if index.column() == 0: self.doc = QTextDocument(self) colWid = option.rect.width() painter.save() painter.setPen(QColor(244,243,243,255)) painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight()) options = QStyleOptionViewItem(option) self.initStyleOption(options, index) self.doc.setPlainText('') cursor = QTextCursor(self.doc) fmt = QTextCharFormat() fmt.setFontFamily('Arial') fmt.setFontWeight(QFont.Bold) fmt.setFontPointSize(10) serifFont = QFont("Arial", 10, QFont.Bold) fm = QFontMetrics(serifFont) cursor.setCharFormat(fmt) cursor.beginEditBlock() cursor.insertText(fm.elidedText(options.text, Qt.ElideRight, colWid -10)) cursor.endEditBlock() tbm = QTextBlockFormat() tbm.setTopMargin(10) cursor.insertBlock(tbm) cursor.beginEditBlock() fmt.setBackground(Qt.transparent) fmt.setFontWeight(QFont.Normal) fmt.setFontPointSize(8) fmt.setForeground(QColor(123,123,123,255)) cursor.setCharFormat(fmt) serifFont = QFont("Arial", 8, QFont.Normal) fm = QFontMetrics(serifFont) elidedstr = fm.elidedText(index.model().data(index.siblingAtColumn(1), Qt.DisplayRole), Qt.ElideRight, colWid -10) cursor.insertText(elidedstr) cursor.endEditBlock() ctx = QAbstractTextDocumentLayout.PaintContext() if option.state & QStyle.State_Selected: ctx.palette.setColor(QPalette.Text, option.palette.color( QPalette.Active, QPalette.HighlightedText)) else: ctx.palette.setColor(QPalette.Text, option.palette.color( QPalette.Active, QPalette.Text)) textRect = option.widget.style().subElementRect(QStyle.SE_ItemViewItemText, options) textRect.adjust(5, 0, 0, 0) painter.translate(textRect.topLeft()) self.doc.documentLayout().draw(painter, ctx) painter.restore() else: QStyledItemDelegate.paint(self, painter, option, index) class MyTableView(QTableView): def __init__(self): super().__init__() self.verticalHeader().setDefaultSectionSize(40) self.horizontalHeader().setVisible(False) self.verticalHeader().setVisible(False) self.setFixedWidth(300) def setModel(self, model): super().setModel(model) # 初始隐藏列1 self.setColumnHidden(1, True) # 监听行插入信号,重新隐藏列1 model.rowsInserted.connect(lambda: self.setColumnHidden(1, True)) if __name__ == '__main__': app = QApplication(sys.argv) myWindow = QWidget() lyt = QVBoxLayout() myWindow.setLayout(lyt) t = MyTableView() t.setItemDelegate(TableDelegate()) m = MyTableModel([['mike', '12', 'male', 124], ['jack', '15', 'male', 123]]) t.setModel(m) btn = QPushButton('new') btn.clicked.connect(m.insertNewRow) lyt.addWidget(btn) lyt.addWidget(t) myWindow.show() sys.exit(app.exec_())
内容的提问来源于stack exchange,提问作者Arthur
相关产品推荐
相关产品推荐

