【Qt源码笔记】浅谈 Qt 中的控件绘制

2021-07-27 10:12:12 浏览数 (1)

在工作中经常要根据 UI 提供的稿子做自绘控件,而且在新项目中,我自己基于 Qt 做了一套项目自用控件库,还会涉及到换肤,所以对 Qt 的控件绘制,着重的研究了一下。看过代码之后,觉得 Qt 项目本身,确实是做 UI 发家,绘制流程清晰,可以说是做界面的范本了。将流程梳理记录一下。

分类

对于自绘控件的分类,我将其分为两大类:

  • 基于 Qt 控件类派生
  • 基于 QWidget 派生

除非是行为跟 Qt 本身的控件相差太多,或者是缺少需要的交互逻辑,否则的话尽可能从 Qt 现有的控件类派生,这是一条基本的原则。因为绘制上来说重写 paintEvent 方法,基本就可以达到想要的目的,而控件类本身会提供一些现成的控件逻辑,这样会大大减少开发难度。

绘制流程

其实提起绘制流程来说,无非就是重写 paintEvent 方法。但是如果要做一整套 UI 库,没有结构,都在 paintEvent 里边写死,在后期加换肤,或者是在代码整洁度上都会大打折扣。 Qt 本身的控件绘制,就可以给我们很大的启示。以绘制事件方法代码最简单的 QPushbutton 为例,足见一斑:

代码语言:javascript复制
void QPushButton::paintEvent(QPaintEvent *)
{
    QStylePainter p(this);
    QStyleOptionButton option;
    initStyleOption(&option);
    p.drawControl(QStyle::CE_PushButton, option);
}

可以简单理解为两个流程:

  1. 装配绘制必要上下文
  2. 绘制

Qt 考虑到对于按钮样式,开发者有着自己的需求,而对于逻辑状态可能开发者可以用现成的。所以 initStyleOption 是一个保护方法,如果开发者没有对按钮状态的特殊要求,用这个方法,就可以把图标,按钮的点击状态什么的放进这个 option 中。在绘制的时候直接拿来用。

而下边的绘制,可以看到这里是用了 QStylePainter 而不是常规的 QPainter。不过 QStylePainterQPainter 差别不大,前者只是在里边保存了当前 QWidget::style()的指针。所以这段代码改成:

代码语言:javascript复制
QPainter p(this);
QStyleOptionButton option;
initStyleOption(&option);
style()->drawControl(QStyle::CE_PushButton, option, &p, this);

也是一样的。如果你翻看其他控件,比方说 QMenu 就是下边这种用法,可能是开发人员的不同习惯,但是他们逻辑是一致的。

Qt 的绘制精髓就在于此。对于绘制, Qt 控件是交给 style 来管理的。打开源码路径 qtbasesrcwidgetsstyles 这里边保存了 Qt 绘制的基本样式。为什么说是基本样式呢,因为还有一部分 qtbasesrcpluginsstyles 在这个目录下,这所有的加一起就是全部样式了。话说回来, Qt 正是因为将绘制逻辑都保存在了 style 中,所以 Qt 才可以在不同平台都表现的像一个原生控件一样,“千平台千面”。

走到这里,就不得不一探 QStyle 的究竟了。QStyle 是一个抽象类,头文件因为包含了太多枚举,所以特别长,就不粘了。里边关于绘制的几个方法是纯虚的,在绘制方法中,通过传进去的枚举类型,来找到对应控件的绘制逻辑。除了绘制方法,还有一些是计算绘制区域大小的方法,以及 hitTest 获取子控件的方法。这就是整个 Qt 绘制的大本营了。而对于 Qt 控件的实际绘制逻辑,其实也对做自绘控件库也十分有帮助,不过既然是浅谈,也就不再罗嗦。

0 人点赞