让你的 Qt 桌面程序看上去更加 native(三):自定义 style

简介:
前面我们一再强调,Qt 使用自己的方式绘制组件。然而我们也看到,在不同的平台上,Qt 的组件表现也不相同。这和 Swing 有些类似:Swing 使用 look and feel 表现组件的外观,Qt 也是类似的。用来绘制组件外观的类就是 QStyle。
 
需要说明一点,组件的 style 是一个非常复杂的内容,仅在这里不可能全部讲解清楚。如果需要,还是要自己仔细阅读相关文档。另外,这部分牵扯的类很多,函数也很复杂,步步为营才是最好的对待方法。除非非常必要,还是建议不要轻易去碰 style 这部分。
 
好了,说明也说明过了,吓唬也吓唬过了,下面进入正题。
 
自定义 style,顾名思义,也就是自己实现外观。这里通常有两种实现方式:第一,重写 widget 的 paintEvent() 函数;第二,使用 QStyle 类。两种方式的侧重点不同:重写组件的 paintEvent() 函数,可以简单地实现某一类组件的样式,而继承 QStyle 类,则可以实现对全部组件一致性处理,例如,将程序中所有的 text 变成红色等。
 
首先我们来看看重写 paintEvent() 函数。paintEvent() 是 QWidget 的一个函数,用于实现自身的绘制。一个组件显示到屏幕上,就是通过调用 paintEvent() 函数。看看一个组件有多复杂,全部要使用 QPainter 提供的画点、画线的函数实现,就知道这里的工作量了。当然也有偷懒的办法,就是重写 paintEvent() 的时候使用一张图片代替。我们这里就不讨论这种思路了,完全从代码开始。
 
我们以 QPushButton 为例。这里,我们创建一个 button,这个 button 在点击时可以凹下显示。为了重写 paintEvent() 函数,我们必须继承 QPushButton 类。头文件很简单,暂且略去,下面只看 paintEvent() 这个函数:
 
  1. void MyPushButton::paintEvent(QPaintEvent *) 
  2.     QStyleOptionButton option; 
  3.     option.initFrom(this); 
  4.     qDebug() << option.state; 
  5.     option.state |= isDown() ? QStyle::State_Sunken : QStyle::State_Raised; 
  6.     qDebug() << option.state; 
  7.     if (isDefault()) 
  8.         option.features |= QStyleOptionButton::DefaultButton; 
  9.     option.text = text(); 
  10.     option.icon = icon(); 
  11.  
  12.     QPainter painter(this); 
  13.     style()->drawControl(QStyle::CE_PushButton, &option, &painter, this); 
尽管前面说过,我们需要重头绘制整个组件,但实际上,Qt 为我们提供了一系列方便的函数,用于绘制出各个组件。这种在将组建组合的时候非常有用。例如,一个 combo box 实际上是一个 button 加上一个向下的三角形构成。那么,我不需要将整个 combo box 用像素画出来,而是借用 Qt 已有的组建绘制,画出一个 button 和一个三角形就可以了。所以,这里我们也使用类似的思路,让 Qt 绘制出组件,我们要做的就是修改参数,让它按照我们的参数绘制。
 
如果调用 Qt 的组件绘制函数呢?这个绘制函数是 QStyle 类的成员。QWidget 提供了 style() 函数,返回当前的 QStyle 对象。那么,我们就可以通过这个对象绘制。注意上面代码中最后一行,我们从这里看起。下面给出这个函数的签名:
 
  1. virtual void QStyle::drawControl ( ControlElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget = 0 ) const = 0; 
尽管这是一个纯虚函数,但是类似于 Java 的 interface,我们可以直接使用 style() 返回的对象调用。这是一个很典型的 style 式的函数调用。翻看一下 QStyle 的定义,QStyle 类提供了很多以 draw 打头的函数,用于绘制整个系统组件的绘制。这类 draw 函数一般会有四个参数:
  • 第一个是一个 enum,用于指定要绘制哪个元素。这个 enum 在不同的 draw 函数中可能是不一样的。例如,在 drawControl() 中是 QStyle::ControlElement,指的是组件;在 drawPrimitive() 中则是 QStyle::PrimitiveElement,指的是组件的原始组成元素,例如焦点框,check box 的小勾等;
  • 第二个是 QStyleOption 对象指针。这个对象保存了 painter 绘制时所需要的所有数据信息,比如绘制大小、坐标、绘制文本等。不同的 element 可能对应着不同的 QStyleOption 的子类,这个在文档中可以找到;
  • 第三个是 QPainter 对象指针。系统即用这个 painter 进行绘制;
  • 第四个是 QWidget 对象指针,用于辅助绘制。
回到代码,我们可以看到,在 drawControl() 函数的四个参数中,只有最后一个有默认值。也就是说,如果要调用这个函数,我们必须准备好参数数据。这就是在 paintEvent() 中,前面几行代码做在的工作。
 
通过文档我们查到,QPushButton 需要的是 QStyleOptionButton 作为第二个参数。于是,我们新建一个 QStyleOptionButton 对象。初始化调用 initFrom(),也就是使用本对象设置一个初始值。QStyleOption 有很多属性。比如 QStyleOption::state 指的是当前状态。例如,如果 button 被按下,也就是 isDown() 返回 true 的时候,我们将 state 设置为 QStyle::State_Sunken,也就是凹下,否则则是 QStyle::State_Raised。这样,我们就完成了设置。另外,还要根据需要设置别的属性,例如,如果 isDefault() 返回 true 时,我们需要设置 option.features,这样才能绘制出默认的效果。text 和 icon 属性则是通过 button 自身函数获得。这样,我们完成对绘制数据的设置,就可以调用 QStyle::drawControl() 函数,将这个 button 绘制出来。
 
