QTabWidget的tabbar不同方向显示 文字方向设置 图标跟随变化 实现方式 qt控件绘制原理

news/2024/5/7 14:55:24/文章来源:https://blog.csdn.net/kangkanglhb88008/article/details/136989882

先来看结果图:(参考博客:QTabWidget中tab页文本水平或垂直设置_pyqt tab_widget.settabposition(qtabwidget.west) 字体-CSDN博客)

从图中可知,"普通"是qt自己的样式,但是很明显,在垂直方向tab时候,字体也跟着垂直了,不太利于阅读,而第3个tab,则是将文字给正着显示过来了,第5个图,更是直接将文字也水平放置过来了,都是做了改进。但是仍然存在个问题,例如tab3,图标却仍然是反着的,不太好看,所以,如何解决这个问题?效果图如下:

我们需要来研究一下QTabBar绘制的原理,然后编写相关代码进行实现。

QTabBar绘制原理

QTabBar绘制过程,函数调用层级大概如下:

说明:一般的绘制简单控件,线条,路径,图片啥的,就是在paintEvent函数里直接调用painter的drawText函数,drawPixmap函数等, 绘制就可以了,但是绘制复杂一些的qt自己的控件,是通过创建一个QStylePainter 绘制控件专用对象,QStylePainter stylePainter里有个drawControl成员函数进行绘制的,而该函数需要传入绘制的控件类型(也叫element,例如tab头、pushbutton、listview等),然后里面调用 其拥有的成员QStyle对象 的 专用绘制方法void drawControl(QStyle::ControlElement element, const QStyleOption *option,   QPainter *painter, const QWidget *widget = nullptr) const;进行绘制。

这里面就有几个是虚函数,即我们可以子类化这些类重写这些函数,实现定制化功能:

  1. 子类化该控件类,重写 paintEvent(QPaintEvent * painter)函数
  2. 子类化QStyle,重写drawControl函数

paintEvent(QPaintEvent * painter) 

  1. QStylePainter stylePainter(this);

  2. QStyleOptionTab opt;

  3. initStyleOption(&opt,i); //初始化,将opt赋值为绘制时候所需要的信息,例如文本内容,线宽,尺寸大小等信息
  4. 这里可以设置一下画笔stylePainter的一些属性,例如绘制位置,旋转情况,颜色等
  5. stylePainter.drawControl(QStyle::CE_TabBarTabLabel,opt); //指定绘制对应的元素(tab文字和图标内容),以及绘制需要的细节信息
  6. stylePainter.drawControl(QStyle::CE_TabBarTabShape, opt);//指定绘制对应的元素(tab形状),以及绘制需要的细节信息

所以有了以上绘制原理的认识,接下来就是如何实现tabbar不同方向时,图标和文字同样能正着显示了。此外,qt其它控件的绘制,也是同样道理,以后我们都可以定制化实现或者魔改已有的控件了。

注:QStyle在qt中,已经有各种现成的子类了,QStyle <- QCommonStyle <- QProxyStyle

代码实现

这里仅仅举几个例子,因为原理明白了,就可以自己定制化实现了。

例1:实现图5:tab在左侧,但是tab文字水平

注:设置tab的方向,可以在UI设计时候直接设置了,也可以tabwidget的设置tab方向函数进行设置,然后tab头就是改变方向了的。

这个有两种实现方法

  1. 子类化 QTabBar ,重写 paintEvent(QPaintEvent *) 函数,里面调用 drawControl 前,修改掉 stylePainter 的方向为旋转90度即可(因为默认的drawControl(QStyle::CE_TabBarTabLabel,opt)函数里面会再次旋转90读的),此外,重写tabSizeHint(int index),返回该tab的大小也要跟着旋转一下的,即 QSize.transpose(); 可以参考博客:Qt tabWidget设置tab左右显示时 文字横向显示_qtabwidget标签文字横向-CSDN博客
  2. 子类化QStyle,重写drawControl函数。因为,控件的实际绘制是交给QStyle来完成的,所以我们就重写它的drawControl函数即可。然后把子类化的QStyle对象设置到目标控件中去即可(setStyle()函数实现)。可以参考最开始那个博客:(就是将每个字符后面加一个\n 换行符,从而实现一个垂直的qstring,然后画笔将文本直接正着画上去即可)QTabWidget中tab页文本水平或垂直设置_pyqt tab_widget.settabposition(qtabwidget.west) 字体-CSDN博客

