本篇博客主要介绍如何把基本的C++知识与Qt所提供的功能组合起来创建一些简单的图形户用界面(Graphical User Interface,GUI)应用程序。关键在于理解Qt中的两个重要的概念:其一是“信号与槽”;其二是“布局”。
1.从Hello Qt谈起
先从一个非常简单的Qt程序开始。一行一行的研究这个程序。第一行和第二行包含了类QApplication和QLabel的定义。对于每个Qt类,都有一个与该类同名且大写的头文件,这个头文件中包含了对该类的定义。<span style="font-size:18px;">#include <QApplication> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel *label = new QLabel ("Hello Qt"); label->show(); return app.exec(); }</span>
第五行创建了一个QApplication对象,用来管理整个应用程序所用到的资源。这个QApplication构造函数需要两个参数,分别是argc和argv,因为Qt支 持他自己的一些命令行参数。第六行创建一个显示“Hello Qt”的QLabel窗口部件(widget)。在Qt和UNIX的术语中,窗口部件就是用户界面中的一个可视化元素。该词起源于“window gadget”这两个词,它相当于Windows系统术语中的“控件control”和“容器container”。按钮、菜单、滚动条和框架都是窗口部件。窗口部件也可以包含其他窗口部件,例如,应用程序的窗口通常就是一个包含了一个QMenuBar、一些QToolBar、一些QStatusBar以及一些其他窗口部件的窗口部件。绝大多数应用程序都会使用一个QMainWindow或者一个QDialog来作为他的窗口。但是,不可否认的是,Qt十分的灵活,任意的窗口部件都可以作为他的窗口。在上面的例子中,就是采用了窗口部件QLabel作为应用程序的窗口的。第七行使QLabel标签可见。在创建窗口部件的时候,标签通常都是隐藏的,这就允许我们可以先对其进行设置然后在显示他们,从而避免了窗口部件有闪烁现象。第八行将应用程序的控制权传递给Qt。此时,程序会进入事件循环状态,这是一种等待模式,程序会等候用户的动作,例如鼠标单击和按键等操作。用户的动作会让可以产生相应的的程序生成一些时间(event,也称为消息),这里谈到的响应其实就是执行一个或多个消息响应函数。为了简单起见,上面的程序没有过多关注在main()函数末尾对QLabel对象的delete操作调用。在如此短小的程序内,这样一点点的内存泄漏(memory leak)问题无关大局,因为在程序结束时,这部分内存是可以有操作系统重新回收的。测试结果如下图所示:
2.如何响应用户操作
下面的例子是要说明如何响应y用户的动作。该应用程序由一个按钮构成,用户可以单击这个按钮退出程序。除了应用程序的主窗口部件使用的是QPushButton而不是QLabel之外,该程序的源代码和上面的极其相似。该段程序演示的目的就是为了理解如何将用户的一个动作与一段代码连接起来。Qt的窗口部件通过发射信号(signal)来表明一个用户动作已经发生了或者是一个状态已经改变了。例如,当用户单击QPushButton时,该按钮就会发射一个click()信号。信号可以与函数(在这里称为槽,slot)相连接,以便在发射信号时,槽可以得到自动执行。上面那个例子,就是把这个按钮的click()信号与QApplication对象的quit()槽连接起来。宏SIGNAL()和SLOT()是Qt语法中的一部分。<span style="font-size:18px;">#include <QApplication> #include <QPushButton> int main(int argc, char *argv[]) { QApplication app(argc, argv); QPushButton *button = new QPushButton("Quit"); QObject::connect(button,SIGNAL(clicked()),&app,SLOT(quit())); button->show(); return app.exec(); } </span>
经过编译,运行结果如下图所示。如果点击Quit按钮,或者是按下空格键,那么将会退出应用程序。
3.窗口部件的布局
该例主要创建一个简单的程序,旨在说明如何应用布局(Layout)来管理窗口中窗口部件的几何形状,还要说明如何应用信号和槽来同步窗口部件。运行结果:#include <QApplication> #include <QHBoxLayout> #include <QSlider> #include <QSpinBox> int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget *window = new QWidget; window->setWindowTitle("Enter your Age"); QSpinBox *spinbox = new QSpinBox; QSlider *slider = new QSlider (Qt::Horizontal); spinbox->setRange(0,130); slider->setRange(0,130); QObject::connect(spinbox,SIGNAL(valueChanged(int)), slider,SLOT(setValue(int))); QObject::connect(slider,SIGNAL(valueChanged(int)), spinbox,SLOT(setValue(int))); spinbox->setValue(35); QHBoxLayout *layout = new QHBoxLayout; layout->addWidget(slider); layout->addWidget(spinbox); window->setLayout(layout); window->show(); return app.exec(); }
这个应用程序有三个窗口部件组成:一个QSpinBox、一个QSlider和一个QWidget。QWidget是这个应用程序的主窗口。QSlider和QSpinBox会显示在QWidget中,他们都是QWidget窗口部件的子对象。换言之,QWidget窗口部件是QSpinBox和QSlider的父对象。QWidget窗口部件自己则没有父对象,因为程序是把他当成顶层窗口。QWidget的构造函数以及他的所有子类都会带一个参数QWidget*,用来说明谁是他们的父窗口部件。QWidget *window = new QWidget;
window->setWindowTitle("Enter your Age");
创建了QWidget对象,并把它作为应用程序的主窗口。通过调用setWindowTitle()函数来设置像是在窗口标题栏上的文字。QSpinBox *spinbox = new QSpinBox;
QSlider *slider = new QSlider (Qt::Horizontal);
spinbox->setRange(0,130);
slider->setRange(0,130);
分别创建了一个QSpinBox和一个QSlider,并设置了他们的有效范围。其实,我们本应该把这个窗口传递给QSpinBox和QSlider的构造函数,以说明这两个窗口部件的父对象都是这个窗口,但是,在这里没有这个必要,因为布局系统将会自行得出这一结果,并自动把该窗口设置为微调框和滑块的父对象。QObject::connect(spinbox,SIGNAL(valueChanged(int)),
slider,SLOT(setValue(int)));
QObject::connect(slider,SIGNAL(valueChanged(int)),
spinbox,SLOT(setValue(int)));
调用了两次QObject::connect(),这是为了确保能够让微调框和滑块同步,以便他们两个总是可以显示相同的数值。一旦一个窗口部件的值发生了改变,那么就会自动发射出他的valueChange(int)信号,而另一个窗口部件就会用这个新值调用它的setValue(int)槽。QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(slider);
layout->addWidget(spinbox);
window->setLayout(layout);
使用了一个布局管理器对微调框和滑块进行布局处理。布局管理器(layout manager)就是一个能够对其所负责窗口部件的尺寸和位置进行设置的对象。Qt有三个主要的布局管理器类:#QHBoxLayo ut:在水平方向排列窗口部件,从左到右;#QVBoxLayout:在竖直方向上排列窗口部件,从上到下;#QGridLayout:把各个窗口部件排列在一个网格中。其中,QWidget::setLayout() 函数调用会在窗口上安装该布局管理器。从软件的底层实相上来说,QSlider和QSpinBox会自动“从定义父对象”,他们会成为这个安装了布局的窗口部件的子对象。也正是这种原因,当创建一个需要放进某个不居中的窗口部件时,就没有必要为其显式地指定父对象了。经管我们没有明确地设置任何一个窗口部件的位置和大小,但QSpinBox和QSlider还是能够非常好看的一个挨一个地显示出来。这是因为QHBoxLayout可以根据所负责的子对象的需要为他们分配所需的位置个大小。布局管理器使我们从应用程序的各种屏幕位置关系指定的复杂中脱离出来。此外,最大的好处莫过于这可以确保窗口尺寸大小发生改变时的平稳性。
4.小结
Qt中构建用户接口的方法很容易理解并且非常灵活。Qt程序员最常用的方式就是先声明所需的窗口部件,然后在设置他们所应具备的属性。程序员把这些窗口部件调价到布局中,布局会自动设置它们的大小和位置。利用Qt的信号和槽机理,并通过窗口部件之间的连接就能够管理用户的交互行为。所以看出,Qt确实要比MFC具有更广阔的发展前景。