Qt样式表的底层实现原理

26 篇文章 3 订阅

写在前面

在Qt开发中,样式表(StyleSheet)是一个非常强大的工具,它允许我们通过类似于CSS的语法来定制控件的外观。然而,很多开发者对Qt样式表的底层实现原理并不清楚。本文将详细介绍Qt样式表的内部实现机制,并探讨在自定义控件中如何应用样式表。

Qt样式表底层实现细节

  1. 样式表解析器:
    Qt有一个内置的样式表解析器,负责解析样式表字符串。这个解析器将样式表中的规则转换为内部表示,以便应用到相应的控件上。

  2. 属性系统:
    Qt的控件有一个属性系统,用于存储和管理控件的各种属性,如颜色、字体、边框等。样式表中的规则会修改这些属性的值。

  3. 渲染引擎:
    Qt的渲染引擎负责根据控件的属性绘制控件的外观。当控件的属性发生变化时,渲染引擎会重新绘制控件。

  4. 继承和层叠:
    Qt样式表支持继承和层叠的概念。子控件可以继承父控件的样式,同时可以被更具体的样式覆盖。这与CSS中的继承和层叠规则类似。

  5. 选择器:
    Qt样式表使用选择器来确定哪些控件应用哪些样式规则。选择器可以基于控件的类型、对象名、类名等属性。

  6. 伪状态和伪类:
    Qt支持伪状态(如:hover、:enabled、:disabled)和伪类(如:checked、:selected),允许开发者根据控件的状态动态地改变样式。

  7. 动画和过渡:
    Qt样式表支持动画和过渡效果,允许开发者定义控件状态变化时的动画效果。这通过内部的动画框架实现。

  8. 子控件和子对象:
    Qt样式表可以应用于控件的子控件和子对象。开发者可以通过选择器定位到特定的子控件,并为其定义样式。

  9. 样式表缓存:
    为了提高性能,Qt会缓存样式表的解析结果。当样式表发生变化时,缓存会被更新,确保样式的一致性和响应性。

  10. 样式表应用:
    当应用样式表时,Qt会遍历控件树,应用匹配的样式规则。这个过程是递归的,确保所有相关的控件都应用了正确的样式。

  11. 样式表覆盖:
    Qt允许开发者通过不同的方式覆盖样式表。例如,可以在运行时动态地修改样式表字符串,或者通过编程方式设置控件的属性。

通过这些机制,Qt的样式表系统提供了一种灵活且强大的方式来自定义应用程序的外观。开发者可以利用样式表来实现复杂的视觉效果,同时保持代码的清晰和可维护性。

注意:对于前9点,Qt对外是封闭不可见的,只能通过部分接口获取少量的样式信息。

setStyleSheet

通过setStyleSheet()设置样式的内部实现机制实际上是复用了QStyle子类。

QStyle是所有Qt控件样式类的基类,Qt自身实现了一些QStyle的子类,比如QWindowsStyle,QMacStyle等。当我们调用setStyleSheet()时,Qt实际是通过QStyleSheetStyle这个QStyle的子类实现的,这个子类在QStyle中并没有暴露出来,是Qt内部使用的。

当你为一个QWidget调用setStyleSheet()时,这会创建一个QStyleSheetStyle的实例(如果已经有了一个实例,它就会被复用)。然后,这个QStyleSheetStyle对象会解析你给出的样式,并应用到对应的子部件中。QStyleSheetStyle对象在解析样式规则时,会将其转化为一种声明解析树(DRT, Declaration Rule Tree),即将比如"QPushButton { color: red }"的规则解析成为规则树的形式保存,之后便通过这个规则树来进行样式的匹配。

在QWidget的paintEvent方法中,会进一步调用QStyle的draw*****()方法来实现具体的绘制。如果样式表中包含这个部件的样式,那么在这里就会使用QStyleSheetStyle绘制这个部件,否则使用QCommonStyle来进行绘制。

Qt是如何应用样式表的

在QWidget::setStyleSheet()这类函数在内部是通过创造一个称为QStyleSheetStyle的QStyle子类实例,通过这个实例解析并应用提供的样式表。

当部件开始计算如何被渲染时,它首先会检查自身是否有设置过样式表。如果有,样式表就会被应用上。如果没有,它会逐级向上查找父部件,直到找到设置了样式表的部件或者整个应用程序,并从那里获取样式进行渲染。

可以理解成设置的样式,都会在绘画函数中应用。

Qt中的样式表是在部件的绘画(painting)函数中应用的。当部件需要被渲染或者重绘时,例如在创建、状态改变或者显式调用更新函数时,部件会调用其绘画函数。这个函数调用了QStyle的几个方法如drawControl()drawComplexControl()drawPrimitive()等来实际进行绘制工作。

