[Qt]QListView 重绘实例之一:背景重绘

news/2023/12/9 6:57:15

0 环境

  1. Windows 11
  2. Qt 5.15.2 MinGW x64

1 系列文章

简介:本系列文章,是以纯代码方式实现 Qt 控件的重构,尽量不使用 Qss 方式。

《[Qt]QListView 重绘实例之一:背景重绘》

《[Qt]QListView 重绘实例之二:列表项覆盖的问题处理》

《[Qt]QListView 重绘实例之三:滚动条覆盖的问题处理》

《[Qt]QListView 重绘实例之四:效果一讲解》

《[Qt]QListView 重绘实例之五:效果二讲解》

2 开始

自定义 Qt 控件,无外乎两个主要目的:

  • 实现更漂亮的样式;
  • 实现更强大的/更合适的功能;

要实现以上两个主要目的,基本上都需要对 Qt 原生控件进行一定的重绘,以适应需求。

本节中,主要讲解 QListView 的背景绘制。

QListView

(之所以单独写一文,是因为自己动手实现时才发现:虽然最后的实现代码并不多,但要弄懂这些,还是要花费很多精力的。)

→ 解决方案直达 ←

3 paintEvent 重绘与问题

通常,重构一个新控件,基本上都是直接重写 void paintEvent(QPaintEvent *event) 方法。

void PListView::paintEvent(QPaintEvent *event)
{Q_UNUSED(event)QPainter painter(this);		// Errorpainter.setRenderHint(QPainter::Antialiasing);painter.setPen(QPen(Qt::red));painter.setBrush(QBrush(Qt::white));painter.drawRoundedRect(rect(), 5, 5);
}

3.1 问题 1 —— 绘图对象

通常,进行重绘时,新建 QPainter 对象都是以父控件为对象,意即在父控件中进行绘制。

但是,如果这样直接对 QListView 进行重绘,是会出错的:

QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setRenderHint: Painter must be active to set rendering hints
QPainter::setPen: Painter not active
QPainter::setBrush: Painter not active

(猜测)原因大致应该是:QListView 是由多个子控件组成,实际负责显示内容的只是其中的一个子控件,所以绘制对象需要具体指定到负责显示的对象。

QListView 继承树如下:

QListView-inherittree

而一个默认 QListView 对象包含的子控件如下:

(QWidget(0x1eb4600, name = "qt_scrollarea_viewport"),
QStyledItemDelegate(0x1eb1840),
QItemSelectionModel(0x1eb1ba0),
QWidget(0x1eb1010, name = "qt_scrollarea_hcontainer"),
QWidget(0x1eb1150, name = "qt_scrollarea_vcontainer"))

其中,实际显示内容的对象就是 “qt_scrollarea_viewport”,也就是 QListView 的视口(viewport)。这样做的主要原因,是要实现对 QListView 内容的滚动显示(显示部分内容)。

所以,对于 QListView 重绘,必须要针对视口 viewport()

void PListView::paintEvent(QPaintEvent *event)
{Q_UNUSED(event)QPainter painter(viewport());painter.setRenderHint(QPainter::Antialiasing);painter.setPen(QPen(Qt::red));painter.setBrush(QBrush(Qt::white));painter.drawRoundedRect(rect(), 5, 5);
}

效果如下图示:

QListView-paint1

3.2 问题 2 —— 外边线框

从上图看,这次倒是绘制出背景框。但首先注意到的问题是 QListView 默认外连线框,非常显眼。因此,首要目的是要去掉这个外连线框。

上文实现代码中的重绘过程,仅做了两件事:

  • 绘制了一个圆角矩形;
  • 阻止了 QListView 的其它默认绘制;

因此 ,基本可以肯定,外连线框并不是由 paintEvent() 绘制过程中引起的。看来原因得到 QListView 里层查找。

原因查找的具体过程略过不述,QListView 的外边线框其实就是其父类 QFrame 的边框(可以理解为一个底层,其它内容都绘制在这个底层之上,毕竟 QListView 是 UI 控件)。

只需要对 QListView 进行如下设置,改变一下 QFrame 样式即可去掉外边线框:

PListVeiw::PListView(QWidget *parent) : QListView(parent)
{setFrameStyle(QFrame::NoFrame);
}

效果如下:

QListView-noframe

强制隐藏/关闭垂直滚动条,效果如下:

QListView-noscrollbar

3.3 问题 3 —— 绘制区域

