这个例子显示了如何使用信号和槽来创建自定义窗口部件,和如何使用更加复杂的方式把它们连接起来。
- lcdrange.h包含LCDRange类定义。
- lcdrange.cpp包含LCDRange类实现。
- main.cpp包含MyWidget和main。
一行一行地解说
lcdrange.h:
这个文件主要利用了第六章的main.cpp
,在这里只是说明一下改变了哪些。
#ifndef LCDRANGE_H
#define LCDRANGE_H
这里是一个经典的C语句,为了避免出现一个头文件被包含不止一次的情况。如果你没有使用过它,这是开发中的一个很好的习惯。#ifndef
需要把这个头文件的全部都包含进去。
#include <QWidget>
QWidget
被包含了。LCDRange
继承了QWidget
,所以父类的头文件必须被包含。我们在前几章里面偷了一点懒,我们通过包含其它一些头文件,比如Qpushbutton
,这样就可以间接地包含QWidget
。
class QSlider;
这里是另外一个小伎俩,但是没有前一个用的多。因为我们在类的界面中不需要QSlider
,仅仅是在实现中,我们在头文件中使用一个前置的类声明,并且在.cpp
文件中包含一个QSlider
的头文件。 (点这里详细说明)
这会使编译一个大的项目变得更快,因为当一个头文件改变的时候,很少的文件需要重新编译。它通常可以给大型编译加速两倍或两倍以上。
class LCDRange : public QWidget
{
Q_OBJECT
public:
explicit LCDRange(QWidget *parent = 0, Qt::WindowFlags name = Qt::WindowFlags());
meta object file. 注意Q_OBJECT。这个宏必须被包含到所有使用信号和/或槽的类。如果你很好奇,它定义了在元对象文件中实现的一些函数。
int value() const;
public slots:
void setValue(int);
signals:
void valueChanged(int);
这三个成员函数构成了这个窗口部件和程序中其它组件的接口。直到现在,LCDRange
根本没有一个真正的接口。
value()
是一个可以访问LCDRange
的值的公共函数。setValue()
是我们第一个自定义槽,并且valueChanged()
是我们第一个自定义信号。
槽必须按通常的方式实现(记住槽也是一个C++成员函数)。信号可以在元对象文件中自动实现。信号也遵守C++函数的保护法则(比如,一个类只能发射它自己定义的或者继承来的信号)。
当LCDRange
的值发生变化时,valueChanged()
信号就会被使用——你从这个名字中就可以猜到。这将不会是你将会看到的命名为somethingChanged()
的最后一个信号。
lcdrange.cpp:
这个文件主要利用了t6 main.cpp,在这里只是说明一下改变了哪些。
connect(slider, SIGNAL(valueChanged(int)), lcd, SLOT(display(int)));
connect(slider, SIGNAL(valueChanged(int)), SIGNAL(valueChanged(int)));
这个代码来自LCDRange
的构造函数。
第一个connect
和你在上一章中看到的一样。第二个是新的,它把滑块的valueChanged
信号和这个对象LCDRange
的valueChanged
信号连接起来了。带有三个参数的connect()
函数连接到this
对象的信号或槽。
是的,这是正确的。信号可以被连接到其它的信号。当第一个信号被发射时,第二个信号也被发射。
让我们来看看当用户操作这个滑块的时候都发生了些什么。滑块看到自己的值发生了改变,并发射了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数字的值。另外,如果滑块的值超过了合法范围,它会自动调节。
main.cpp :
LCDRange *previous = 0;
for(int r = 0 ; r < 4 ; r++)
{
for(int c = 0 ; c < 5 ; c++)
{
LCDRange* lcdRange = new LCDRange;
grid->addWidget(lcdRange, r, c);
if (previous)
{
connect(lcdRange, SIGNAL(valueChanged(int)), previous, SLOT(setValue(int)));
}
previous = lcdRange;
}
}
main.cpp
中所有的部分都是上一章复制的,除了MyWidget
的构造函数。当我们创建20个RCDRange
对象时,我们现在使用信号/槽机制连接它们。每一个的valueChanged()
信号都和前一个的setValue()
槽连接起来了。因为当LCDRange
的值发生改变的时候,发射一个valueChanged()
信号(惊奇!),我们在这里创建了一个信号和槽的“链”。
**lcdrange.h **
#ifndef LCDRANGE_H
#define LCDRANGE_H
#include <QWidget>
class QSlider;
class LCDRange : public QWidget
{
Q_OBJECT
public:
explicit LCDRange(QWidget *parent = 0, Qt::WindowFlags name = Qt::WindowFlags());
int value() const;
public slots:
void setValue(int);
signals:
void valueChanged(int);
private:
QSlider *slider;
};
#endif // LCDRANGE_H
lcdrange.cpp
#include "lcdrange.h"
#include <QLCDNumber>
#include <QSlider>
LCDRange::LCDRange(QWidget *parent, Qt::WindowFlags name)
: QWidget(parent, name)
{
QLCDNumber *lcd = new QLCDNumber(2);
slider = new QSlider(Qt::Horizontal);
slider->setRange(0, 99);
slider->setValue(0);
connect(slider, SIGNAL(valueChanged(int)), lcd, SLOT(display(int)));
connect(slider, SIGNAL(valueChanged(int)), SIGNAL(valueChanged(int)));
QVBoxLayout* layout = new QVBoxLayout;
layout->addWidget(lcd);
layout->addWidget(slider);
setLayout(layout);
}
int LCDRange::value() const
{
return slider->value();
}
void LCDRange::setValue(int value)
{
slider->setValue(value);
}
**main.cpp **
#include <QApplication>
#include <Qpushbutton>
#include <QVBoxLayout>
#include <QGridLayout>
#include <QLCDNumber>
#include <QSlider>
#include <Qfont>
#include "lcdrange.h"
class MyWidget : public QWidget
{
public:
MyWidget(QWidget *parent=0, Qt::WindowFlags name = Qt::WindowFlags());
};
MyWidget::MyWidget(QWidget *parent, Qt::WindowFlags name)
: QWidget(parent, name)
{
QPushButton *quit = new QPushButton("Quit");
quit->setFont(QFont("Times", 18, QFont::Bold));
connect(quit, SIGNAL(clicked()), qApp, SLOT(quit()));
QGridLayout *grid = new QGridLayout;
LCDRange *previous = 0;
for(int r = 0 ; r < 4 ; r++)
{
for(int c = 0 ; c < 5 ; c++)
{
LCDRange* lcdRange = new LCDRange;
grid->addWidget(lcdRange, r, c);
if (previous)
{
connect(lcdRange, SIGNAL(valueChanged(int)), previous, SLOT(setValue(int)));
}
previous = lcdRange;
}
}
QVBoxLayout* layout = new QVBoxLayout;
layout->addWidget(quit);
layout->addLayout(grid);
setLayout(layout);
}
int main(int argc, char **argv)
{
QApplication a(argc, argv);
MyWidget w;
w.show();
return a.exec();
}