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

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]QListView 重绘实例之三:滚动条的处理》。

本文先解决列表项覆盖的问题。

QListView-residual2

至少有两个思路解决列表项的覆盖问题:

  • 使用委托。如子类化 QItemDelegate,然后进行列表项的重绘;
  • 使用代理样式。如子类化 QProxyStyle,然后进行列表项的重绘;

本文选择第二种思路,原因有二:

  • 其一,承接上文《之一》已经使用了子类化 QProxyStyle 的方法,继续实现对列表项的重绘功能即可,没有必要再另外添加一个新委托子类实现;
  • 其二,样式 QStyle /代理样式 QProxyStyle,本身即包含委托实现的功能。而且它们更多强大,可以对某类或全局样式进行控件;

具体的重绘过程分为两个部分:

  1. 对于视口最上行/最下行,需要处理上半部分/下半部分的圆角绘制;
  2. 对于视口的其它中间行,则进行默认绘制(或按期望样式绘制);

→→→ 解决方案直达 ←←←

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 from QCommonStyle’s implementation of CE_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 实现。

效果图如下:

QListView-itembg

从上图中可以看到,绘制列表项的圆角效果确实出来了,高亮效果也正确。说明,至少这样的处理方案没有问题。

但是,还是发现,背景上有一个白色直角矩形,仍然破坏了圆角矩形背景。

这也同样说明,这个白色直角矩形并不是由于列表项产生的影响。

3.4 视口 viewport()

好了,现在问题好像又回到 QListView 的里层了。

很明显,列表项应该是表层的,因为用户是可以直接看到的。

接下来,结合上一文《之一》的内容,来看一下以下这张图。

QListView-layers

这里有一个层级关系:

  • 最底层: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);
}

效果图如下:

QListView-viewport

由此可知,确认了之前的推论。

补充内容:

解决方法只需要一条语句: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);
}

最后来看一下效果图,达到了预期的效果目标。

QListView-items

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/145778.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

elementui引入弹出框报错:this.$alert is not defined 解决方案

1.按需引入文件element.js 注意&#xff1a;引入Message&#xff0c;MessageBox两个组件就行&#xff0c;alert包括在MessageBox里面了。 之前我引入了Alert组件&#xff0c;发现不行 2.在vue的prototype里注册伪名字 3.组件里直接调用就行了 4.实现效果 我发现elementui调用…

【C++进阶】:C++11

C11 一.统一列表的初始化1.{}初始化2.initializer_list 二.声明1.decltype2.nullptr 三.右值引用和移动语义1.左值和右值1.转义语句2.完美转发 四.可变参数模板1.基本概念2.STL里emplace类接口 五.lambda表达式六.新的类功能 一.统一列表的初始化 1.{}初始化 在C98中&#xf…

图像处理: 马赛克艺术

马赛克 第一章 马赛克的历史渊源 1.1 马赛克 艺术中的一种表面装饰&#xff0c;由紧密排列的、通常颜色各异的小块材料&#xff08;如石头、矿物、玻璃、瓷砖或贝壳&#xff09;组成。与镶嵌不同的是&#xff0c;镶嵌是将要应用的部件放置在已挖空以容纳设计的表面中&#xff0…

【教学类-35-03】学号+姓名+班级(小3班)学号字帖(A4竖版2份)

图片展示: 背景需求: 本周排到小3班&#xff0c;还没有来得及设计小班主题活动书的内容&#xff0c;于是就把小2班的学号字帖微调一下&#xff0c;做一份竖版2份的学号字帖。 让幼儿熟悉自己的学号&#xff0c;让我也熟悉幼儿的名字和学号 材料准备&#xff1a; 描字写&#…

关于RabbitMQ你了解多少?

关于RabbitMQ你了解多少&#xff1f; 文章目录 关于RabbitMQ你了解多少&#xff1f;基础篇同步和异步MQ技术选型介绍和安装数据隔离SpringAMQP快速入门Work queues交换机Fanout交换机Direct交换机Topic交换机 声明队列和交换机MQ消息转换器 高级篇消息可靠性问题发送者的可靠性…

妙不可言的Python之旅----(一)

初识Python python的起源 1989年&#xff0c;为了打发圣诞节假期&#xff0c;Gudio van Rossum吉多 范罗苏姆&#xff08;龟叔&#xff09;决心开发一个新的解释程序&#xff08;Python雏形&#xff09; 1991年&#xff0c;第一个Python解释器诞生 Python这个名字&#xff…

Golang中的包和模块设计

Go&#xff0c;也被称为Golang&#xff0c;是一种静态类型、编译型语言&#xff0c;因其简洁性和对并发编程的强大支持而受到开发者们的喜爱。Go编程的一个关键方面是其包和模块系统&#xff0c;它允许创建可重用、可维护和高效的代码。本博客文章将深入探讨在Go中设计包和模块…

Servlet操作与用法(保姆式教学)

