当前位置:网站首页>QStyle实现自绘界面项目实战(一)
QStyle实现自绘界面项目实战(一)
2022-06-27 10:57:00 【荆楚闲人】
1.前言
QT自绘技术系列文章链接如下:
- 《QStyle类用法总结(一)》。对Qt自定义风格简单描述,对QStyle及其相关类作了概念性的描述。
- 《QStyle类用法总结(二)》。对QStyle及其相关类作了详细描述。
- 《QStyle类用法总结(三)》。对QStyle中的各widget元素的层次继承树做了详细描述。
- 《QProxyStyle用法简述》。通过一个简单的例子,演示了QProxyStyle类用法。
- 《QStyle实现自绘界面项目实战(一)》通过项目实战来理解前几篇提到的内容,理解QStyle及其子类在自绘时的工作机制。
本文通过项目实战来理解前几篇提到的内容,理解QStyle及其子类在自绘时的工作机制。在阅读本文章之前,请先阅读上述列出的相关文章,否则没有自绘基础,很难读懂本文章。
2.问题的提出
问题的提出:在不用样式表的情况下,如何完全用代码实现如下界面,且该界面能符合各种平台风格:

图1
Qt内建的各种widgets用QStyle类执行近乎所有关于这些widgets的绘制工作,以保证这些widgets看起来非常接近它们所处操作系统平台的风格。下面这张图显示QComboBox 在9种不同操作系统平台下的风格:

图2 QComboBox 在9种不同的操作系统平台下的风格
如何只需写一次代码能实现图1的风格,且能像图2的QComboBox 能适应9种不同操作系统平台下的风格?
3.工程说明
style工程存放在Qt安装目录下的Examples\Qt-x.xx.xx\widgets\animation\stickman目录下。其中
x.xx.xx为Qt的版本号。如:5.14.1
4.工程代码剖析
4.1 .NorwegianWoodStyle类
4.1.1.standardPalette函数
该函数重写了从基类QStyle继承过来的standardPalette()函数。该函数的作用是返回一个标准调色板。该函数负责各个widget部件的背景色、前景色、文本前景色、背景色、高亮、对比突出显示等颜色设置。总之,一切和界面颜色相关的设置都在该函数中进行。本函数中用到QPalette类颜色角色、颜色组等知识点,可参见《QPalette类的使用和说明》描述。读者可以更改各个颜色角色对应的颜色,从而更直观地看看每个颜色角色到底是表示的啥含义。
4.1.2.polish() 、unpolish()函数
QStyle提供了polish() 、unpolish()函数。所有widgets在被显示之前,都会调用polish()函数,在隐藏之后,会调用unpolish()函数。可以利用这些函数在widget上设置一些属性或者做些其它工作。例如:如果你需要知道鼠标是否在widge上悬浮,可以通过QWidget::setAttribute()函数设置Qt::WA_Hover属性,之后,State_MouseOver状态标志将会在widget的QStyleOption参数中设置。
4.1.3.pixelMetric函数
本函数计算widget各个部件元素占用像素尺寸。如:本函数中的PM_ComboBoxFrameWidth表示combo box组合框边框的宽度,其默认等同于widget中的默认边框占据的宽度,通常为2个像素。可以看到,在本函数中拦截并修改combo box组合框边框的宽度为8个像素宽。 如下为 PM_ComboBoxFrameWidth为8时的效果图:

图1
如下为 PM_ComboBoxFrameWidth为98时的效果图:

图2
如下为 PM_ComboBoxFrameWidth为2时的效果图:

图3
可以看到高度、宽度都发生了变化。
PM_ScrollBarExtent表示垂直滚动条的宽度和水平滚动条的高度。可以看到,在本函数中拦截并修改垂直滚动条的宽度或水平滚动条的高度为QStyle默认的垂直滚动条的宽度或水平滚动条的高度值再加4个像素宽。效果如下:

图4
将加4改为加88,如果如下:

