第五章 创建自定义窗口部件
这一章讲解如何使用Qt开发自定义窗口部件。通过对-一个已经存在的Qt窗口部件进行子类化或者直接对QWidget进行子类化,就可以创建自定义窗口部件。本章将示范这两种方式,并且也会说明如何把自定义窗口部件集成到Qt设计师中,这样就可以像使用内置的Qt窗口部件一样来使用它们了。最后,将通过展示一个使用了双缓冲技术(一种用于快速绘制的强大技术)的自定义窗口部件来结束这一章的内容。
5.1 自定义Qt窗口部件
在某些情况下,我们发现Qt窗口部件需要更多的自定义定制,这些定制可能要比它在Qt设计师里可设置的属性或者对它调用的那些函数更多一些。一个简单而直接的解决方法就是对相关的窗口部件类进行子类化并且使它能够满足我们的需要。
这一节将开发一个十六进制微调框,以说明是如何完成这一工作的。QSpinBox 一般只支持十进制整数,但是通过子类化方法,可以让它非常容易地接受并且显示十六进制数值。
//hexspinbox.h
#ifndef HEXSPINBOX_H
#define HEXSPINBOX_H
#include <QSpinBox>
class QRegExpValidator;
class HexSpinBox : public QSpinBox
{
Q_OBJECT
public:
HexSpinBox(QWidget *parent = 0);
protected:
QValidator::State validate(QString &text, int &pos) const;
int valueFromText(const QString &text) const;
QString textFromValue(int value) const;
private:
QRegExpValidator *validator;
};
//HexSpinBox从QSpinBox中继承了它的绝大多数功能,它提供了一个典型的构造函数,并且重新实现了QSpinBox中的三个虚函数。
#endif // HEXSPINBOX_H
//hexspinbox.cpp
#include <QtWidgets>
#include "hexspinbox.h"
HexSpinBox::HexSpinBox(QWidget *parent)
: QSpinBox(parent)
{
/* 我们设置的默认范围是从0到255(即从0x00到0xFF)。对于十六进制微调框来说,这样的设置比QSpinBox的默认设置值范围(从0到99)更为合适些。
* 用户可以通过单击微调框的向上和向下箭头或者在它的行编辑器中输人数值来修改微调框的当前值。
* 在后一种情况中,我们想要严格控制用户输人的数据必须是合法的十六进制数字。
* 为了实现这一点,我们使用一个QRegExpValidator,它可以接受1到8个字符,所有这些字符都必须是集合{'0',...,'9','A',.,'F','a',...,'f'}中的一个。
*/
setRange(0, 255);
validator = new QRegExpValidator(QRegExp("[0-9A-Fa-f]{1,8}"), this);
}
QValidator::State HexSpinBox::validate(QString &text, int &pos) const
{
/* 这个函数由QSpinBox 调用,用来检查目前为止用户输人文本的合法性。
* 有三种结果可能会出现:Invalid(无效,输人的文本与常规表达式不匹配)、
* Intermnediate(部分有效部分无效,输入的文本是一个有效值中似是而非的一部分)以及Acceptable(可以接受输入的文本合法有效)。
* QRegExpValidator有一个合适的validate()函数,因此只需返回对其调用后的最终结果即可。
* 理论上,应当为位于微调框范围之外的那些值返回Invalid或者Intermediate, 但是QSpinBox具有很好的自适应性,它可以在没有任何帮助的情况下检测出那种情况。
*/
return validator->validate(text, pos);
}
QString HexSpinBox::textFromValue(int value) const
{
/* textFromValue()函数把一个整数值转换成一个字符串。
* 当用户按下微调框的向上或者向下箭头时,QSpinBox会调用它来更新微调框的编辑器部分。
* 我们使用静态函数QString::number(),将其第二个参数设置为16,把该值转换为小写格式的十六进制字符串,并且对结果调用QString::toUpper(),使其成为大写格式的结果。
*/
return QString::number(value, 16).toUpper();
}
int HexSpinBox::valueFromText(const QString &text) const
{
/* valueFromText()函数执行从字符串到整数值的逆向转换。
* 当用户在微调框的编辑器部分输人一个值并且按下Enter时,QSpinBox就会调用它。
* 我们使用QString::toInt()试着把当前文本转换成一个整数值,当然还是使用16作为基数。
* 如果这个字符串不是有效的十六进制数,那么就把ok设置为false,并且由toInt()返回一个0。
* 在这里,不需要考虑这种可能出现的情况,因为此处的验证器只允许输人有效的十六进制字符串。
* 我们希望能够给toInt()的第一个参数传递的是一个空指针,而不是传递一个虚拟变量(ok)的地址。
*/
bool ok;
return text.toInt(&ok, 16);
}
现在已经完成了这个十六进制微调框。自定义其他Qt窗口部件也可以遵循相同的模式:选择一个合适的Qt窗口部件,对它进行子类化,并且通过重新实现一些虚函数来改变它的行为即可。如果我们想做的全部就是对一个已经存在的窗口部件的外观进行自定义设置,那么只需对其应用一个样式表或者重新实现- -种自定义风格即可,而不必对其进行子类化,就像第19章中说明的那样。
5.2 子类化QWidget
许多自定义窗口部件都是对现有窗口部件的简单组合,不论它们是内置的Qt窗口部件,还是其他一些像HexSpinBox这样的自定义窗口部件。通过对现有窗口部件的组合构建而成的自定义窗口部件通常都可以在Qt设计师中开发出来:
● 使用 “Widget” 模板创建一个新窗体。
● 把一些必需的窗口部件添加到这个窗体上,并且对它们进行摆放。
● 设置一些信号和槽的连接。
● 如果通过信号和槽不能获得所需的行为,则只需在类中添加一些必要的代码即可——这个类需要同时从QWidget类和uic生成的类中派生出来。
当然,要对这些现有窗口部件进行组合,也完全可以通过手写代码方式来加以实现。但无论使用的是哪种方式,最终生成的类都会是QWidget类的一个子类。
如果窗口部件本身没有任何信号和槽,并且它也没有重新实现任何虛函数,那么我们甚至还是有可能通过对现有窗口部件的组合而不是通过子类化的方式来生成这样的窗口部件。这就是在第1章创建Age应用程序时所使用的方法,其中用到了一个QWidget、一个QSpinBox以及一个
QSlider。虽然如此,也还是可以很容易地通过子类化QWidget,并且在它的子类构造函数中创建QSpinBox和QSlider的方式来做到这一点。
当手里没有任何一个Qt窗口部件能够满足任务要求,并且也没有办法通过组合现有窗口部件来满足所需的期望结果时,仍旧可以创建出我们想要的窗口部件来。要实现这一点,只需通过子类化QWidget,并且通过重新实现一些用来绘制窗口部件和响应鼠标点击的事件处理器即可。这一方法给了我们定义并且控制自己的窗口部件的外观和行为的完全自由。Qt 的一些内置窗口部件,像QLabel、QPushButton和QTableWidget,都是通过这种方法得以重新实现的。如果它们没有在Qt中存在,那么还是完