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

先来看结果图:(参考博客: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.mzph.cn/news/768577.shtml

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

相关文章

最新Java面试题5【2024初级】

互联网大厂面试题 1&#xff1a;阿里巴巴Java面试题 2&#xff1a;阿里云Java面试题-实习生岗 3&#xff1a;腾讯Java面试题-高级 4&#xff1a;字节跳动Java面试题 5&#xff1a;字节跳动Java面试题-大数据方向 6&#xff1a;百度Java面试题 7&#xff1a;蚂蚁金服Java…

Excel打开CSV文件中文乱码问题

Excel的数据导入功能 直接用Excel打开下载的CSV文件&#xff0c;会看到汉字乱码&#xff0c;数字显示正常。如下图所示现象。 请先正常打开一份空白的excel文件&#xff0c;将鼠标定位在第一行第一列&#xff0c;这边鼠标定位的位置将决定后续打开的csv文件在excel中展示的位置…

【Python从入门到进阶】51、电影天堂网站多页面下载实战

接上篇《50、当当网Scrapy项目实战&#xff08;三&#xff09;》 上一篇我们讲解了使用Scrapy框架在当当网抓取多页书籍数据的效果&#xff0c;本篇我们来抓取电影天堂网站的数据&#xff0c;同样采用Scrapy框架多页面下载的模式来实现。 一、抓取需求 打开电影天堂网站&…

使用ADB一键停止Android设备上所有应用程序的批处理脚本

当在 Android 设备上进行开发或测试时&#xff0c;经常需要停止某些应用程序。这可能是为了清除缓存、重新加载应用程序或测试新的应用程序行为。幸运的是&#xff0c;通过使用 ADB&#xff08;Android 调试桥&#xff09;&#xff0c;可以通过命令行轻松地停止应用程序。 以下…

[leetcode] 26. 删除有序数组中的重复项

给你一个 非严格递增排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的数量为 k &#xff0c;你…

C语言之strsep用法实例(八十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

C++实现FFmpeg音视频实时拉流并播放

1.准备工作: 下载rtsp流媒体服务器rtsp-simple-server,安装go开发环境并编译 编译好后启动流媒体服务器 准备一个要推流的mp4视频文件,如db.mp4 使用ffmpeg开始推流 推流命令: ffmpeg -re -stream_loop -1 -i db.mp4 -c copy -rtsp_transport tcp -f rtsp rtsp://192.168.16…

Soot 安装和简单使用

目录 前言 一、Soot 的下载和安装 1.1 在命令行中使用 Soot 1.2 在项目中使用 Soot 二、使用 Soot 生成中间代码 (IR) 三、使用 Soot 进行 Java 类插桩 四、使用 Soot 生成控制流图 (CFG) 4.1 按语句划分的控制流程图 4.2 按基本块划分的控制流程图 五、Graphviz 工具…

Docker jupyter 容器中添加matplotlib 中文支持

本教程基于 jupyter/datascience-notebook&#xff0c;适用其他容器。 # 查看所有 Docker 容器 docker ps -a # 进入已经运行的 Jupyter 容器 docker exec -it CONTAINER_ID bash 本例中CONTAINER_ID为2e # 切换到 matplotlib 的字体目录&#xff08;find / -name "…

HTML5和CSS3新特性

Html新增属性 1.新增语义化标签 <header>&#xff1a;头部标签 <nav>&#xff1a;导航标签 <article>&#xff1a;内容标签 <section>&#xff1a;定义文档某个区域 <aside>&#xff1a;侧边栏标签 <footer>&#xff1a;尾部标签 2.…

kafka优化--来自gpt

增加Topic的分区数&#xff1a; 分区数越多&#xff0c;可以并行处理的能力越强。 配置参数&#xff1a;num.partitions 增加消费者&#xff08;Consumer&#xff09;的并行度&#xff1a; 根据硬件资源调整消费者实例的数量。 配置消费者组内的消费者实例数。 调整消费者&…

力扣hot100:994. 腐烂的橘子(多源BFS)

这是一个典型的多源BFS问题&#xff0c;如果初学数据结构的同学&#xff0c;可能第一次不能想到&#xff0c;但是如果做过一次应该就能运用了。      主要思路大概是初始时&#xff0c;多个点进入队列然后进行BFS。将某一等价集合视作同一个起始点&#xff08;超级源点&…

blender插件笔记

目录 文件拖拽导入 smpl导入导出 好像可以导入动画 smpl_blender_addon导入一帧 保存pose 导入导出完整代码 文件拖拽导入 https://github.com/mika-f/blender-drag-and-drop 支持格式&#xff1a; *.abc*.bvh*.dae*.fbx*.glb*.gltf*.obj*.ply*.stl*.svg*.usd*.usda*.…

【QT问题】 Qt信号函数如果重名,调用怎么处理

问题描述&#xff1a; 在调用某个类的信号函数的时候&#xff0c;出现信号函数名字相同&#xff0c;参数不同的情况&#xff0c;但是Qt在链接信号槽的时候&#xff0c;又不需要指明信号函数参数&#xff0c;此时就会出现无法分辨的情况。 例如&#xff1a;QComboBox的信号 Q_…

前端学习之用css和html做一个仿淘宝的导航栏

代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>仿淘宝界面案例</title><style>/* 最外层盒子 */.container{width: 270px;height: 385px;border: 1px solid rgb(255, 208, 0);bord…

MacOS快速安装FFmpeg、ffprobe、ffplay

文章目录 一、工具简介二、mac 安装ffprobe、FFmpeg等相关工具2.1 方法一&#xff1a;使用Homebrew安装FFmpeg2.2 从官网下载FFmpeg安装包&#xff0c;源码安装2.3 macOS 无法验证开发者时安装 一、工具简介 这些工具都是与多媒体处理和流媒体相关的开源工具&#xff0c;它们都…

学习vue3第十节(插槽v-slot)

本节主要介绍一下 v-slot 插槽指令&#xff0c;以及插槽相关内容 1、定义&#xff1a; 子组件给父组件提供使用的一个位置&#xff0c;使用<slot></slot>表示&#xff0c;父组件可以在这个位置填充任何代码&#xff1b; 2、默认插槽 匿名插槽&#xff1a;会自定…

JavaScript中的正则表达式使用总结

JavaScript中的正则表达式是一种强大的工具&#xff0c;用于处理文本数据&#xff0c;包括搜索、匹配和替换文本中的特定字符或模式。以下是对JavaScript中正则表达式使用的一些总结&#xff1a; 1. 创建正则表达式 在JavaScript中&#xff0c;你可以使用字面量或RegExp对象来…

管道疏通房屋补漏官方网站源码-视频搭建教程

[安全]修复jquery低版本的xss安全漏洞&#xff0c;升级用最新版jquery&#xff1b; [新增]后台的登录页、欢迎页支持自定义模板文件&#xff1b; [新增]后台多语言列表管理支持手工同步文档数据&#xff1b; [新增]后台商品发布时&#xff0c;单规格商品支持会员折扣价的设置…

微信小程序多图列表页面性能问题为什么会出现?如何解决?

微信小程序中的多图列表页面性能问题主要是由于以下几个原因导致的&#xff1a; 图片过大&#xff1a;在多图列表页面中&#xff0c;如果图片过大&#xff0c;会导致页面加载时间过长&#xff0c;从而影响用户体验。请求过多&#xff1a;在多图列表页面中&#xff0c;如果一次…