备注:QSize sizeFromContents函数是内容显示的尺寸控制功能,比如我们可以额外增加一些size加进去,但是可以的。void drawItemText函数是绘制文本的集成化函数,我们也可以重新实现它,实现

例2:实现目标效果图:tab在左侧,tab文字垂直正着的,且图标也是正着的(也就是我们目标效果图)

唯一方法,子类化QStyle,重写drawControl函数。因为此时需要文字方向是竖直的,但是又是正的,是没法通过旋转画笔的方式直接来绘制实现。所以只能是重写QStyle的drawControl函数。

原理:文本的每一个字符后面加一个换行符,从而实现竖直方向的字符串。但是需要先绘制icon,然后y方向平移一下画笔,继续绘制处理好的字符串即可。

这里有几个要点:

  1. 传入的const QStyleOption *opt ,可以强转为 const QStyleOptionTab *tab,然后就能获得tab->rect,tab->text,tab->shape等信息,而tab->shape是能知道当前tab是水平的还是垂直的了。(查看qt的QTabBar控件源码得知,所以不知道怎么实现时候可以去看看qt的源码,很多问题就一目了然了
  2. 具体实现该函数时,直接从qt源码实现里拷贝过来吧,然后自己修改指定地方,实现自己功能即可。

核心代码如下:(在第一个博客基础上,替换掉TabBarStyle.cpp文件内容即可编译运行 QTabWidget中tab页文本水平或垂直设置_pyqt tab_widget.settabposition(qtabwidget.west) 字体-CSDN博客)

#include "TabBarStyle.h"
#include <QPainter>
#include <QStyleOptionTab>
#include <QDebug>//拷贝的qt源码,不然下面有的函数调用该函数会报错
static QWindow *qt_getWindow(const QWidget *widget)
{return widget ? widget->window()->windowHandle() : nullptr;
}TabBarStyle::TabBarStyle(): QProxyStyle()
{// m_orientation = orientation;
}//拷贝的qt源码,不然下面有的函数调用该函数会报错
void TabBarStyle::tabLayout(const QStyleOptionTab *opt, const QWidget *widget, QRect *textRect, QRect *iconRect) const
{Q_ASSERT(textRect);Q_ASSERT(iconRect);QRect tr = opt->rect;bool verticalTabs = opt->shape == QTabBar::RoundedEast|| opt->shape == QTabBar::RoundedWest|| opt->shape == QTabBar::TriangularEast|| opt->shape == QTabBar::TriangularWest;if (verticalTabs)tr.setRect(0, 0, tr.height(), tr.width()); // 0, 0 as we will have a translate transformint verticalShift = pixelMetric(QStyle::PM_TabBarTabShiftVertical, opt, widget);int horizontalShift = pixelMetric(QStyle::PM_TabBarTabShiftHorizontal, opt, widget);int hpadding = pixelMetric(QStyle::PM_TabBarTabHSpace, opt, widget) / 2;int vpadding = pixelMetric(QStyle::PM_TabBarTabVSpace, opt, widget) / 2;if (opt->shape == QTabBar::RoundedSouth || opt->shape == QTabBar::TriangularSouth)verticalShift = -verticalShift;tr.adjust(hpadding, verticalShift - vpadding, horizontalShift - hpadding, vpadding);bool selected = opt->state & QStyle::State_Selected;if (selected) {tr.setTop(tr.top() - verticalShift);tr.setRight(tr.right() - horizontalShift);}// left widgetif (!opt->leftButtonSize.isEmpty()) {tr.setLeft(tr.left() + 4 +(verticalTabs ? opt->leftButtonSize.height() : opt->leftButtonSize.width()));}// right widgetif (!opt->rightButtonSize.isEmpty()) {tr.setRight(tr.right() - 4 -(verticalTabs ? opt->rightButtonSize.height() : opt->rightButtonSize.width()));}// iconif (!opt->icon.isNull()) {QSize iconSize = opt->iconSize;if (!iconSize.isValid()) {int iconExtent = pixelMetric(QStyle::PM_SmallIconSize, opt);iconSize = QSize(iconExtent, iconExtent);}QSize tabIconSize = opt->icon.actualSize(iconSize,(opt->state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled,(opt->state & QStyle::State_Selected) ? QIcon::On : QIcon::Off);// High-dpi icons do not need adjustment; make sure tabIconSize is not larger than iconSizetabIconSize = QSize(qMin(tabIconSize.width(), iconSize.width()), qMin(tabIconSize.height(), iconSize.height()));const int offsetX = (iconSize.width() - tabIconSize.width()) / 2;*iconRect = QRect(tr.left() + offsetX, tr.center().y() - tabIconSize.height() / 2,tabIconSize.width(), tabIconSize.height());if (!verticalTabs)*iconRect = QStyle::visualRect(opt->direction, opt->rect, *iconRect);tr.setLeft(tr.left() + tabIconSize.width() + 4);}if (!verticalTabs)tr = QStyle::visualRect(opt->direction, opt->rect, tr);*textRect = tr;
}TabBarStyle::~TabBarStyle()
{
}void TabBarStyle::drawControl(ControlElement element, const QStyleOption *opt,QPainter *p, const QWidget *widget) const
{// 步骤一:调用父类的绘制 tab 的其它控件,即其它空间都按照默认绘制即可if (element != CE_TabBarTabLabel)QProxyStyle::drawControl(element, opt, p, widget);#if 1// 步骤二:定制化 绘制tab标签页文本,以及图标if (element == CE_TabBarTabLabel){if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) {QRect tr = tab->rect;bool verticalTabs = tab->shape == QTabBar::RoundedEast|| tab->shape == QTabBar::RoundedWest|| tab->shape == QTabBar::TriangularEast|| tab->shape == QTabBar::TriangularWest;int alignment = Qt::AlignCenter | Qt::TextShowMnemonic;if (!proxy()->styleHint(SH_UnderlineShortcut, opt, widget))alignment |= Qt::TextHideMnemonic;if (verticalTabs) {p->save();QTransform m1 = QTransform::fromTranslate(tr.x()-8, tr.y()+5);p->setTransform(m1);}//自己控制绘制tab的 文本,icon//原本:west:文本(上方)+icon(下方),且文字和ico方向不对。east:icon(上方)+文本(下方),且文字和ico方向不对//目标:west:icon(上方)+文本(下方),且文字和ico方向正确。east:同理QRect iconRect;tabLayout(tab, widget, &tr, &iconRect);tr = proxy()->subElementRect(SE_TabBarTabText, opt, widget); //we compute tr twice because the style may override subElementRectif (!tab->icon.isNull()) {QPixmap tabIcon = tab->icon.pixmap(qt_getWindow(widget), tab->iconSize,(tab->state & State_Enabled) ? QIcon::Normal: QIcon::Disabled,(tab->state & State_Selected) ? QIcon::On: QIcon::Off);p->drawPixmap(iconRect.x(), iconRect.y(), tabIcon);}if (verticalTabs)p->restore();QString tabText;if (verticalTabs){if (verticalTabs)p->save();p->resetTransform();QTransform m1 = QTransform::fromTranslate(0, 5);p->setTransform(m1);// 将文本字符串换行处理for (int i = 0; i < tab->text.length(); i++){tabText.append(tab->text.at(i));tabText.append('\n');}if (tabText.length() > 1)tabText = tabText.mid(0, tabText.length() - 1);}elsetabText = tab->text;if(verticalTabs)//注:这里传入的绘制区域,写的是 tab->rect,正确来说,不应该是这个区域,而是tr,即上面subElementRect(SE_TabBarTabText)//但是用tr显示不了,tr旋转一下也显示不了,当然,直接用tab->rect也就可以搞定的,但是其对齐方式作用就是//从tab最顶部开始算的,这里还存在一丁点问题,但是也没有什么明显问题了,不再继续研究了,这个不重要了proxy()->drawItemText(p, tab->rect, alignment, tab->palette, tab->state & State_Enabled, tabText, QPalette::WindowText);elseproxy()->drawItemText(p, tr, alignment, tab->palette, tab->state & State_Enabled, tabText, QPalette::WindowText);if (verticalTabs)p->restore();//qt本身源码if (tab->state & State_HasFocus) {const int OFFSET = 1 + pixelMetric(PM_DefaultFrameWidth);int x1, x2;x1 = tab->rect.left();x2 = tab->rect.right() - 1;QStyleOptionFocusRect fropt;fropt.QStyleOption::operator=(*tab);fropt.rect.setRect(x1 + 1 + OFFSET, tab->rect.y() + OFFSET,x2 - x1 - 2*OFFSET, tab->rect.height() - 2*OFFSET);drawPrimitive(PE_FrameFocusRect, &fropt, p, widget);}}}#endif
}QSize TabBarStyle::sizeFromContents(QStyle::ContentsType type, const QStyleOption *opt, const QSize &contentsSize, const QWidget *widget /*= nullptr*/) const
{if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)){bool verticalTabs = tab->shape == QTabBar::RoundedEast|| tab->shape == QTabBar::RoundedWest|| tab->shape == QTabBar::TriangularEast|| tab->shape == QTabBar::TriangularWest;QString text = tab->text;int cnt = text.length()-1;QSize size = contentsSize;if (type == CT_TabBarTab){if (verticalTabs){size.rheight() += cnt*7;    //这是因为文本每个字符后加了换行符后,和水平摆放占据长度空间不一样了}}// size.setWidth(size.width()-20);return size;}elsereturn QProxyStyle::sizeFromContents(type, opt, contentsSize, widget);
}

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何使用Python结合Pillow、matplotlib和OpenCV实现图片读取

使用Pillow库 matplotlib是一个绘图库&#xff0c;经常用于数据可视化&#xff0c;但它也可以用来展示图片。 from PIL import Image# 读取图片 image Image.open(.jpg)# 展示图片 image.show()使用OpenCV库 OpenCV是一个强大的计算机视觉和机器学习库。它不仅提供了大量的图像…

开源大数据集群部署(十八)Hive 安装部署

作者&#xff1a;櫰木 1 创建hive Kerberos主体 bash /root/bigdata/getkeytabs.sh /etc/security/keytab/hive.keytab hive2 安装 在hd1.dtstack.com主机root权限下操作&#xff1a; 解压包 [roothd3.dtstack.com software]# tar -zxvf apache-hive-3.1.2-bin.tar.gz -C …

【微服务】Nacos(注册中心)

文章目录 1.基本介绍1.概述2.Nacos下载和运行&#xff08;java8/maven3.2.x&#xff09;1.解压到没有中文路径的2.双击startup3.浏览器输入http://192.168.242.124:8848/nacos4.用户名和密码为nacos5.cmd输入netstat -anb | more查看监听端口 2.创建Nacos服务提供者 100041.项目…

VLAN的原理及配置

文章目录 一、VLAN的概述1、VLAN的概念2、VLAN的优势 二、静态VLAN三、静态VLAN的配置1.VLAN的范围2.VLAN基本配置 四、Trunk和access的作用参考 一、VLAN的概述 1、VLAN的概念 VLAN就是将网络从逻辑上划分为若按个小的网络&#xff0c;也就是虚拟局域网。 2、VLAN的优势 使…

鸿蒙OS开发案例:【API9】遍历沙漏文件夹并输入文件的大小

1.获取打印文件大小 /*** 获取打印文件大小*/static getFileSize(byteNum: number) {if (byteNum < 0) {return "shouldnt be less than zero!";} else if (byteNum < 1024) {return ${byteNum.toFixed(3)}B;} else if (byteNum < 1048576) {return (byteNu…

37.网络游戏逆向分析与漏洞攻防-游戏网络通信数据解析-解码器细化类的实现

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;36.数据解码器的…

剑指Offer题目笔记19(二分查找)

面试题68&#xff1a; 问题&#xff1a; ​ 输入一个排序的整形数组nums和一个目标值t&#xff0c;如果数组nums中包含t&#xff0c;则返回在数组中的下标&#xff0c;否则返回按照顺序插入到数组的下标。 解决方案&#xff1a; ​ 使用二分查找。每次二分查找都选取位于数组…

鸿蒙HarmonyOS应用开发之使用Node-API实现跨语言交互开发流程

使用Node-API实现跨语言交互&#xff0c;首先需要按照Node-API的机制实现模块的注册和加载等相关动作。 ArkTS/JS侧&#xff1a;实现C方法的调用。代码比较简单&#xff0c;import一个对应的so库后&#xff0c;即可调用C方法。 Native侧&#xff1a;.cpp文件&#xff0c;实现模…

左手医生:医疗 AI 企业的云原生提效降本之路

相信这样的经历对很多人来说并不陌生&#xff1a;为了能到更好的医院治病&#xff0c;不惜路途遥远奔波到大城市&#xff1b;或者只是看个小病&#xff0c;也得排上半天长队。这些由于医疗资源分配不均导致的就医问题已是老生长谈。 云计算、人工智能、大数据等技术的发展和融…

centos2anolis

我的centos7原地升级到anolis7记录 注意&#xff1a;如果是桌面版请先卸载firefox&#xff0c;否则so文件冲突。 参考&#xff1a; CentOS 7和8Linux系统迁移到国产Linux龙蜥Anolis OS 8手册_disable pam_pkcs11 module in pam configuration-CSDN博客 关于 CentOS 迁移龙蜥…

[2021]Zookeeper getAcl命令未授权访问漏洞概述与解决

今天在漏洞扫描的时候蹦出来一个zookeeper的漏洞问题&#xff0c;即使是非zookeeper的节点&#xff0c;或者是非集群内部节点&#xff0c;也可以通过nc扫描2181端口&#xff0c;获取极多的zk信息。关于漏洞的详细描述参考apache zookeeper官方概述&#xff1a;CVE-2018-8012: A…

ps国潮样机合集,内含茶杯、包装礼盒、抱枕、手机等

ps国潮样机合集&#xff0c;内含茶杯、包装礼盒、抱枕、手机等 链接&#xff1a;https://pan.baidu.com/s/1T-pXLcbHhHsZYho0WoV00g?pwdi5gs 提取码&#xff1a;i5gs 部分展示图 首先&#xff0c;PS样机的作用&#xff1a; 产品验证&#xff1a;PS样机可以帮助设计师和制…

【二叉树】Leetcode 102. 二叉树的层序遍历【中等】

二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09; 示例1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7]] 解题思路…

第二篇:3.1 广告印象(AD Impression) - IAB与MRC及《增强现实广告效果测量指南1.0》

--- 我为什么要翻译美国IAB科技公司系列标准 翻译计划 第一篇概述—IAB与MRC及《增强现实广告效果测量指南》之目录、适用范围及术语第二篇广告效果测量定义和其他矩阵之- 3.1 广告印象&#xff08;AD Impression&#xff09;第三篇广告效果测量定义和其他矩阵之- 3.2 可见度 …

最新的Flutter3.x版本获取应用包名的方法

以前的flutter项目可以在 AndroidManifest.xml 中获取应用包名&#xff0c; 最新的Flutter3.x版本要获取应用包名可以找到build.gradle 更多内容参考&#xff1a;最新的Flutter3.x版本如何获取应用包名

视图的作用

目录 视图的作用 创建视图 为 scott 分配创建视图的权限 查询视图 复杂视图的创建 视图更新的限制问题 更新视图中数据的部门编号&#xff08;视图的存在条件&#xff09; 限制通过视图修改数据表内容 创建只读的视图 复杂视图创建 oracle从入门到总裁:​​​​​​h…

UMass、MIT等提出3D世界具身基础模型,机器人根据生成的世界模型无缝连接3D感知、推理和行动

在最近的研究中&#xff0c;视觉-语言-动作&#xff08;VLA&#xff0c;vision-language-action&#xff09;模型的输入基本都是2D数据&#xff0c;没有集成更通用的3D物理世界。 此外&#xff0c;现有的模型通过学习「感知到动作的直接映射」来进行动作预测&#xff0c;忽略了…

数据结构——线性表(一)

线性表&#xff0c;顾名思义&#xff0c;是具有像线一样的性质的表。如同学生们在操场上排队&#xff0c;一个跟着一个排队&#xff0c;有一个打头&#xff0c;有一个收尾&#xff0c;在其中的学生都知道前一个是谁&#xff0c;后一个是谁&#xff0c;这样就像一根线将他们都串…

html页面使用@for(){},@if(){},利用jquery 获取当前class在列表中的下标

基于以前的项目进行修改优化&#xff0c;前端代码根据List元素在html里进行遍历显示 原先的代码&#xff1a; 其中&#xff0c;noticeGuide.Id是标识noticeGuide的唯一值&#xff0c;但是不是从0开始的【是数据库自增字段】 但是在页面初始化加载的时候&#xff0c;我们只想…

鸿蒙OS开发问题:(ArkTS) 【解决中文乱码 string2Uint8Array、uint8Array2String】

在进行base64编码中&#xff0c;遇到中文如果不进行处理一定会出现乱码 let result1: string CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse((一二三四五六七八九十123)))LogUtils.i("result1 " result1);let result2: string CryptoJS.enc.Base64.par…