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

news2024/12/24 18:54:58

先来看结果图:(参考博客: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 transform
    
    int 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 widget
    if (!opt->leftButtonSize.isEmpty()) {
        tr.setLeft(tr.left() + 4 +
                   (verticalTabs ? opt->leftButtonSize.height() : opt->leftButtonSize.width()));
    }
    // right widget
    if (!opt->rightButtonSize.isEmpty()) {
        tr.setRight(tr.right() - 4 -
                    (verticalTabs ? opt->rightButtonSize.height() : opt->rightButtonSize.width()));
    }
    
    // icon
    if (!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 iconSize
        tabIconSize = 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 subElementRect
            
            if (!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);
            }
            else
                tabText = 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);
            else
                proxy()->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;
    }
    else
        return QProxyStyle::sizeFromContents(type, opt, contentsSize, widget);
}

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

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

相关文章

Ubuntu18.04桌面版设置静态IP地址

引用: Ubuntu配置静态IP_ubuntu配置静态ip地址-CSDN博客 正文 默认Unbuntu 18.04 Desktop桌面版使用 netplan 管理网卡网络地址。使用Unbuntu 18.04 桌面版配置&#xff0c;可以通过桌面上的设置图标配置网卡的静态IP地址。 点击桌面右上角下拉框&#xff0c;点击“设置”按…

蓝桥杯(2):python基础算法【上】

时间复杂度、枚举、模拟、递归、进制转换、前缀和、差分、离散化 1 时间复杂度 重要是看循环&#xff0c;一共运行了几次 1.1 简单代码看循环 #时间复杂度1 n int(input()) for i in range(1,n1):for j in range(0,i):pass ###时间复杂度&#xff1a;123....nn(1n)/2 所以…

面试题 之 react

1.说说对react的理解 1️⃣是什么 React是用于构建用户界面的 JavaScript 库,遵循组件设计模式、声明式编程范式和函数式编程概念&#xff0c;更高效使用虚拟 DOM 来有效地操作 DOM &#xff0c;遵循从高阶组件到低阶组件的单向数据流。 react 类组件使用一个名为 render() 的方…

JAVA实战开源项目:大病保险管理系统(Vue+SpringBoot)

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统配置维护2.2 系统参保管理2.3 大病保险管理2.4 大病登记管理2.5 保险审核管理 三、系统详细设计3.1 系统整体配置功能设计3.2 大病人员模块设计3.3 大病保险模块设计3.4 大病登记模块设计3.5 保险审核模块设计 四、…

面试算法-88-反转链表

