如何将QTableView修改为类似列表的显示样式?
实现QTableView的分组列表样式
你要的是分组标题+条目的列表样式(比如按首字母分组,每组顶部带背景标题行),QSortFilterProxyModel仅负责排序过滤,要实现这种样式需结合自定义代理/模型,或直接用更适配层级结构的QTreeView,以下是Python实现方案:
方案1:用QTreeView替代(最简方案)
如果数据本身有层级分组,QTreeView + QStandardItemModel天然支持分组结构,无需额外绘制:
from PyQt5.QtWidgets import QApplication, QTreeView, QStandardItemModel, QStandardItem from PyQt5.QtCore import Qt import sys app = QApplication(sys.argv) model = QStandardItemModel() # 添加A分组及条目 group_a = QStandardItem("A") group_a.setFlags(group_a.flags() ^ Qt.ItemIsSelectable) # 分组标题设为不可选 group_a.appendRow(QStandardItem("Alice")) group_a.appendRow(QStandardItem("Andy")) model.appendRow(group_a) # 添加B分组及条目 group_b = QStandardItem("B") group_b.setFlags(group_b.flags() ^ Qt.ItemIsSelectable) group_b.appendRow(QStandardItem("Bob")) model.appendRow(group_b) tree = QTreeView() tree.setModel(model) tree.expandAll() # 默认展开所有分组 tree.show() sys.exit(app.exec_())
方案2:QTableView + 自定义委托+ProxyModel(模拟分组)
若必须用QTableView,需通过自定义QStyledItemDelegate绘制分组标题,配合QSortFilterProxyModel完成排序:
步骤1:准备带分组标记的模型
给原始模型添加自定义角色,标记该行是否为分组标题:
from PyQt5.QtCore import QAbstractTableModel, Qt, QModelIndex from PyQt5.QtWidgets import QStyledItemDelegate, QTableView, QApplication from PyQt5.QtGui import QPainter, QColor import sys class CustomTableModel(QAbstractTableModel): def __init__(self, data): super().__init__() self._data = data # 数据格式:[(是否为分组标题, 内容), ...] self.header = ["联系人"] def rowCount(self, parent=QModelIndex()): return len(self._data) def columnCount(self, parent=QModelIndex()): return len(self.header) def data(self, index, role=Qt.DisplayRole): if not index.isValid(): return None row = index.row() if role == Qt.DisplayRole: return self._data[row][1] # 自定义角色:标记是否为分组行 elif role == Qt.UserRole + 1: return self._data[row][0] return None def headerData(self, section, orientation, role=Qt.DisplayRole): if orientation == Qt.Horizontal and role == Qt.DisplayRole: return self.header[section] return None
步骤2:自定义委托绘制分组样式
重写paint方法,区分分组行和普通行的绘制逻辑:
class GroupDelegate(QStyledItemDelegate): def paint(self, painter, option, index): is_group = index.data(Qt.UserRole + 1) if is_group: # 绘制分组标题背景 painter.fillRect(option.rect, QColor("#f0f0f0")) # 设置加粗字体 option.font.setBold(True) painter.setFont(option.font) painter.drawText(option.rect, Qt.AlignLeft | Qt.AlignVCenter, index.data()) else: # 正常绘制普通条目 super().paint(painter, option, index) def sizeHint(self, option, index): hint = super().sizeHint(option, index) if index.data(Qt.UserRole + 1): hint.setHeight(hint.height() + 8) # 分组行增高 return hint
步骤3:结合QSortFilterProxyModel排序
if __name__ == "__main__": app = QApplication(sys.argv) # 模拟数据 raw_data = [ (True, "A"), (False, "Alice"), (False, "Andy"), (True, "B"), (False, "Bob"), (False, "Ben"), (True, "C"), (False, "Cindy"), ] model = CustomTableModel(raw_data) # 用ProxyModel实现排序 from PyQt5.QtCore import QSortFilterProxyModel proxy = QSortFilterProxyModel() proxy.setSourceModel(model) proxy.sort(0) # 按第一列排序 table = QTableView() table.setModel(proxy) table.setItemDelegate(GroupDelegate()) table.setShowGrid(False) # 隐藏网格线 table.verticalHeader().hide() # 隐藏行号 table.resize(300, 400) table.show() sys.exit(app.exec_())
关键说明
- QSortFilterProxyModel仅负责排序/过滤,无法直接实现分组样式,必须配合自定义委托绘制分组行
- 若数据动态生成,可重写ProxyModel的
lessThan方法,自定义排序逻辑确保分组与对应条目关联 - 优先推荐QTreeView,原生支持层级结构,无需额外绘制逻辑
内容的提问来源于stack exchange,提问作者IKNCODE
相关产品推荐
相关产品推荐

