正确使用Qt Style Sheet设置边框

为窗口或控件添加边框是经常需要用到的一个操作,通常可以使用两种方式,一是在代码中通过QPainter绘制边框,这种方式比较灵活,可以实现很多特殊效果,然而使用起来并不是很方便。另外一种是通过Qt Style Sheet(以下简称QSS),可以通过简单的几行代码实现。然而QSS本身存在很多需要特别注意的问题,稍不注意可能就无法达到想要的效果。本文探讨了一下QSS在不同情况下如何正确设置边框。

边框属性顺序

若要添加一个边框,一般需要设置边框的三个属性:宽度,线条样式和颜色。虽然看上去这个属性是互不相关,即正交的,然而,在实际使用时,三者的顺序决定了能否达到预期的效果。经过测试,三者必须严格保持宽度,线条样式和颜色的顺序进行设置才会有效,缺少任意一个属性或交换了任意两条属性的顺序,都会导致没有任何边框效果。至于原因,可能是因为这三条属性的设置在转换成代码后其实是有相互影响的。

不同情况下的边框设置

在实际使用过程中发现,影响边框设置效果成功与否一共有5个影响因素,分别是

  • 控件类型

    • QWidget及继承于QWidget的自定义控件
      以下简称*QWidget系控件*
    • QFrame及继承于QFrame的自定义控件
      以下简称*QFrame系控件*
  • 控件角色

    • 控件做主窗口时
    • 控件做子部件时
  • 边框类型

    • 单侧边框
    • 四周边框(全边框)
  • paintEvent函数提供与否

    • 提供paintEvent函数相关实现,实现如下:
    1
    2
    3
    4
    5
    6
    7
    void CustomWidget::paintEvent(QPaintEvent *)
    {
    QStyleOption opt;
    opt.init(this);
    QPainter p(this);
    style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
    }

    以下简称特定paintEvent实现

    • 不提供如上的paintEvent实现
  • 背景颜色属性

    • 在边框属性设置前设置背景颜色属性,如
    1
    2
    3
    4
    setStyleSheet("QWidget{"
    "background-color:#FFFFFF;" // 设置背景颜色属性
    "border:2px solid gray;"
    "}");

    以下简称正确设置背景颜色属性

    • 不设置背景颜色属性或未在边框属性设置前设置

下面将以控件类型和控件角色为分类标准一一介绍在正确提供边框设置的QSS语句条件下如何设置不同边框效果

作为主窗口的QWidget系控件

对于QWidget类,QSS官方文档上有这样一句话

Supports only the background, background-clip and background-origin properties.

也就是说,QWidget实际上是不支持盒子模型的,具体使用时,需要按一定规则重写void paintEvent(QPaintEvent *e)函数来实现对盒子模型的支持。因此理论上来说在未重写该函数时,我们是无法通过设置border相关属性来对一个QWidget来添加边框。在对于QWidget及继承于QWidget的自定义控件作为主窗口时均需要提供特定的paintEvent函数相关实现。

单侧边框

需要满足的条件为:

  • 提供了特定paintEvent实现

全边框

需要满足的条件为:

  • 提供了特定paintEvent实现
  • 正确设置背景颜色属性

作为子部件的QWidget系控件

虽然QWidget并不支持盒子模型,然而这一特性貌似仅在QWidget系控件作为主窗口时才有效,当QWidget系控件作为子部件时,无需任何条件即可实现任意边框效果

单侧边框/全边框

无需任何条件

作为主窗口的QFrame系控件

对于QFrame来说,其与QWidget在对QSS的支持上最大的区别在于QFrame支持盒子模型,因此,QFrame系控件无需提供特定paintEvent实现即可实现边框效果,然而其在作为主窗口时会受到背景颜色属性是否正确设置的影响。

单侧边框

需要满足的条件为:

  • 正确设置背景颜色属性

全边框

需要满足的条件为:

  • 正确设置背景颜色属性

作为子部件的QFrame系控件

QFrame系控件在作为子部件时,依然无需提供特定paintEvent实现即可实现边框效果,然而背景颜色属性是否正确设置的影响却根据边框类型而有不同的结果。

单侧边框

需要满足的条件为:

  • 正确设置背景颜色属性

全边框

无需任何条件

以上所有情况均在Qt 5.8.0 (GCC 5.3.1 (Deepin 15.4.1), 64 bit)环境下实际验证,不排除会受到Qt、编译器和操作系统的类型和版本的影响而产生不同的效果。

总结

综合所有情况,大致可以得出以下结论:

  1. 对于QWidget类控件,为了保证QSS的边框效果正常显示,除非我们能确保该控件一定不会作为一个主窗口来使用,否则请一定提供特定paintEvent实现。
  2. 对于QFrame类控件,为了更好的复用性、拓展性,务必在设置边框属性前设置背景颜色属性。
  3. 推荐使用QFrame作为主窗口的基类,而使用QWidget作为子部件的基类,如不确定,请尽量使用QFrame作为基类。

最后,本文仅仅验证了边框(border)属性,理论上paddingmargin将会和border会有一致的表现。具体各位读者可自行验证。另外,对于Qt提供的其他控件,如QPushButtonQLabel等控件可能存在其他情况,本文列举的情况并不一定适用,使用时需注意实际情况。