[Qt]QListView 重绘实例之二:列表项覆盖的问题处理
0 环境
- Windows 11
- Qt 5.15.2 MinGW x64
1 系列文章
简介:本系列文章,是以纯代码方式实现 Qt 控件的重构,尽量不使用 Qss 方式。
《[Qt]QListView 重绘实例之一:背景重绘》
《[Qt]QListView 重绘实例之二:列表项覆盖的问题处理》
《[Qt]QListView 重绘实例之三:滚动条覆盖的问题处理》
《[Qt]QListView 重绘实例之四:效果一讲解》
《[Qt]QListView 重绘实例之四:效果二讲解》
2 问题开始
继上文《之一》,绘制圆角矩形背景时,遗留了两个主要问题:
- 列表项覆盖破坏背景效果;
- 滚动条覆盖破坏背景效果;
其中,对于滚动条的问题,留作下一文《之三》讲解。参考《[Qt]QListView 重绘实例之三:滚动条的处理》。
本文先解决列表项覆盖的问题。
至少有两个思路解决列表项的覆盖问题:
- 使用委托。如子类化
QItemDelegate
,然后进行列表项的重绘; - 使用代理样式。如子类化
QProxyStyle
,然后进行列表项的重绘;
本文选择第二种思路,原因有二:
- 其一,承接上文《之一》已经使用了子类化
QProxyStyle
的方法,继续实现对列表项的重绘功能即可,没有必要再另外添加一个新委托子类实现; - 其二,样式
QStyle
/代理样式QProxyStyle
,本身即包含委托实现的功能。而且它们更多强大,可以对某类或全局样式进行控件;
具体的重绘过程分为两个部分:
- 对于视口最上行/最下行,需要处理上半部分/下半部分的圆角绘制;
- 对于视口的其它中间行,则进行默认绘制(或按期望样式绘制);
→→→ 解决方案直达 ←←←
3 重绘列表项背景
代理样式中,与列表项相关的主要有一个样式类和两个元素类型:
- 样式类:
QStyleOptionViewItem
- 元素类型:
QStyle::CE_ItemViewItem
-void drawControl() const
QStyle::PE_PanelItemViewItem
-void drawPrimitive() const
Qt 文档关于 QStyle
类有如下说明:
Styles in Item Views
…
The primitive element
PE_PanelItemViewItem
is responsible for painting the background of items, and is called fromQCommonStyle
’s implementation ofCE_ItemViewItem
.(译:
QCommonStyle
子类实现中的控制类型CE_ItemViewItem
会调用原始类型PE_PanelItemViewItem
,其中,原始类型PE_PanelItemViewItem
负责绘制列表项的背景。)…
即,在 QStyle::PE_PanelItemViewItem
中绘制列表项的背景,在 QStyle::CE_ItemViewItem
中绘制列表项的内容。
3.1 保存视口大小信息
Qt 明确说明
QListView
是垂直型的列表。
首先,需要判断视口中的最上/最下行位置。而判断位置则需要知道整个列表的高度。具体实现是在绘制 QFrame
时保存数据。
/* .h */
class PListViewStyle : public QListView
{
public:// ...
private:mutable QRect mRect; // Need mutable
}/* .cpp */
void PListViewStyle::drawControl(QStyle::ControlElement element,const QStyleOption *option,QPainter *painter,const QWidget *widget) const
{switch(element){case QStyle::CE_ShapedFrame:{const QStyleOptionFrame *opt = qstyleoption_cast<const QStyleOptionFrame *>(option);if(nullptr == opt) { return; }mRect = opt->rect;//...return;}default:break;}QProxyStyle::drawControl(element, option, painter, widget);
}
注意:变量 mRect
必须使用 mutable
修饰。
3.2 判断视口中的最上/最下行位置
- 判断视口中的最上行位置
if(0 == opt->rect.y())
如果没有设置内填充,最上行位置的 y 坐标应该等于 0,因此使用此条件进行判断。
- 判断视口中的最下行位置
bool PListViewStyle::isLastRow(const QRect rect, int &rowHeight) const
{/* 列表可显示行数 */int rowCount = mRect.height() / rect.height();/* 由 y 坐标与行高计算行索引 */int index = rect.y() / rect.height();if(rowCount == index){rowHeight = mRect.height() - index * rect.height();return true;}return false;
}
3.3 绘制列表项背景
/* 添加常量定义,需要添加到 cpp 文件开头位置 */
const int Radius = 15;void PListViewStyle::drawPrimitive(QStyle::PrimitiveElement element,const QStyleOption *option,QPainter *painter,const QWidget *widget) const
{switch(element){/* PE_PanelItemViewItem 主要负责绘制列表项的背景(选中背景/高亮背景)*/case QStyle::PE_PanelItemViewItem:{const QStyleOptionViewItem *opt = qstyleoption_cast<const QStyleOptionViewItem *>(option);if(nullptr == opt) { break; }QColor c(Qt::lightGray);if(QStyle::State_MouseOver & opt->state){c = QColor(0, 0, 255, 255 * 0.2);}else if(QStyle::State_Selected & opt->state){c = QColor(0, 0, 255, 255 * 0.5);}int x, y, w, h;opt->rect.getRect(&x, &y, &w, &h);QPainterPath path;int rowHeight = 0;/* 最上一行 */if(0 == y){/* 创建最上一行,带圆的角矩形路径 */path.moveTo(x, y + h);path.arcTo(QRect(x, y, 2 * Radius, 2 * Radius), 180, -90);path.lineTo(x + w, y);path.lineTo(x + w, y + h);path.closeSubpath();}/* 最下一行 */else if(isLastRow(opt->rect, rowHeight)){/* 创建最下一行,带圆角的矩形路径 */path.moveTo(x, y);path.lineTo(x + w, y);path.lineTo(x + w, y + rowHeight);path.arcTo(QRect(x, y + rowHeight - 2 * Radius, 2 * Radius, 2 * Radius), 270, -90);path.closeSubpath();}else{path.addRect(QRect(x, y, w, h));}painter->save();painter->setRenderHint(QPainter::Antialiasing);painter->setPen(Qt::NoPen);painter->setBrush(QBrush(c));painter->drawPath(path);painter->restore();return;}default:break;}QProxyStyle::drawPrimitive(element, option, painter, widget);
}
为了显示绘图的效果,这里特地将列表项的默认白色背景,改成了浅灰色。
其中的重点是:对视口最上行和最下行进行了圆角处理,通过 QPainterPaht
实现。
效果图如下:
从上图中可以看到,绘制列表项的圆角效果确实出来了,高亮效果也正确。说明,至少这样的处理方案没有问题。
但是,还是发现,背景上有一个白色直角矩形,仍然破坏了圆角矩形背景。
这也同样说明,这个白色直角矩形并不是由于列表项产生的影响。
3.4 视口 viewport()
好了,现在问题好像又回到 QListView
的里层了。
很明显,列表项应该是表层的,因为用户是可以直接看到的。
接下来,结合上一文《之一》的内容,来看一下以下这张图。
这里有一个层级关系:
- 最底层:
QFrame
。在《之一》中使用paintEvent()
进行重绘时,正是设置的这个,直接设置成了QFrame::NoFrame
; - 上一层:
viewpotr()
。在《之一》中也验证过这点,而且,viewport()
区域是不包含滚动条的。如上图中列表项底下的白色矩形; - 最上层:列表项。上文已有说明。
现在,基本上可以确定问题应该聚焦在 viewport()
上。
查看 Qt 帮助文档,与视口相关的有两个接口:
QWidget *QAbstractScrollArea::viewport() const;
void QAbstractScrollArea::setViewport(QWidget *widget);
可知,视口即是一个 QWidget
控件。
那么,再来验证一下,重新设置视口控件,然后设置其背景色为蓝色:
PListView::PListView(QWidget *parent) : QListView(parent)
{auto *widget = new QWidget;widget->setStyleSheet("background: blue");setViewport(widget);
}
效果图如下:
由此可知,确认了之前的推论。
补充内容:
解决方法只需要一条语句:
setViewport(new QWidget)
,而且,做过一些深入的尝试,但没有理解具体的原因。
- 既然视口是一个
QWidget
,那么,对QWidget
进行绘制,是不是应该也可以当作背景使用?实际上的情况是,子类化QWidget
后重写paintEvent()
方法,再设置为新的视口控件,重绘函数根本不会被调用。经实测,可以改变这个视口控件的方式,只有通过设置 Qss 才有效。也没明白是为什么。- 然后,设置
QWidget
对象为新的视口控件,该对象的调色板(或者说对象样式)是会影响列表项的默认样式的。例如QWidget
通过 Qss 设置背景颜色为蓝色,则列表项的默认背景色也会变为蓝色。当然,这也可能是 Qss 设置影响了子对象。此外,之所以只要设置一个默认的
QWidget
对象作为新视口即可,猜测原因是:默认QWidget
本身是一个透明的(或者是统一风格背景色的)控制,在QListView
中即表现为透明的一层,所以不会影响圆角背景的效果。虽然实际原因不知,但能解决问题。
4 解决方案
- 添加新的视口控件
PListView::PListView(QWidget *parent) : QListView(parent)
{setViewport(new QWidget);setFrameStyle(QFrame::NoFrame); // Must//...
}
- 绘制列表项背景
void PListViewStyle::drawPrimitive(QStyle::PrimitiveElement element,const QStyleOption *option,QPainter *painter,const QWidget *widget) const
{switch(element){/* PE_PanelItemViewItem 主要负责绘制列表项的背景(以及选中背景/高亮背景) */case QStyle::PE_PanelItemViewItem:{const QStyleOptionViewItem *opt = qstyleoption_cast<const QStyleOptionViewItem *>(option);if(nullptr == opt) { break; }QColor c(Qt::white); /* 默认背景色 */if(QStyle::State_MouseOver & opt->state){c = QColor(0, 0, 255, 255 * 0.2);}else if(QStyle::State_Selected & opt->state){c = QColor(0, 0, 255, 255 * 0.5);}int x, y, w, h;opt->rect.getRect(&x, &y, &w, &h);QPainterPath path;int rowHeight = 0;/* 最上一行 */if(0 == y){/* 创建最上一行,带圆角的矩形路径 */path.moveTo(x, y + h);path.arcTo(QRect(x, y, 2 * Radius - 5, 2 * Radius - 5), 180, -90);path.lineTo(x + w, y);path.lineTo(x + w, y + h);path.closeSubpath();}/* 最下一行 */else if(isLastRow(opt->rect, rowHeight)){/* 创建最下一行,带圆角的矩形路径 */path.moveTo(x, y);path.lineTo(x + w, y);path.lineTo(x + w, y + rowHeight);path.arcTo(QRect(x, y + rowHeight - 2 * Radius, 2 * Radius, 2 * Radius), 270, -90);path.closeSubpath();}else{path.addRect(QRect(x, y, w, h));}painter->save();painter->setRenderHint(QPainter::Antialiasing);painter->setPen(Qt::NoPen);painter->setBrush(QBrush(c));painter->drawPath(path);painter->restore();return;}default:break;}QProxyStyle::drawPrimitive(element, option, painter, widget);
}
最后来看一下效果图,达到了预期的效果目标。
相关文章:

[Qt]QListView 重绘实例之二:列表项覆盖的问题处理
0 环境 Windows 11Qt 5.15.2 MinGW x64 1 系列文章 简介:本系列文章,是以纯代码方式实现 Qt 控件的重构,尽量不使用 Qss 方式。 《[Qt]QListView 重绘实例之一:背景重绘》 《[Qt]QListView 重绘实例之二:列表项覆…...

Java 函数式编程思考 —— 授人以渔
引言 最近在使用函数式编程时,突然有了一点心得体会,简单说,用好了函数式编程,可以极大的实现方法调用的解耦,业务逻辑高度内聚,同时减少不必要的分支语句(if-else)。 一、函数式编…...

操作系统权限提升(二十八)之数据库提权-SQL Server 数据库安装
SQL Server 数据库安装 SQL Server介绍 SQL Server 是Microsoft 公司推出的关系型数据库管理系统。具有使用方便可伸缩性好与相关软件集成程度高等优点,可跨越从运行Microsoft Windows 98 的膝上型电脑到运行Microsoft Windows 2012 的大型多处理器的服务器等多种平台使用。…...
腾讯mini项目-【指标监控服务重构-会议记录】2023-08-18
2023-08-18 会议纪要 进度 venus 的 metrics 独立分支开发venus 的 trace 修复了一些bug 返回 error 主动调用 span.end() profile 的 watemill pub/sub 和 trace 上报还原原本功能profile 的 hyperscan 的继续调研 待办 调研如何关闭otel,设置开关配置性能benc…...
如何通过axios拦截器,给除了登录请求以外,axios的所有异步请求添加JWT令牌!
在 Vue 项目中配置除了登录请求以外的所有请求的令牌,通常涉及到在请求头中添加令牌(Token)信息。这可以通过使用 Axios 或其他 HTTP 请求库来实现。以下是一般的步骤: 1. **安装 Axios**: 如果你还没有安装 Axios&a…...

Spring学习笔记9 SpringIOC注解式开发
Spring学习笔记8 Bean的循环依赖问题_biubiubiu0706的博客-CSDN博客 注解的存在主要是为了简化XML的配置.Spring6倡导全注解式开发 回顾下 注解怎么定义,注解中的属性怎么定义 注解怎么使用 通过反射机制怎么读取注解 注解的自定义 注解的使用 通过反射机制怎么读取注解 I…...
【新日标习题集】第13課 までのまとめ (discarded)
2. 学校にコンピューターがごだいあります。 这个句子好像有点问题,辞典中没有查到有「ごだい」这个单词 学校里有5台电脑。 5. わたしは英語がよくわかります。 我很懂英语。...

Java基础常考知识点(基础、集合、异常、JVM)
作者:逍遥Sean 简介:一个主修Java的Web网站\游戏服务器后端开发者 主页:https://blog.csdn.net/Ureliable 觉得博主文章不错的话,可以三连支持一下~ 如有需要我的支持,请私信或评论留言! Java基础常考知识点…...

虚拟机桥接模式下没有无线网卡选项
我以为是雷电模拟器占用了网卡的缘故,但想起之前可能修改了无线网卡的某些内容,于是到网络属性里面查看。 如下所示,原来是之前我不小心把这个红箭头指向的项目取消勾选了。...

设计模式笔记
关于设计模式 1. 如何阅读本文 略 2. 面向对象程序设计简介 2.1 面向对象程序设计基础 面向对象程序设计 (Object-Oriented Programming,缩写为 OOP)是一种范式,其基本理念是将 数据块 及 与数据相关的行为 封装成为特殊的、…...

c==ubuntu+vscode debug redis7源码
新建.vscode文件夹,创建launch.json和tasks.json {"version": "0.2.0","configurations": [{"name": "C/C Launch","type": "cppdbg","request": "launch","prog…...
java字符串储存底层原理
字符串原理:原理1: 内存原理 (1)直接赋值给字符串,会把这个字符串放到常量池里,如果之后出现重复使用这个字符串的,就会直接从这个常量池中去引用,不会再去new一个字符串 (2)new出来的字符串不会重复使用,而是开辟一个新的空间存储原理2: 字符串中的""比较的是什么?…...

c++获取当前时间的字符串
代码 void getNowTimePrefix(std::string& prefix) {std::time_t nowTime;struct tm* p new tm;std::time(&nowTime);localtime_s(p, &nowTime);int year p->tm_year 1900;int month p->tm_mon 1;int day p->tm_mday;int hour p->tm_hour;int …...
【精品】通用Mapper 批量更新bug解决方案
问题描述 环境:mysql8.xmybatis3.5.13tk.mybatis4.2.3 在使用tk.mybatis做批量更新时,程序会报错,说是执行的SQL语法错误,经研究源代码发现tk.mybatis在实现批量更新时是通过多次执行update语句实现的。这本身就不符合MySQL批量…...

腾讯mini项目-【指标监控服务重构-会议记录】2023-07-06
7/6 会议记录 Profile4个步骤 解压kafka消息初始化性能事件,分析事件将数据写入kafkaRun 开始执行各stage handler 上报耗时到otel-collector。。。 // ConsumerDispatchHandler consumer // // param msg *sarama.ConsumerMessage // param consumer *databus.K…...
【React】函数式组件和类式组件的用法和逻辑
组件的使用 当应用是以多组件的方式实现,这个应用就是一个组件化的应用 注意: 组件名必须是首字母大写虚拟DOM元素只能有一个根元素虚拟DOM元素必须有结束标签 < /> 渲染类组件标签的基本流程React 内部会创建组件实例对象调用render()得到虚拟 …...
题目 1061: 二级C语言-计负均正
从键盘输入任意20个整型数,统计其中的负数个数并求所有正数的平均值。 保留两位小数 样例输入 1 2 3 4 5 6 7 8 9 10 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 样例输出 10 5.50 解题思路: 如题所示,输入20个正负数,---》求付数的个…...
数位和(C++)
系列文章目录 进阶的卡莎C++_睡觉觉觉得的博客-CSDN博客数1的个数_睡觉觉觉得的博客-CSDN博客双精度浮点数的输入输出_睡觉觉觉得的博客-CSDN博客足球联赛积分_睡觉觉觉得的博客-CSDN博客大减价(一级)_睡觉觉觉得的博客-CSDN博客小写字母的判断_睡觉觉觉得的博客-CSDN博客纸币(…...

[牛客复盘] 牛客周赛round13 20230924
[牛客复盘] 牛客周赛round13 20230924 总结矩阵转置置2. 思路分析3. 代码实现 小红买基金1. 题目描述2. 思路分析3. 代码实现 小红的密码修改1. 题目描述2. 思路分析3. 代码实现 小红的转账设置方式1. 题目描述2. 思路分析3. 代码实现 小红打boss1. 题目描述2. 思路分析3. 代码…...

mybatsi-MyBatis的逆向工程
mybatsi-MyBatis的逆向工程 一、前言二、创建逆向工程的步骤1.添加依赖和插件2.创建MyBatis的核心配置文件3.创建逆向工程的配置文件4.执行MBG插件的generate目标 一、前言 正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。 Hibernate是支…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...