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框架多页面下载的模式来实现。 一、抓取需求 打开电影天堂网站&…

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.…

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

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

前端学习之用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…

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

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

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

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

C语言自定义类型联合体和枚举

union n.工会&#xff1b;联邦&#xff0c;联盟&#xff1b;协会&#xff0c;俱乐部&#xff1b;联合&#xff0c;合并&#xff1b; 结婚&#xff0c;婚姻&#xff1b;美利坚合众国&#xff08;the Union&#xff09;&#xff1b; &#xff08;数&#xff09;并&#xff0c;并集…

交互式QGraphicsView(平移/缩放/旋转)

一 简述 Graphics View提供了一个平台&#xff0c;用于大量自定义 2D 图元的管理与交互&#xff0c;框架包括一个事件传播架构&#xff0c;支持场景 Scene 中的图元 Item 进行精确的双精度交互功能。Item 可以处理键盘事件、鼠标按下、移动、释放和双击事件&#xff0c;同时也…

福昕阅读器 PDF 文档基本操作

福昕阅读器 PDF 文档基本操作 References 转至 PDF 顶部 快捷键&#xff1a;Home. 转至 PDF 顶部 快捷键&#xff1a;End. 打开超链接 文本选择工具 -> 手形工具 (Hand Tool) -> 点击超链接 福昕阅读器 同时在多个窗口中打开多个文件 文件 -> 偏好设置 -> 文…

高中信息技术教资刷题笔记_选择题篇

1.信息技术基础 位与字节的换算 模2除法运算 网页保存 进制之间的计算 教你快速学会二进制、十进制、十六进制之间的转换 - 知乎 (zhihu.com) 原码、补码、反码计算 物联网技术 位运算 按位与&#xff1a;同位置为1&#xff0c;则为1&#xff0c;其他都是0按位或&#xff1a;有…

MRC是谁?- 媒体评级委员会 Media Rating Council

在在线广告的世界里&#xff0c;有许多不同的技术和实践用于提供和衡量广告。对于广告商、出版商和营销人员来说&#xff0c;了解这些技术是如何工作的以及如何有效使用这些技术很重要。在这方面发挥关键作用的一个组织是媒体评级委员会&#xff08;MRC&#xff09;。 1. 了解…

Android 项目新建问题总结

title: Android 项目新建问题总结 search: 2024-03-24 tags: “#Android 项目新建问题总结” Android 项目新建问题总结 一、gradle 项目每次都自动下载依赖包到C盘 背景&#xff1a;idea 首次打开一个 gradle 项目&#xff0c;都会在 C 盘下载项目所需的依赖包&#xff0c;但…

Automatic Prompt Engineering

让大模型自己生成prompt&#xff0c;生成提示&#xff08;prompt&#xff09;存在两种不同的操作方式。第一种方式是在文本空间中进行&#xff0c;这种提示以离散的文本形式存在。第二种方式是将提示抽象成一个向量&#xff0c;在特征空间中进行操作&#xff0c;这种提示是抽象…

React高阶组件(HOC)

高阶组件的基本概念 高阶组件&#xff08;HOC&#xff0c;Higher-Order Components&#xff09;不是组件&#xff0c;而是一个函数&#xff0c;它会接收一个组件作为参数并返回一个经过改造的新组件&#xff1a; const EnhancedComponent higherOrderComponent(WrappedCompo…