从上图可知,绘制的背景效果基本出来了。但是,也被垂直滚动条挡住了一部分。

再回来看一看绘图代码,其中有一行如下:

	painter.drawRoundedRect(rect(), 5, 5);

此时,指定的绘图区域为 rect(),即针对控件的整个显示区域。而我们指定的绘图对象是 QListView 的视口,原则上为了保证一致性,在什么上绘图,就应该在该对象的区域内进行绘制。所以,修改以上那行的代码:

	painter.drawRoundedRect(viewport()->rect(), 5, 5);

效果如下:

QListView-viewport

这种效果,也还可以。一些样式也确实是将滚动条置于控件之外的。

本文不针对此样式进行讲解,主要考虑滚动条内含在列表内的样式。

滚动条的问题,先按下不提,具体详见本系列后文说明。参考《[Qt]QListView 重绘实例之三:滚动条覆盖的问题处理》。

3.4 问题 4 —— 滚动时残留

先前为了重点显示 QListView 的背景绘制效果,所以没有绘制 QListView 的内容。

现在,加上内容的绘制代码:

void PListView::paintEvent(QPaintEvent *event)
{QPainter painter(viewport());painter.setRenderHint(QPainter::Antialiasing);painter.setPen(QPen(Qt::red));painter.setBrush(QBrush(Qt::white));painter.drawRoundedRect(rect(), 5, 5);	// 理解为视口占据整个控件区域QListView::paintEvent(event);
}

说明:绘制顺序是有要求的。应该先绘制背景,然后绘制列表内容(即前景)。

效果图如下:

QListView-paint2

但是,如果我们使用鼠标滚轮滚动或拖动滚动条,滚动 QListView 的内容,却出现了如下效果:

QListView-residual

这显然不是想要的效果。

具体原因未深究,暂时未知,猜测应该是底层代码的原因。因为,上文中的重绘代码其实很简单,并未做多余的动作。

但这种残留效果,显然不可接受。

因此,至少到目前,这种方式绘制 QListView 的背景是不可行的。

(考虑到添加委托会对列表项进行绘制,可能会影响到这个残留问题。尝试过添加委托,但这个残留问题依然存在。)

4 解决方案

从上文得知,采用 paintEvent()QListView 背景进行绘制的方案不可行。

另,考虑到后来的 Qt 版本对于 Qss 的性能问题,本系列也不考虑 Qss 方案。

于是,已知可行的方案只剩使用 QProxyStyle 代理样式定制了。

(之前也没有实际使用过代理样式,通过学习/练习/测试得出了合适的效果。)

关于 QProxyStyle 的具体内容,查找资料的过程中有发现,有不少介绍的好博文,请酌情参考(文末参考资料有链接),本文不另述。

4.1 定义背景绘制样式

/* .h */
class PListViewStyle : public QProxyStyle
{
public:PListViewStyle();void drawControl(QStyle::ControlElement element,const QStyleOption *option,QPainter *painter,const QWidget *widget = nullptr) const override;
};/* .cpp */
PListViewStyle::PListViewStyle()
{
}
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; }painter->save();painter->setRenderHint(QPainter::Antialiasing);painter->setPen(QPen(Qt::red));painter->setBrush(QBrush(Qt::white));painter->drawRoundedRect(opt->rect, 5, 5);painter->restore();return;}default:break;}QProxyStyle::drawControl(element, option, painter, widget);
}

4.2 使用代理样式

PListVeiw::PListView(QWidget *parent) : QListView(parent)
{// setFrameStyle(QFrame::NoFrame);	// Must delete or comment itsetStyle(new PListViewStyle);
}

注意:

  • 需要删除重写函数 void paintEvent(QPaintEvent *event),否则可能覆盖效果。
  • 需要删除对 QFrame 的样式设置,不能再设置为 QFrame::NoFrame。因为代理样式实际是对 QFrame 进行绘制的,如果设置了 QFrame::NoFrame,则绘制的样式根本就不会显示。

效果如下:

QListView-paintbg

至少看上去,基本达到了预期的效果。

但是,

但是,

但是,总有但是,哈哈。

将背景的圆角矩形圆角半径加大一下,再来看看效果图:

QListView-residual2

从上图可以看出有几个问题:

  • 列表项在背景的上层,即背景绘制先于列表项。而列表项也是有背景的(以及高亮/选中背景),可以理解为列表项就是一个个小矩形(默认没有圆角)。由上可以看出,视口的最上/最下一行,都有矩形直角覆盖了背景(圆角矩形),因此破坏了背景的效果;
  • 同理,滚动条也在背景上层,滚动条也是一个直角矩形,矩形直角覆盖了背景,因此也破坏了背景的效果;