在这个过程中,如果为部件设置了样式表,那么用于解析和应用样式表的QStyleSheetStyle就会被调用,而QStyleSheetStyle就会根据样式表的规则和部件的当前状态来决定如何绘制部件,例如使用什么颜色、边框、背景和文字等。

所以,可以理解为设置的样式都会在部件的绘画函数中被应用。

但也要注意,这是一个动态的过程,样式表允许你定义部件在不同状态下的外观,例如鼠标悬停和点击等,当部件的状态改变时,样式也会随之改变。

动态状态处理

Qt中的样式表允许你定义部件在不同状态下的外观,例如鼠标悬停和点击等。

当部件的状态改变时,样式也会随之改变。

部件的外观是由其状态驱动的,这意味着当部件的状态改变时,例如:鼠标悬停、获取焦点、被点击等,部件的外观也会随之改变。

在update()或repaint()函数来进行。当这个函数被调用时,Qt会安排在下一个可用的时刻重新调用部件的paintEvent()函数,也就是重新绘制部件。此时,如果部件有设置样式表,那么样式表就会根据部件的新状态来决定部件新的外观。

自定义控件的样式表绘制

在自定义控件的绘画函数中应用设置的样式需要以下几个步骤:

  1. 创建QStyleOption对象:这个对象包含了控件的当前样式属性。
  2. 创建QStyleOption对象:这个对象包含了控件的当前样式属性。
  3. 初始化QStyleOption对象:用控件的位置、大小、状态等信息填充QStyleOption对象。
  4. 创建QPainter对象:绑定到当前控件。
  5. 调用QStyle的draw()函数*:进行绘制工作。

下面是一个应用样式表的自定义QPushButton的简单示例:

void CustomButton::paintEvent(QPaintEvent *) {
    QStyleOptionButton option;
    initStyleOption(&option);

    QPainter painter(this);
    style()->drawControl(QStyle::CE_PushButton, &option, &painter, this);
}

扩展

既然设置的样式都会在绘画函数中进行应用,如果我的自定义控件重新实现了绘画函数而没有应用设置的样式,这样会导致通过setStyleSheet设置的样式无法生效吗?

答案是肯定的。

在Qt中,如果你为自定义控件重写了绘制(paint)函数,并且在这个函数中没有处理或应用样式表,那么会导致用setStyleSheet()设置的样式无法生效。

setStyleSheet()设置的样式是在Qt的默认绘制过程中应用的,特别是在控件的paintEvent()函数中,通过调用QStyle的各种draw函数(如drawControl(), drawComplexControl(), drawPrimitive()等)来引入的。这个过程中,将会考察和应用控件当前的样式表。

如果重写了控件的paintEvent()函数并且直接使用了QPainter来自定义绘制,那么这个过程就会被跳过,从而导致样式表规则没有应用到控件上。在这种情况下,要使样式表生效,就需要在自定义绘制代码中,考虑如何将样式表规则引入到绘制过程。

你可以在重写的paintEvent()函数中,先调用默认的paintEvent()处理,再添加你自定义的绘制代码。

这样可以保证你的自定义代码不会影响Qt的默认处理,包括样式表的应用。你还可以通过QStyleOption来获取控件应用了样式表后的各项属性,如颜色、尺寸、边框等,然后在自定义绘制中应用这些属性。

总的来说,如果你希望在自定义控件中使用Qt的样式表系统,需要在你的绘制过程中考虑和处理样式表的规则。

例如这里自定义一个MPushButton, 重写paintEvent自定义绘制,同时创建对象时设置样式表,看看会有什么效果。

在不重写paintEvent时设置样式表:

	QString qsBtnCSS = "QPushButton{font-family: Microsoft YaHei; font-size: 14px; font-weight: normal; color: #333333; border: 1px 					   solid #DBDBDB;border-radius: 4px;text-align: center; padding-left: 0px;background: #FAFBFC; }"
		"QPushButton:hover{ background: #EBEBEB;  }"
		"QPushButton:pressed{background: #EBEBEB;  }"
		"QPushButton:disabled{background: #EBEBEB;  }";
	btn->setStyleSheet(qsBtnCSS);

效果如下:
1
2

设置样式表,且重写paintEvent自定义绘制

void MPushButton::paintEvent(QPaintEvent* event)
{
	QStylePainter painter(this);

	// 初始化 QStyleOptionButton
	QStyleOptionButton option;
	initStyleOption(&option);
	
	//自己绘制文本,且忽略其他样式表设置
	QFontMetrics fm = painter.fontMetrics();
	QRect contentRect = style()->subElementRect(QStyle::SE_PushButtonContents, &option, this);
	painter.drawText(contentRect.x(), contentRect.y(), fm.width(option.text), fm.height(), Qt::AlignCenter, option.text);
}

重写paintEvent中没有对样式表进行处理,就会使前面设置的样式表失效:
3

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值