题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 解 class Solution {public ListNode reverseList(ListNode head) {if(head null || hea…

chatGPT中文在线版本(亲测可用

ChatGPT是一个先进的自然语言处理模型&#xff0c;由OpenAI开发。它通过深度学习技术训练而成&#xff0c;可以进行对话、回答问题等多种自然语言处理任务。对于学生、开发者、研究人员和任何对人工智能感兴趣的人来说&#xff0c;这是一个非常有用的工具。 最近找到一个国内可…

【算法篇】逐步理解动态规划1(斐波那契数列模型)

目录 斐波那契数列模型 1. 第N个泰波那契数 2.使用最小花费爬楼梯 3.解码方法 学过算法的应该知道&#xff0c;动态规划一直都是一个非常难的模块&#xff0c;无论是状态转移方程的定义还是dp表的填表&#xff0c;都非常难找到思路。在这个算法的支线专题中我会结合很多力…

赋能数据收集:从机票网站提取特价优惠的JavaScript技巧

背景介绍 在这个信息时代&#xff0c;数据的收集和分析对于旅游行业至关重要。在竞争激烈的市场中&#xff0c;实时获取最新的机票特价信息能够为旅行者和旅游企业带来巨大的优势。 随着机票价格的频繁波动&#xff0c;以及航空公司和旅行网站不断推出的限时特价优惠&#xff…

chrome浏览器插件extension开发中content内容脚本和background脚本通讯

有时候我们想监听页面中的数据变化&#xff0c;然后将监听到的数据传递给background脚本处理&#xff0c;比如根据不同的数据&#xff0c;来处理不同的业务逻辑&#xff0c;存储到服务器&#xff1f;或者控制浏览器显示效果&#xff1f;都可以&#xff0c;问题的重点是怎么让co…

【python 装饰器 - 重试】做一个简易重试装饰器,如果函数执行错误则会自动重新执行,可设置重试次数,对爬虫比较友好

文章日期&#xff1a;2024.03.19 使用工具&#xff1a;Python 类型&#xff1a;装饰器 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 AES解密处理&#xff08;直接解密即可&#xff09;&#xff08;crypto-js.js 标准算法&#xff09;&…

Java异常类型及异常处理方式

本章学习内容&#xff1a;使用异常处理机制&#xff0c;对程序运行过程中出现的异常情况进行捕捉并处理. 目录 &#x1f4cc; Java异常概述 &#x1f4cc; Java异常体系结构 &#x1f4cc; 常见的异常 &#x1f4cc; 异常处理 &#x1f4cc; Java异常概述 ○ 异常的概念&…

Python并发编程:线程和多线程的使用

前面的文章&#xff0c;我们讲了什么Python的许多基础知识&#xff0c;现在我们开始对Python并发编程进行学习。我们将探讨 Python 中线程和多线程的使用。帮助大家更好地理解如何使用这种技术。 目录 1. 线程&#xff08;Threads&#xff09; 1.1 Python 中的线程工作原理 …

C语言数据结构之归并排序

疏雨池塘见 微风襟袖知 目录 归并排序的介绍 基本思想 时间复杂度分析 ⭐归并排序步骤 空间复杂度分析 代码展示 ✨归并排序的非递归 代码展示 总结&#x1f525; 归并排序的介绍 归并排序&#xff0c;是创建在归并操作上的一种有效的排序算法。算法是采用分治法&#xff…

Linux Ncurses库部分函数使用说明

目录 1. initscr&#xff08;&#xff09;函数 2. endwin&#xff08;&#xff09;函数 3. curs_set()函数 4.noecho()函数 5. keypad()函数 6. start_color()函数 7.init_pair()函数 8.getch()函数 9.move()函数 10.addch()函数 11. refresh()函数 12.inch()函数…

国务院办公厅发布:政府类网站网页设计规范(试行)

国务院办公厅于2019年12月发布了《政府类网站网页设计规范&#xff08;试行&#xff09;》。该规范的发布旨在统一政府类网站的设计风格和标准&#xff0c;提升政府网站的用户体验和可访问性&#xff0c;推动政府信息公开和服务的提升。 该规范涵盖了政府类网站的各个方面&…

php搭建websocket

1.项目终端执行命令&#xff1a;composer require topthink/think-worker 2.0.x 2.config多出三个配置文件&#xff1a; 3.当使用php think worker:gateway命令时&#xff0c;提示不支持Windows。 4.打包项目为zip格式 5.打包数据库 6.阿里云创建记录 7.宝塔面板新增站点…

揭秘海外谷歌关键词优化细分人群的3个独家技巧--大舍传媒

揭秘海外谷歌关键词优化细分人群的3个独家技巧--大舍传媒 引言 在当今数字化的时代&#xff0c;拥有一个强大的线上存在是成功的关键之一。而在谷歌搜索引擎中&#xff0c;关键词优化是提高流量增长率的重要策略。本文将揭示海外谷歌关键词优化的三个独家技巧&#xff0c;帮助…

MySQL的事务深入理解和存储系统

目录 一、事务的基本理论 1.事务的隔离 1.1事务之间的相互影响 1.2事物隔离级别 2.查询和设置事物隔离级别 2.1查询全局事务隔离级别 2.2查询会话事物隔离级别 2.3设置全局事务隔离级别 2.4设置会话事务隔离级别 ​编辑3.事务控制语句 ​编辑3.1提交事务 ​编辑3.2…

CSP-S 真题:格雷码

原文链接&#xff1a;CSP-S 真题第二讲&#xff1a;格雷码 说明&#xff1a;CSDN和公众号文章同步发布&#xff0c;需要第一时间收到最新内容&#xff0c;请关注公众号【比特正传】。 一、题目背景 题目来源&#xff1a;CSP-S 2019年 T1 题目考察点&#xff1a;递归、搜索 …

【每周赠书活动第1期】Python编程 从入门到实践 第3版(图灵出品)

编辑推荐 适读人群 &#xff1a;本书适合对Python感兴趣的所有读者阅读。 编程入门就选蟒蛇书&#xff01; 【经典】Python入门经典&#xff0c;常居Amazon等编程类图书TOP榜 【畅销】热销全球&#xff0c;以12个语种发行&#xff0c;影响超过 250 万读者 【口碑】好评如潮…