其中:

  • 对于列表项产生的覆盖问题,可以通过使用委托,控制列表项背景(默认背景/高亮背景/选中背景)的绘制,使绘制视口最上/最下一行时,绘制合适的圆角效果。
  • 对于滚动条的问题,就复杂得多,具体详见本系列后文内容。参考《[Qt]QListView 重绘实例之三:滚动条覆盖的问题处理》。

5 参考资料

  1. 《C++ GUI Qt 4编程(第二版)》,第 19 章,19.2 子类化 QStyle
  2. QStyle类用法总结(一)
  3. 绘制自定义QSlider

http://www.ppmy.cn/news/1125369.html

相关文章

缓冲区溢出漏洞预防

什么是缓冲区溢出 组成所有应用程序的程序由缓冲区组成&#xff0c;缓冲区是在内存中分配的临时空间&#xff0c;用于保存数据&#xff0c;直到它们移动到程序的其他部分&#xff0c;缓冲区可以包含的数据字节数最初将在代码开发期间指定&#xff0c;由于没有任何类型的边界检…

基于C++实现的3D野外赛车驾驶游戏源码+项目文档+汇报PPT

项目介绍&#xff1a;本项目实现了一个户外场景下的赛车游戏&#xff0c;可以通过键盘控制赛车的移动&#xff0c;视角为第二人称视角。场景中有汽车&#xff0c;建筑&#xff0c;道路&#xff0c;天空等物体&#xff0c;拥有光照和阴影的效果。通过粒子系统模拟尾气效果&#…

9、JavaSE总结

9、JavaSE总结 9.1 Java语言 9.1.1 MarkDown语法 9.1.2 简单的Dos命令 9.1.3计算机语言发展 9.1.4 Java的诞生 1995年诞生&#xff1a;JavaSE、JavaME、JavaEE 2006年Hadoop大数据系列 9.1.5 JDK、JRE JDK&#xff1a;开发者工具包、配置环境变量&#xff08;配置Java…

React基础知识点

1、简述什么是React&#xff08;概念&#xff09;&#xff1f; React是Facebook开发的一款用于构建用户界面的JS库。React一般被采用作为MVC中的V层&#xff0c;它不依赖其他任何的库&#xff0c;因此在开发中&#xff0c;可以与任何其他的库集成使用&#xff0c;包括Jquery等…

ElementUI之首页导航+左侧菜单

Mock.js Mock.js是一个用于生成随机数据的模拟数据生成器&#xff0c;用于前端测试、调试和数据模拟。它可以生成各种类型的随机数据&#xff0c;包括但不限于数字、字符串、日期、图片、邮件、电话等等。 Mock.js可以帮助前端开发人员摆脱依赖后端接口提供的数据的束缚&…

坐标系上的交互+分治与交互:CF788D

https://codeforces.com/contest/788/problem/D 坐标系上的交互有一种常见套路&#xff0c;就是抓住一些关键的线 x轴y轴yx&#xff08;就是此题&#xff09; 然后考虑接下来怎么做。 交互题常见有二分的套路&#xff0c;此题我们可以考虑推广到分治。 不断判断mid&#xf…

Visual Studio 中使用 CMake

官网英文链接 https://learn.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio?viewmsvc-170&viewFallbackFromvs-2019 官网中文链接 https://learn.microsoft.com/zh-cn/cpp/build/cmake-projects-in-visual-studio?viewmsvc-170&viewFallbackFromv…

Spring学习笔记12 面向切面编程AOP

Spring学习笔记11 GoF代理模式_biubiubiu0706的博客-CSDN博客 AOP(Aspect Oriented Programming):面向切面编程,面向方面编程. AOP是对OOP的补充延申.底层使用动态代理实现. Spring的AOP使用的动态代理是:JDK动态代理_CGLIB动态代理技术.Spring在这两种动态代理中灵活切换.如…

ElasticSearch - DSL查询文档语法,以及深度分页问题、解决方案

目录 一、DSL 查询文档语法 前言 1.1、DSL Query 基本语法 1.2、全文检索查询 1.2.1、match 查询 1.2.2、multi_match 1.3、精确查询 1.3.1、term 查询 1.3.2、range 查询 1.4、地理查询 1.4.1、geo_bounding_box 1.4.2、geo_distance 1.5、复合查询 1.5.1、相关…

