文件:
- tutorials/tutorial/t7/lcdrange.cpp
- tutorials/tutorial/t7/lcdrange.h
- tutorials/tutorial/t7/main.cpp
- tutorials/tutorial/t7/t7.pro
这个例子显示了如何用信号和槽来创建自定义的窗口部件,以及如何用更复杂的方法把它们连接起来。首先,源文件被分开放到多个文件中并放在教程7目录中。
一行一行地品读
这个文件主要利用了第六章的main.cpp;这里只是说明一下非常规的改变。
#ifndef LCDRANGE_H
#define LCDRANGE_H
这里,包括#endif在文件的末尾,是标准的C++构造函数来避免某个头文件被包含了多次的错误。如果你还没有用过它(就使用它吧),这是一个程序开发的好习惯。
#include <QWidget>
<QWidget>被包含了因为我们的LCDRange类继承QWidget。父类的头文件应该总是被包含——在前面的章节我们实际上是“投机取巧”的行为:我们让<QWidget>通过其他头文件间接的被包含。
class QSlider;
这里是另一个小伎俩,但是比前一个用的少的多。因为我们在类的界面中不需要QSlider,仅仅是在实现中,我们在头文件中使用一个类的前置声明,并且在.cpp文件中包含QSlider的头文件。
这会使编译一个大的工程变的快很多,因为编译器通常花费大多数时间去解析头文件,而不是实际的源代码。仅这个小伎俩单独就能将编译的速度加快两倍或者两倍以上。
注意Q_OBJECT。这个宏必须被包含在所有包含信号和/或槽的类中。如果你很好奇,(你会发现)它定义了在元对象文件中实现的函数。
int value() const;
public slots:
void setValue(int value);
signals:
void valueChanged(int newValue);
这三个成员函数构成了这个窗口部件和程序中其它组件的接口。直到现在,LCDRange还根本没有一个真正的API。
value()是一个可以访问LCDRange的值的公共函数。setValue()是我们第一个自定义槽, valueChanged()是我们第一个自定义信号。
槽必须按通常的方式实现(记住槽也是一个C++成员函数)。信号可以在元对象文件中自动实现。信号也遵守C++函数的访问保护法则(比如,它们只能被那些自己定义的或者继承来类所发射)。
valueChanged()信号在LCDRange的值发生变化时就会被使用。
t7/lcdrange.cpp
这个文件主要利用了第六章的main.cpp,这里只是说明一下有哪些改变。
connect(slider, SIGNAL(valueChanged(int)),
lcd, SLOT(display(int)));
connect(slider, SIGNAL(valueChanged(int)),
this, SIGNAL(valueChanged(int)));
这个代码来自LCDRange的构造函数。
第一个connect和你在上一章中看到的一样。第二个是新的,它把滑块的valueChanged()信号和这个对象的valueChanged()信号连接起来了。是的,这是正确的。信号可以被连接到其它的信号。当第一个信号被发射时,第二个信号也被发射。
让我们来看看当用户操作这个滑块的时候都发生了些什么。滑块看到自己的值发生了改变,发射valueChanged()信号。这个信号被连接到QLCDNumber的display()槽和LCDRange的valueChanged()信号。
因此,当这个信号被发射的时候,LCDRange发射它自己的valueChanged()信号。另外,QLCDNumber::display()被调用并显示新数字。
注意你并没有保证任何执行的具体顺序;LCDRange::valueChanged()也许在QLCDNumber::display()被调用之前或者之后被发射。
int LCDRange::value() const
{
return slider->value();
}
value()的实现是直接了当的,它简单地返回滑块的值。
void LCDRange::setValue(int value)
{
slider->setValue(value);
}
setValue()的实现也是直接了当的。注意因为滑块和LCD数字是连接的,设置滑块的值就会自动的改变LCD数字的值。另外,如果滑块的值超过了合法范围,它会自动调节。
LCDRange *previousRange = 0;
for (int row = 0; row < 3; ++row) {
for (int column = 0; column < 3; ++column) {
LCDRange *lcdRange = new LCDRange;
grid->addWidget(lcdRange, row, column);
if (previousRange)
connect(lcdRange, SIGNAL(valueChanged(int)),
previousRange, SLOT(setValue(int)));
previousRange = lcdRange;
}
}
main.cpp中除了MyWidget的构造函数外所有的部分都是从前一章复制的。当我们创建这9个LCDRange对象时,我们使用信号/槽机制来连接它们。每一个的valueChanged()信号都和前一个的setValue()槽连接起来了。因为LCDRange在它的值发生变化的时候发射valueChanged()信号,我们在这里建立了一个信号和槽的“链”。
编译
为一个多文件的应用程序创建一个makefile和为一个单文件的应用程序创建一个makefile是没有什么不同的。如果你已经把这个例子中的所有文件都保存到它们自己的目录中,你所要做的就是:
qmake -project
qmake
第一个命令告诉qmake去穿件一个.pro文件。第二个命令让它根据这个项目文件来生成一个(相关平台的)makefile。你现在应该可以键入make(或者nmake,如果你用的是Visual Studio)来建立你的应用程序。
运行这个应用程序
在开始的时候,这个程序看起来和前一章的一样。试着操作右下角的滑块。
练习
使用右下角的滑块将所有的LCD设置到50。然后通过点击上面一排的滑块将上面的6个设置为30。现在,你可以通过把最后一个调到左边来把前五个LCD设置回50。
点击右下角滑块的滑块的左边。发生了什么?为什么这是正确的行为?
注:原文见http://doc.trolltech.com/4.4/tutorials-tutorial-t7.html