Servlet介绍 什么是servlet Servlet&#xff08;Servlet Applet的缩写&#xff0c;全称 Java Servlet&#xff09;&#xff1a;适用于Java编写的服务器程序&#xff0c;其主要功能是在于交互式的浏览和修改数据&#xff0c;生成动态Web内容。狭义的Servlet是指Java语言实现的…

MySQL学习笔记27

MySQL主从复制的核心思路&#xff1a; 1、slave必须安装相同版本的mysql数据库软件。 2、master端必须开启二进制日志&#xff0c;slave端必须开启relay log 日志。 3、master主服务器和slave从服务器的server-id号不能一致。 4、slave端配置向master端来同步数据。 master…

云安全之访问控制的常见攻击及防御

访问控制攻击概述 访问控制漏洞即应用程序允许攻击者执行或者访问某种攻击者不具备相应权限的功能或资源。 常见的访问控制可以分为垂直访问控制、水平访问控制及多阶段访问控制 (上下文相关访问控制)&#xff0c;与其相应的访问控制漏洞为也垂直越权漏洞(普通用户可以访问或…

【QT】使用toBase64方法将.txt文件的明文变为非明文(类似加密)

目录 0.环境 1.背景 2.详细代码 2.1 .h主要代码 2.2 .cpp主要代码&#xff0c;主要实现上述的四个方法 0.环境 windows 11 64位 Qt Creator 4.13.1 1.背景 项目需求&#xff1a;我们项目中有配置文件&#xff08;类似.txt&#xff0c;但不是这个格式&#xff0c;本文以…

计算机网络之传输层

计算机网络 - 传输层 计算机网络 - 传输层 UDP 和 TCP 的特点UDP 首部格式TCP 首部格式TCP 的三次握手TCP 的四次挥手TCP 可靠传输TCP 滑动窗口TCP 流量控制TCP 拥塞控制 1. 慢开始与拥塞避免2. 快重传与快恢复 网络层只把分组发送到目的主机&#xff0c;但是真正通信的并不是…

leetcode-----二叉树习题

目录 前言 1. 二叉树的中序遍历 2. 相同的树 3. 二叉树的最大深度 4. 二叉树的最小深度 5.二叉树的前序遍历 6. 二叉树的后序遍历 7. 对称二叉树 前言 前面我们学习过了二叉树的相关知识点&#xff0c;那么今天我们就做做练习&#xff0c;下面我会介绍几道关于二叉树的…

JUnit介绍

JUnit是用于编写和运行可重复的自动化测试的开源测试框架&#xff0c; 这样可以保证我们的代码按预期工作。JUnit可广泛用于工业和作为支架(从命令行)或IDE(如Eclipse)内单独的Java程序。 JUnit提供&#xff1a; 断言测试预期结果。 测试功能共享通用的测试数据。 测试套件轻…

Java实现word excel ppt模板渲染与导出及预览 LibreOffice jodconverter

Java Office 一、文档格式转换 文档格式转换是office操作中经常需要进行一个操作&#xff0c;例如将docx文档转换成pdf格式。 java在这方面有许多的操作方式&#xff0c;大致可以分为内部调用&#xff08;无需要安装额外软件&#xff09;&#xff0c;外部调用&#xff08;需…

STM32三种开发方式及标准库和HAL库的编程差异

三种开发方式 STM32基于标准库函数和HAL库编程差异_stm32库函数和hal库-CSDN博客本文目的是以串口通信来简要分析STM32使用标准库函数和HAL库函数编程的差异。目录&#xff08;一&#xff09;开发方式1.配置寄存器2.库函数3.HAL库&#xff08;二&#xff09;库函数与HAL库对比…

RDMA技术(解决主从数据库数据不一致问题)

优质博文&#xff1a;IT-BLOG-CN 一、简介 RDMA(remote direct memory access)即远端直接内存访问&#xff0c;是一种高性能网络通信技术&#xff0c;具有高带宽、低延迟、无CPU消耗等优点。 主要解决网络传输中服务器端数据处理的延迟问题。 Remote&#xff1a;数据通过网络…

Arduino PLC IDE

Arduino PLC IDE MCU单片机进入全新的PLC领域概述需要的硬件和软件下一步操作1. Arduino PLC IDE Tool Setup2. Arduino PLC IDE Setup3. Project Setup4. Download the Runtime5. Connect to the Device6. License Activation with Product Key (Portenta Machine Control) 结…

MySQL 索引介绍和最佳实践

目录 一、前言二、索引类型1.1 主键索引&#xff08;PRIMARY KEY&#xff09;1.2 唯一索引&#xff08;UNIQUE&#xff09;1.3 普通索引&#xff08;NORMAL&#xff09;1.3.1 单列普通索引1.3.2 单列前缀普通索引1.3.3 多列普通索引1.3.4 多列前缀普通索引 1.4 空间索引&#x…

力扣 -- 10. 正则表达式匹配

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:bool isMatch(string s, string p) {int ms.size();int np.size();//处理后续映射关系s s;//处理后续映射关系p p;vector<vector<bool>> dp(m1,vector<bool>(n1));//初始化dp[0][0]true…