【面试题精讲】JavaSe和JavaEE的区别

“ 有的时候博客内容会有变动&#xff0c;首发博客是最新的&#xff0c;其他博客地址可能会未同步,认准https://blog.zysicyj.top ” 首发博客地址[1] 文章更新计划[2] 系列文章地址[3] 1. 什么是 JavaSE 和 JavaEE? JavaSE&#xff08;Java Platform, Standard Edition&#…

全国职业技能大赛云计算--高职组赛题卷②(私有云)

全国职业技能大赛云计算--高职组赛题卷②&#xff08;私有云&#xff09; 第一场次题目&#xff1a;OpenStack平台部署与运维任务1 基础运维任务&#xff08;5分&#xff09;任务2 OpenStack搭建任务&#xff08;15分&#xff09;任务3 OpenStack云平台运维&#xff08;15分&am…

火山引擎边缘云:数智化项目管理助力下的业务增长引擎

近日&#xff0c;“QCon全球软件开发大会2023北京站”、“第十二届中国PMO大会”、“2023第二届中国PMO&PM大会”相继召开&#xff0c;火山引擎边缘云项目管理团队受邀参加&#xff0c;并就项目管理相关主题开展分享。 会上&#xff0c;火山引擎边缘云项目管理负责人申建表…

GitStats - 统计Git所有提交记录工具

如果你是研发效能组的一员或者在从事 CI/CD 或 DevOps&#xff0c;除了提供基础设施&#xff0c;指标和数据是也是一个很重要的一环&#xff0c;比如需要分析下某个 Git 仓库代码提交情况&#xff1a; 该仓库的代码谁提交的代码最多 该仓库的活跃度是什么样子的 各个时段的提交…

【Oracle】Oracle系列之二--Oracle数据字典

文章目录 往期回顾前言1. 什么是Oracle数据字典2. 数据字典的内容&#xff08;1&#xff09;X$表&#xff08;2&#xff09;数据字典表、数据字典视图&#xff08;3&#xff09;动态性能视图 3. 数据字典应用示例&#xff08;1&#xff09;查询表的信息&#xff08;2&#xff0…

Qt Chats(一)绘制折线图

1、一个简单的QChart绘图程序 Qt Charts基于Qt的Graphics View架构&#xff0c;其核心组件是QChartView 和 QChart QChartView是显示图标的视图&#xff0c;基类为QGraphicsViewQChart的基类是QGraphicsltem QChart类继承关系 QPolarChart 用于绘制 极坐标图的图表类 1.项目…

使用JavaScript将数字格式化成千分位的n种方法

1,使用数组方法: 1) 数字转字符串,字符串按照小数点.分割 2) 整数部分拆分成字符串数组,并倒叙 3) 遍历, 按照每三位添加逗号,号 4) 拼接整数部分小数部分 function format_width_array(number) { // 将数字转换为千分位字符串const arr String(number).split(.);// 整数…

SpringCloudStream+Rocket事务消息配置

本文用到的版本 spring-cloud-stream 3.2.6 rocketmq-client 4.9.4 spring-cloud-starter-stream-rocketmq 2021.0.5.0 一、依赖导入 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-stream-rocketmq</art…

数字IC基础协议篇(1)——I2C协议

数字IC基础协议篇&#xff08;1&#xff09;——I2C协议 写在前面的话I2C协议应用框图I2C数据格式协议注意点 I2C读写EEPROM例程&#xff08;基于iverilog和gtkwave&#xff09;软件环境要求 项目框图总结 写在前面的话 协议介绍&#xff1a; I2C&#xff08;Inter-Integrated…

C++ 友元、重载、继承、多态

友元 关键字&#xff1a;friend 友元的三种实现 全局函数做友元类做友元成员函数做友元 全局函数做友元 //建筑物类 class Building {//goodGay全局函数是Building好朋友&#xff0c;可以访问Building中私有成员friend void goodGay(Building& building); public:Build…

小程序搜索词排名优化的诀窍

随着小程序的普及,如何提高小程序在搜索结果中的排名也变得重要。优化小程序搜索词排名可以扩大用户流量,提高曝光度。那么,小程序搜索词排名优化需要注意哪些方面呢?下面我就结合自己的经验,和大家分享些实用技巧。【名即薇】 首先,选择合适的搜索词非常关键。目标是找到既符…
最新文章