当前位置: 首页 > news >正文

基于PANDAS的QAbstractTableModel实现高级TableView详细解析(九、在TableView实现多重表头)

一、背景

我们在展示一些数据时需要使表头不能编辑,实现这个效果有两种方式1.在flag中将标题行设定为不可编辑,2.直接使用表头,但默认情况下,TableView会将多重表头压扁显示,形式为:

(LEVEL1,LEVEL2,LEVEL3)

我们要做的就是重写表头信息

二、逻辑实现

1.表头获取

在你的QAbstractTableModel中添加get_header_structure,我在模型中存储数据的变量是_full_data替换成你定义的即可

def get_header_structure(self, orientation): "直接获取总数据的表头信息,默认方式的布局刷新机制可能获取到错误的表头" if orientation == Qt.Horizontal: return self._full_data.columns.to_list() return self._full_data.index.to_list()

2.重写QHeaderView高度计算逻辑

我们需要做一个可以自动调整层级的部件,因此这个是必须的

初始化定义:

self._header_level = 1

self._default_style = {} #可以自定义一些数据,做UI美化

然后定义_max_level(层级获取),_update_height(高度设定)

def _max_level(self): "刷新最大层级" m = self.model() if m is None: self._header_level = 1 return if not hasattr(m, "get_header_structure"): self._header_level = 1 return headers = m.get_header_structure(self.orientation()) if not headers: self._header_level = 1 return levels = 1 for h in headers: if isinstance(h, (tuple, list)): levels = max(levels, len(h)) self._header_level = levels def _update_height(self): "设置最小高度" h = self._total_height() if self.orientation() == Qt.Horizontal: self.setMinimumHeight(h) else: self.setMinimumHeight(h) def _total_height(self): heights = self._base_heights() total = 0 for i in range(self._header_level): total += heights[ min(i, len(heights)-1) ] return total def _base_heights(self): '基础高度' return [30]

3.渲染逻辑(2+3的完整代码)

from PySide6 import QtCore, QtGui, QtWidgets from PySide6.QtCore import Qt, QRect from app.shared.views.models.ShowDataModel import ShowDataModel class MultiHeaderView(QtWidgets.QHeaderView): def __init__(self, orientation=Qt.Horizontal, parent=None): super().__init__(orientation, parent) self.setDefaultAlignment(Qt.AlignCenter) self.setSectionsClickable(True) self.setStretchLastSection(False) self._header_level = 1 def _max_level(self): model = self.model() if model is None: self._header_level = 1 return if not hasattr(model, "get_header_structure"): self._header_level = 1 return headers = model.get_header_structure(self.orientation()) if not headers: self._header_level = 1 return level = 1 for h in headers: if isinstance(h, (tuple, list)): level = max(level, len(h)) self._header_level = level def _base_heights(self): return [30] def _total_height(self): heights = self._base_heights() total = 0 for i in range(self._header_level): total += heights[min(i, len(heights) - 1)] return total def _update_height(self): h = self._total_height() self.setMinimumHeight(h) def sizeHint(self): s = super().sizeHint() s.setHeight(self._total_height()) return s def sectionSizeFromContents(self, logicalIndex): s = super().sectionSizeFromContents(logicalIndex) s.setHeight(self._total_height()) return s def _header_texts(self, section): model: ShowDataModel = self.model() if model is None: return [""] if not hasattr(model, "get_header_structure"): return [""] headers = model.get_header_structure(self.orientation()) if not headers: return [""] if section >= len(headers): return [""] data = headers[section] if isinstance(data, (tuple, list)): return [str(i) for i in data] return [str(data)] def _span_range(self, section, level): model = self.model() if model is None: return section, section count = model.columnCount() texts = self._header_texts(section) current = texts[level] if level < len(texts) else "" start = section while start > 0: left = self._header_texts(start - 1) txt = left[level] if level < len(left) else "" if txt != current: break if left[:level] != texts[:level]: break start -= 1 end = section while end < count - 1: right = self._header_texts(end + 1) txt = right[level] if level < len(right) else "" if txt != current: break if right[:level] != texts[:level]: break end += 1 return start, end def paintEvent(self, event): painter = QtGui.QPainter(self.viewport()) painter.setRenderHint(QtGui.QPainter.TextAntialiasing) painter.fillRect( self.viewport().rect(), QtGui.QColor("#FFFFFF") ) model = self.model() if model is None: return heights = self._base_heights() count = ( model.columnCount() if self.orientation() == Qt.Horizontal else model.rowCount() ) for section in range(count): if self.isSectionHidden(section): continue texts = self._header_texts(section) top = 0 for level in range(self._header_level): h = heights[min(level, len(heights) - 1)] start, end = self._span_range(section, level) # 只有合并起点负责绘制 if section != start: top += h continue x = self.sectionViewportPosition(start) width = 0 for c in range(start, end + 1): if self.isSectionHidden(c): continue width += self.sectionSize(c) rect = QRect( x, top, width, h ) text = texts[level] if level < len(texts) else "" self._draw_cell( painter, rect, text ) top += h def _draw_cell( self, painter, rect, text ): painter.save() # 背景 painter.fillRect( rect, QtGui.QColor("#FFFFFF") ) # 边框 pen = QtGui.QPen( QtGui.QColor("#C9C9C9") ) painter.setPen(pen) painter.drawRect( rect.adjusted(0, 0, -1, -1) ) # 字体 font = painter.font() font.setPointSize(10) font.setBold(True) painter.setFont(font) # 文本颜色 painter.setPen( QtGui.QColor("#000000") ) painter.drawText( rect, Qt.AlignCenter, text ) painter.restore() def _sync_header(self): self._max_level() self._update_height() self.reset() self.updateGeometry() self.viewport().update()