图5
对于其它元素占据的像素尺寸,则采用QStyle默认的值。
4.1.4.styleHint函数
本函数参见《QProxyStyle用法简述》博文的描述。
4.1.5.drawPrimitive函数
drawPrimitive函数switch语句代码如下:
switch (element) {
case PE_PanelButtonCommand:
{
int delta = (option->state & State_MouseOver) ? 64 : 0;
QColor slightlyOpaqueBlack(0, 0, 0, 63);
QColor semiTransparentWhite(255, 255, 255, 127 + delta);
QColor semiTransparentBlack(0, 0, 0, 127 - delta);
int x, y, width, height;
option->rect.getRect(&x, &y, &width, &height);
//! [12]
//! [13]
QPainterPath roundRect = roundRectPath(option->rect);
//! [13] //! [14]
int radius = qMin(width, height) / 2;
//! [14]
//! [15]
QBrush brush;
//! [15] //! [16]
bool darker;
const QStyleOptionButton *buttonOption =
qstyleoption_cast<const QStyleOptionButton *>(option);
if (buttonOption
&& (buttonOption->features & QStyleOptionButton::Flat)) {
brush = option->palette.window();
darker = (option->state & (State_Sunken | State_On));
}
else {
if (option->state & (State_Sunken | State_On)) {
brush = option->palette.mid();
darker = !(option->state & State_Sunken);
}
else {
brush = option->palette.button();
darker = false;
//! [16] //! [17]
}
//! [17] //! [18]
}
//! [18]
//! [19]
painter->save();
//! [19] //! [20]
painter->setRenderHint(QPainter::Antialiasing, true);
//! [20] //! [21]
painter->fillPath(roundRect, brush);
//! [21] //! [22]
if (darker)
//! [22] //! [23]
painter->fillPath(roundRect, slightlyOpaqueBlack);
//! [23]
//! [24]
int penWidth;
//! [24] //! [25]
if (radius < 10)
penWidth = 3;
else if (radius < 20)
penWidth = 5;
else
penWidth = 7;
QPen topPen(semiTransparentWhite, penWidth);
QPen bottomPen(semiTransparentBlack, penWidth);
if (option->state & (State_Sunken | State_On))
qSwap(topPen, bottomPen);
//! [25]
//! [26]
int x1 = x;
int x2 = x + radius;
int x3 = x + width - radius;
int x4 = x + width;
if (option->direction == Qt::RightToLeft) {
qSwap(x1, x4);
qSwap(x2, x3);
}
QPolygon topHalf;
topHalf << QPoint(x1, y)
<< QPoint(x4, y)
<< QPoint(x3, y + radius)
<< QPoint(x2, y + height - radius)
<< QPoint(x1, y + height);
painter->setClipPath(roundRect);
painter->setClipRegion(topHalf, Qt::IntersectClip);
painter->setPen(topPen);
painter->drawPath(roundRect);
//! [26] //! [32]
QPolygon bottomHalf = topHalf;
bottomHalf[0] = QPoint(x4, y + height);
painter->setClipPath(roundRect);
painter->setClipRegion(bottomHalf, Qt::IntersectClip);
painter->setPen(bottomPen);
painter->drawPath(roundRect);
painter->setPen(option->palette.windowText().color());
painter->setClipping(false);
painter->drawPath(roundRect);
painter->restore();
}
break;
//! [32] //! [33]
default:
//! [33] //! [34]
QProxyStyle::drawPrimitive(element, option, painter, widget);
}PE_PanelButtonCommand表示按钮部件元素,按钮层次树如下:

图6
红色方框为PE_PanelButtonCommand在层次树中的位置,其对应到按钮上表示的区域如下:

图7
其含义为:
Button used to initiate an action, for example, a QPushButton.
即表示一个代表动作的按钮。关于更多PE_开头的各值含义及层次树,请参考:
第14行:通过roundRectPath函数依据按钮所在外包围矩形计算出一个圆角矩形的路径类(QPainterPath)对象roundRect。
第26~30行:如果按钮是扁平按钮,则将绘制roundRect的画刷设置为QPalette::Window 即一般背景色。如果按钮被勾选(State_On为true,此时按钮是QCheckBox)或被按下(State_Sunken为true,此时按钮是QPushButton),则将darker置为true。
第31~35行:如果按钮不是扁平按钮,且如果按钮被勾选(State_On为true,此时按钮是QCheckBox)或被按下(State_Sunken为true,此时按钮是QPushButton),此时将darker置为false,否则置为true,且将绘制roundRect的画刷设置为QPalette::mid。
第46~54,70-71行:用上面的画刷或颜色绘制出圆角矩形roundRect,通过交换画笔,以便按钮呈现不同的样式,从而实现按钮勾选或按下状态。
第80-83行:如果布局方向是从右到左,则对x1,x4和x2, x3进行交换,即将原来的左变为右,原来的右变为左。需要说明的是:在一些阿拉伯语的国家,他们的文字的读写方向是从右到左的。
第85-90行:将一些点赋给QPolygon类型的topHalf对象。为了便于后文的描述,暂且称为:
A表示QPoint(x1, y)点;B表示QPoint(x4, y)点;C表示QPoint(x3, y + radius)点;D表示QPoint(x2, y + height - radius)点;E表示QPoint(x1, y + height)点;
第92-108行:绘制圆角矩形等,从而绘制出不规则按钮。第92行通过设置一个QPainterPath类型的裁剪路径对象,则此时除了绘制出如下圆角矩形外,按钮外包围矩形不在此圆角矩形范围内的部分都不会绘制,故经过此代码后,按钮外观如下:(注意:下文为了突出对比,画刷颜色选择蓝色, 画笔颜色选择红色,具体颜色请以本工程代码设置的颜色为准,下同):

图8
第93 ~ 95 行通过设置一个由A、B、C、D、E点构成的多边形且和92行绘制的roundRect路径对象以交集模式裁剪,则此时按钮所占且绘制区域是如下红色边界多边形和蓝色路径表示的圆角矩形相交部分:

图9
即如下:

图10
同样的:第101~ 104 行通过设置一个由A、B、C、D、E点构成的多边形且和101行绘制的roundRect路径对象以交集模式裁剪,则此时按钮所占且绘制区域是如下红色边界多边形和蓝色路径表示的圆角矩形相交部分:

图11
注意:
第4~7行设置鼠标在按钮上悬浮和不悬浮状态的颜色。当在67、68行时,用该颜色分别作为图10、图11的画笔颜色,从而使鼠标移动到按钮上面或移出按钮上面时,呈现不同的边框颜色,这样就会让用户能形成鼠标移进移出按钮的视觉冲击感。
第106~ 108 行:关闭裁剪,以调色板的windowText().color()颜色画出整个圆角矩形。这么做的目的是利用palette.windowText().color()作为画笔颜色、画笔宽度为1,将圆角矩形的边框再绘制一遍,即改变最外面圆角矩形边框颜色,使其颜色一致。 因为整个过程都是用的同一种颜色的画刷,所以本次画出整个圆角矩形,不会将图10、图11画出的部分覆盖。
4.1.6.drawControl函数
该函数中的CE_PushButtonLabel处于QPushButton层次结构树的位置参见
《QStyle类用法总结(三)》博文2.2.1节描述。
从Qt Assist得知:

CE_PushButtonLabel其实就是按钮上的文本子控件即QLabel,如果该QLabel有icon或pixmap,则还包括icon或pixmap。程序能进入到CE_PushButtonLabel所在的case语句,则窗体部件必定是QPushButton、QCheckBox、QRadioButton类型中某种按钮。接下来将option通过qstyleoption_cast强制转换为QStyleOptionButton,因为此时的窗体部件肯定是一个按钮类型,这种转化肯定能成功。接下来判断按钮是否处于禁用状态,如果不是禁用状态,且按钮被按下(此时是QPushButton类型按钮)或被选中状态(此时是QCheckBox或QRadioButton),则将按钮前景色即按钮文本颜色设置为明亮的对比度颜色,一般为白色。
4.2 main.cpp
要使自定义风格样式起作用,必须在main函数QApplication对象之前加入下句代码:
QApplication::setStyle(new NorwegianWoodStyle);边栏推荐
- 【云享新鲜】社区周刊·Vol.68-华为云招募工业智能领域合作伙伴,强力扶持+商业变现
- iMeta:高颜值绘图网站imageGP+视频教程合集,已被引360次(220625更新)
- 杰理之串口通信 串口接收IO需要设置数字功能【篇】
- Eureka core source code analysis
- Co jump
- 【TcaplusDB知识库】TcaplusDB Tmonitor模块架构介绍
- Codeforces Round #786 (Div. 3) ABCDE
- If you find any loopholes later, don't tell China!
- 【值得收藏】Centos7 安装mysql完整操作命令
- Oracle-多表查询
猜你喜欢

飞桨产业级开源模型库:加速企业AI任务开发与应用
![[tcapulusdb knowledge base] Introduction to tmonitor stand-alone installation guidelines (I)](/img/74/a645742a8e135b32154859be956760.png)
[tcapulusdb knowledge base] Introduction to tmonitor stand-alone installation guidelines (I)

Design and Simulation of direct torque control system for induction motor (motion control matlab/simulink)

Glide caching mechanism

杰理之DAC输出方式设置【篇】

Microsoft cloud technology overview

Feedforward feedback control system design (process control course design matlab/simulink)

面试突击60:什么情况会导致 MySQL 索引失效?

记一次 .NET 某物管后台服务 卡死分析

直播电子商务应用程序开发需要什么基本功能?未来发展前景如何?
随机推荐
嵌入式软件架构设计-模块化
记一次 .NET 某物管后台服务 卡死分析
Oracle group statistics query
Cross cluster deployment of helm applications using karmada [cloud native open source]
[tcapulusdb knowledge base] Introduction to new models of tcapulusdb
飞桨产业级开源模型库:加速企业AI任务开发与应用
【TcaplusDB知识库】TcaplusDB数据导入介绍
居家办公竟比去公司上班还累? | 社区征文
Code for structural design of proe/creo household appliances - electric frying pan
中科院微生物所招聘青年PI 20比特,2百萬安家費,千萬啟動經費(長期有效)
Deep understanding of happens before principle
Learning notes - data set generation
Deep learning in finance in cross sectional sectional predictions for random forests
go-zero微服务实战系列(七、请求量这么高该如何优化)
What basic functions are required for live e-commerce application development? What is the future development prospect?
[tcapulusdb knowledge base] Introduction to tmonitor background one click installation (II)
Analysis of mobile ar implementation based on edge computing (Part 2)
机器学习系统在生产中的挑战
Working at home is more tiring than going to work at the company| Community essay solicitation
堆-堆排序-TopK