这里注意一点是,对于 QFlags 对象,使用 = 赋值很可能不是你所期望的结果。QFlags 实现的是 bitmap 位图,如果简单的使用 = 赋值,在赋值的同时会清楚原有位的值。你可以将上面的 option.state |= isDown() ? QStyle::State_Sunken : QStyle::State_Raised; 修改为 option.state = isDown() ? QStyle::State_Sunken : QStyle::State_Raised;,注意比较下前后两个 debug 输出的不同。
 
调用 QStyle::drawControl() 函数时,第一个参数可以通过文档查到。这里的 CE_ 前缀实际就是 ControlElement 的意思。
 
这样,我们就完成了一个简单的自定义 button。代码虽然简单,大体流程已经表现出来,剩下的就是去翻阅大量文档,仔细了解各个 draw 函数的使用,才能够做出满意的自定义组件效果。
 
前面说的第一种自定义组件实现就简单说到这里。然后看看第二种,QStyle 的实现。其实在上面,我们已经使用了 QStyle。想必也能够想到,这里我们依旧要用到 QStyle 的各个 draw 函数,只不过这里我们不是简单的去调用它们,而是通过继承,将这些 draw 函数替换成我们自己的版本,达到自定义样式的目的。
 
虽然我们可以直接继承 QStyle 来实现,但是这并不是一个好主意。因为 QStyle 这个类很复杂,几乎所有的函数都是纯虚函数,这要求我们必须一个个实现它们。有时候,我们并不需要自己实现所有功能,仅仅是做简单的修改。于是,从 4.6 版本开始,Qt 提供了一个专门的类,QProxyStyle。我们要做的就是继承 QProxyStyle,覆盖我们感兴趣的函数即可。看下面一个简单的实例:
 
  1. class MyProxyStyle : public QProxyStyle 
  2.   public
  3.     void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
  4. }; 
  5.  
  6. void MyProxyStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const 
  7.     if(element == QStyle::CE_PushButtonLabel) { 
  8.         painter->drawText(option->rect, "fixed"); 
  9.     } else { 
  10.         QProxyStyle::drawControl(element, option, painter, widget); 
  11.     } 
MyProxyStyle 覆盖了 drawControl() 函数,然后判断,如果是 button label 的话,绘制文本 “fixed”。可想而知,我们的 QPushButton::setText() 函数已经没有作用了,因为我们在绘制时没有使用这个属性,也就不会显示出来了。不管你设不设置,所有 button 的 text 都会是 fixed。如果要使用这个 style ,需要在运行前设置,例如:
 
  1. int main(int argc, char **argv) 
  2.     QApplication app(argc, argv); 
  3.     app.setStyle(new MyProxyStyle); 
  4.     MainWindow w; 
  5.     w.show(); 
  6.     return app.exec(); 
这样,我们就可以用我们自己的 style 显示组件了。
 
就像前面所说,自定义 style 是一个相当复杂的话题,我们不可能在这里完全说明。不过,也正因为 Qt 提供了这种机制,也能够让我们可以比较轻松地实现自定义 style。


本文转自 FinderCheng 51CTO博客,原文链接: 
http://blog.51cto.com/devbean/471941
相关文章
|
2月前
(8)Qt中的自定义信号
本文介绍了如何在Qt框架中创建和使用自定义信号,并通过一个父子窗口切换的示例来展示自定义信号的实现和应用。
110 3
|
5月前
|
Linux iOS开发 开发者
Qt问题(二):无法定位程序输入点于动态链接库
动态链接库(Dynamic Link Library,简称DLL)是一种可执行文件格式,常见于Windows操作系统中,而在Linux和macOS等其他操作系统中,相似的概念通常被称为共享库(Shared Library)。动态链接库允许程序在运行时加载所需的代码和数据,而不是在编译时静态链接到应用程序中。这种方式带来了几个重要的优点:
529 3
|
2月前
(7)Qt中的自定义槽(函数)
这篇文章介绍了在Qt中如何定义和使用自定义槽函数,包括类成员函数、静态类成员函数、全局函数和lambda表达式作为槽函数的示例,以及使用lambda表达式时的注意事项。
73 2
|
3月前
|
C语言 Android开发 C++
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
本文介绍了使用MTuner软件进行Qt MinGW编译程序的内存泄漏检测的方法,提供了MTuner的下载链接和测试代码示例,并通过将Debug程序拖入MTuner来定位内存泄漏问题。
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
|
4月前
Qt Style And Style Sheet
Qt Style And Style Sheet
41 1
|
4月前
|
搜索推荐 C++
【Qt 学习笔记】Qt窗口 | 对话框 | 创建自定义对话框
【Qt 学习笔记】Qt窗口 | 对话框 | 创建自定义对话框
168 4
|
4月前
【qt】自定义对话框(2)
【qt】自定义对话框(2)
35 0
|
4月前
【qt】自定义对话框(1)
【qt】自定义对话框(1)
47 0
|
5月前
|
调度
【浅入浅出】Qt多线程机制解析:提升程序响应性与并发处理能力
在学习QT线程的时候我们首先要知道的是QT的主线程,也叫GUI线程,意如其名,也就是我们程序的最主要的一个线程,主要负责初始化界面并监听事件循环,并根据事件处理做出界面上的反馈。但是当我们只限于在一个主线程上书写逻辑时碰到了需要一直等待的事件该怎么办?它的加载必定会带着主界面的卡顿,这时候我们就要去使用多线程。
180 6