4.渲染加速

若你才用了懒加载逻辑,那缓存就有必要了

初始化缓存:self._span_cache = {}
添加列宽变化计算:self.sectionResized.connect(self._on_section_resized)

替换

def _span_range(self, section, level):
return self._span_cache.get(level, {}).get(
section,
(section, section)
)

新增缓存逻辑

def _rebuild_span_cache(self): self._span_cache.clear() model = self.model() if model is None: return count = ( model.columnCount() if self.orientation() == Qt.Horizontal else model.rowCount() ) for level in range(self._header_level): cache = {} start = 0 while start < count: texts = self._header_texts(start) current = ( texts[level] if level < len(texts) else "" ) end = start while end + 1 < count: right = self._header_texts(end + 1) txt = ( right[level] if level < len(right) else "" ) if txt != current: break # 父节点必须一致 if right[:level] != texts[:level]: break end += 1 # 保存整个区间 for i in range(start, end + 1): cache[i] = (start, end) start = end + 1 self._span_cache[level] = cache

新增:

def _on_section_resized(self, *args): self._rebuild_span_cache() self.viewport().update()

替换

def _sync_header(self): self._max_level() self._update_height() # 新增 self._rebuild_span_cache() self.reset() self.updateGeometry() self.viewport().update()

三、总结

以上多重表头加载就完成了,还可以添加样式、背景色之类的设定,这些我放到资源里面了可以自行下载

http://www.jsqmd.com/news/1093777/

相关文章:

  • 智能门锁室内2寸-5寸屏幕驱动芯片模组方案
  • Paxos算法:如何解决分布式系统中的共识问题
  • 民意调查真伪辨别!四招看懂靠谱民调标准
  • 慢病时代中医养护新思路:糖尿病的系统化调理与健康管理
  • 快消品新零售商城小程序开发
  • 专科大数据专业怎么专升本?升学路径+志愿规划+能力提升全攻略
  • Claude 3.5原生结构化能力:提示编排层为何正在归零
  • gt-checksum v4.0.0 新功能解读系列文章(4):SSL 加密连接——数据校验传输安全再升级
  • 全球AI可见性基础建设:从“信息发布”到“AI记忆持续性”的重构
  • OpenMontage全链路AI视频制作系统:本地部署与全流程实践指南
  • 低功耗4G采集器:低耗稳定运行,常年无人值守无忧
  • Leader 不参与读请求?etcd 线性读实现揭秘
  • AiPy 使用心得:一个能替你干活的 AI 工具箱
  • 基于MCP协议构建AI编程助手持久化代码记忆的实战指南
  • [js] “===“ 及 typeof
  • 开源AI应用平台gstack部署与实战:从零搭建可视化工作流
  • 我从顺丰转行学AI产品经理·扒完招聘数据没敢盲目乐观
  • 深度解析|VLA、强化学习、世界模型,到底是什么关系?
  • CasaOS:十分钟搭建个人家庭云,旧电脑变全能服务器
  • PHP集成PGP加密实战:从GnuPG环境配置到文件签名验签
  • 5分钟快速上手OWASP Dependency-Check:命令行实战与CI/CD集成指南
  • D1117 低压差线性稳压电路
  • OpenMontage:从文本到视频的AI自动化生成框架实践指南
  • 【数据仓库】数仓常见问题治理
  • Agent-Reach:简化大模型API调用,构建稳定自动化流程
  • AI Agent沙箱是什么?跟Docker容器和虚拟机有什么区别
  • Kubernetes 工作负载与网络核心:从 Controller 到 Ingress 生产级实践
  • LoRA训练实战61:Krea2人物角色LoRA保姆级训练教程,几分钟捏出专属IP!
  • 一款H5播放器,搞定所有流媒体协议?EasyPlayer.js流媒体播放器到底有多强
  • 数据脱敏方法有哪些?一文盘点数据脱敏常用方法