当前位置:   article > 正文

QTabWidget的tabbar不同方向显示 文字方向设置 图标跟随变化 实现方式 qt控件绘制原理_qtabwidget设置边框在左面,怎么将字体方向调整成垂直方向的

qtabwidget设置边框在左面,怎么将字体方向调整成垂直方向的

先来看结果图:(参考博客: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博客

  1. #include "TabBarStyle.h"
  2. #include <QPainter>
  3. #include <QStyleOptionTab>
  4. #include <QDebug>
  5. //拷贝的qt源码,不然下面有的函数调用该函数会报错
  6. static QWindow *qt_getWindow(const QWidget *widget)
  7. {
  8. return widget ? widget->window()->windowHandle() : nullptr;
  9. }
  10. TabBarStyle::TabBarStyle()
  11. : QProxyStyle()
  12. {
  13. // m_orientation = orientation;
  14. }
  15. //拷贝的qt源码,不然下面有的函数调用该函数会报错
  16. void TabBarStyle::tabLayout(const QStyleOptionTab *opt, const QWidget *widget, QRect *textRect, QRect *iconRect) const
  17. {
  18. Q_ASSERT(textRect);
  19. Q_ASSERT(iconRect);
  20. QRect tr = opt->rect;
  21. bool verticalTabs = opt->shape == QTabBar::RoundedEast
  22. || opt->shape == QTabBar::RoundedWest
  23. || opt->shape == QTabBar::TriangularEast
  24. || opt->shape == QTabBar::TriangularWest;
  25. if (verticalTabs)
  26. tr.setRect(0, 0, tr.height(), tr.width()); // 0, 0 as we will have a translate transform
  27. int verticalShift = pixelMetric(QStyle::PM_TabBarTabShiftVertical, opt, widget);
  28. int horizontalShift = pixelMetric(QStyle::PM_TabBarTabShiftHorizontal, opt, widget);
  29. int hpadding = pixelMetric(QStyle::PM_TabBarTabHSpace, opt, widget) / 2;
  30. int vpadding = pixelMetric(QStyle::PM_TabBarTabVSpace, opt, widget) / 2;
  31. if (opt->shape == QTabBar::RoundedSouth || opt->shape == QTabBar::TriangularSouth)
  32. verticalShift = -verticalShift;
  33. tr.adjust(hpadding, verticalShift - vpadding, horizontalShift - hpadding, vpadding);
  34. bool selected = opt->state & QStyle::State_Selected;
  35. if (selected) {
  36. tr.setTop(tr.top() - verticalShift);
  37. tr.setRight(tr.right() - horizontalShift);
  38. }
  39. // left widget
  40. if (!opt->leftButtonSize.isEmpty()) {
  41. tr.setLeft(tr.left() + 4 +
  42. (verticalTabs ? opt->leftButtonSize.height() : opt->leftButtonSize.width()));
  43. }
  44. // right widget
  45. if (!opt->rightButtonSize.isEmpty()) {
  46. tr.setRight(tr.right() - 4 -
  47. (verticalTabs ? opt->rightButtonSize.height() : opt->rightButtonSize.width()));
  48. }
  49. // icon
  50. if (!opt->icon.isNull()) {
  51. QSize iconSize = opt->iconSize;
  52. if (!iconSize.isValid()) {
  53. int iconExtent = pixelMetric(QStyle::PM_SmallIconSize, opt);
  54. iconSize = QSize(iconExtent, iconExtent);
  55. }
  56. QSize tabIconSize = opt->icon.actualSize(iconSize,
  57. (opt->state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled,
  58. (opt->state & QStyle::State_Selected) ? QIcon::On : QIcon::Off);
  59. // High-dpi icons do not need adjustment; make sure tabIconSize is not larger than iconSize
  60. tabIconSize = QSize(qMin(tabIconSize.width(), iconSize.width()), qMin(tabIconSize.height(), iconSize.height()));
  61. const int offsetX = (iconSize.width() - tabIconSize.width()) / 2;
  62. *iconRect = QRect(tr.left() + offsetX, tr.center().y() - tabIconSize.height() / 2,
  63. tabIconSize.width(), tabIconSize.height());
  64. if (!verticalTabs)
  65. *iconRect = QStyle::visualRect(opt->direction, opt->rect, *iconRect);
  66. tr.setLeft(tr.left() + tabIconSize.width() + 4);
  67. }
  68. if (!verticalTabs)
  69. tr = QStyle::visualRect(opt->direction, opt->rect, tr);
  70. *textRect = tr;
  71. }
  72. TabBarStyle::~TabBarStyle()
  73. {
  74. }
  75. void TabBarStyle::drawControl(ControlElement element, const QStyleOption *opt,
  76. QPainter *p, const QWidget *widget) const
  77. {
  78. // 步骤一:调用父类的绘制 tab 的其它控件,即其它空间都按照默认绘制即可
  79. if (element != CE_TabBarTabLabel)
  80. QProxyStyle::drawControl(element, opt, p, widget);
  81. #if 1
  82. // 步骤二:定制化 绘制tab标签页文本,以及图标
  83. if (element == CE_TabBarTabLabel)
  84. {
  85. if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) {
  86. QRect tr = tab->rect;
  87. bool verticalTabs = tab->shape == QTabBar::RoundedEast
  88. || tab->shape == QTabBar::RoundedWest
  89. || tab->shape == QTabBar::TriangularEast
  90. || tab->shape == QTabBar::TriangularWest;
  91. int alignment = Qt::AlignCenter | Qt::TextShowMnemonic;
  92. if (!proxy()->styleHint(SH_UnderlineShortcut, opt, widget))
  93. alignment |= Qt::TextHideMnemonic;
  94. if (verticalTabs) {
  95. p->save();
  96. QTransform m1 = QTransform::fromTranslate(tr.x()-8, tr.y()+5);
  97. p->setTransform(m1);
  98. }
  99. //自己控制绘制tab的 文本,icon
  100. //原本:west:文本(上方)+icon(下方),且文字和ico方向不对。east:icon(上方)+文本(下方),且文字和ico方向不对
  101. //目标:west:icon(上方)+文本(下方),且文字和ico方向正确。east:同理
  102. QRect iconRect;
  103. tabLayout(tab, widget, &tr, &iconRect);
  104. tr = proxy()->subElementRect(SE_TabBarTabText, opt, widget); //we compute tr twice because the style may override subElementRect
  105. if (!tab->icon.isNull()) {
  106. QPixmap tabIcon = tab->icon.pixmap(qt_getWindow(widget), tab->iconSize,
  107. (tab->state & State_Enabled) ? QIcon::Normal
  108. : QIcon::Disabled,
  109. (tab->state & State_Selected) ? QIcon::On
  110. : QIcon::Off);
  111. p->drawPixmap(iconRect.x(), iconRect.y(), tabIcon);
  112. }
  113. if (verticalTabs)
  114. p->restore();
  115. QString tabText;
  116. if (verticalTabs)
  117. {
  118. if (verticalTabs)
  119. p->save();
  120. p->resetTransform();
  121. QTransform m1 = QTransform::fromTranslate(0, 5);
  122. p->setTransform(m1);
  123. // 将文本字符串换行处理
  124. for (int i = 0; i < tab->text.length(); i++)
  125. {
  126. tabText.append(tab->text.at(i));
  127. tabText.append('\n');
  128. }
  129. if (tabText.length() > 1)
  130. tabText = tabText.mid(0, tabText.length() - 1);
  131. }
  132. else
  133. tabText = tab->text;
  134. if(verticalTabs)
  135. //注:这里传入的绘制区域,写的是 tab->rect,正确来说,不应该是这个区域,而是tr,即上面subElementRect(SE_TabBarTabText)
  136. //但是用tr显示不了,tr旋转一下也显示不了,当然,直接用tab->rect也就可以搞定的,但是其对齐方式作用就是
  137. //从tab最顶部开始算的,这里还存在一丁点问题,但是也没有什么明显问题了,不再继续研究了,这个不重要了
  138. proxy()->drawItemText(p, tab->rect, alignment, tab->palette, tab->state & State_Enabled, tabText, QPalette::WindowText);
  139. else
  140. proxy()->drawItemText(p, tr, alignment, tab->palette, tab->state & State_Enabled, tabText, QPalette::WindowText);
  141. if (verticalTabs)
  142. p->restore();
  143. //qt本身源码
  144. if (tab->state & State_HasFocus) {
  145. const int OFFSET = 1 + pixelMetric(PM_DefaultFrameWidth);
  146. int x1, x2;
  147. x1 = tab->rect.left();
  148. x2 = tab->rect.right() - 1;
  149. QStyleOptionFocusRect fropt;
  150. fropt.QStyleOption::operator=(*tab);
  151. fropt.rect.setRect(x1 + 1 + OFFSET, tab->rect.y() + OFFSET,
  152. x2 - x1 - 2*OFFSET, tab->rect.height() - 2*OFFSET);
  153. drawPrimitive(PE_FrameFocusRect, &fropt, p, widget);
  154. }
  155. }
  156. }
  157. #endif
  158. }
  159. QSize TabBarStyle::sizeFromContents(QStyle::ContentsType type, const QStyleOption *opt, const QSize &contentsSize, const QWidget *widget /*= nullptr*/) const
  160. {
  161. if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt))
  162. {
  163. bool verticalTabs = tab->shape == QTabBar::RoundedEast
  164. || tab->shape == QTabBar::RoundedWest
  165. || tab->shape == QTabBar::TriangularEast
  166. || tab->shape == QTabBar::TriangularWest;
  167. QString text = tab->text;
  168. int cnt = text.length()-1;
  169. QSize size = contentsSize;
  170. if (type == CT_TabBarTab)
  171. {
  172. if (verticalTabs)
  173. {
  174. size.rheight() += cnt*7; //这是因为文本每个字符后加了换行符后,和水平摆放占据长度空间不一样了
  175. }
  176. }
  177. // size.setWidth(size.width()-20);
  178. return size;
  179. }
  180. else
  181. return QProxyStyle::sizeFromContents(type, opt, contentsSize, widget);
  182. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/613523
推荐阅读
相关标签
  

闽ICP备14008679号