本文向介绍利用Qt开发图形用户界面的应用程序的入门知识。这里,我们首先介绍了如何搭建Qt的开发环境,之后通过一些简单的示例程序来循序渐进地介绍Qt的“信号和槽”以及布局等基本概念。我们希望以此来帮助读者尽快地对Qt图形用户界面应用程序开发有一个初步的认识,并为进一步学习打下一个良好的基础。
一、什么是Qt
Qt 是一个用于桌面系统和嵌入式开发的跨平台应用程序框架。它包括一个直观的API和一个丰富的类库,以及用于GUI开发和国际化的集成工具,另外它支持Java™和C++开发。利用它,我们无须重新编写源代码,便可以构建运行在不同桌面操作系统和嵌入式设备上的软件应用程序。
借助Qt,我们可以更快速地构建先进的用户界面:它不仅提供了丰富的标准widgets库,动态布局引擎等GUI功能,还通过集成OpenGL® 与OpenGL ES提供了先进的3D可视化支持,此外,它还具有强大的图形画布和Widgets样式表,使我们得以使用变焦、旋转和人机互动功能构建先进的用户界面,并且能用寥寥几行代码便可快速定制自己的用户界面。
虽然Qt提供了许多高级功能,但千里之行,始于足下,还是让我们先从最基本的知识开始入手吧。下面介绍如何搭建Qt开发环境。
二、搭建Qt开发环境
虽然Qt自身带有构建工具,但它是在命令行下使用的,多少有些不便。所以,我们在此自己动手建设自己的Qt集成开发环境。下面介绍Qt开发环境的具体搭建过程。
首先,从互联网上搜索并下载Dev-C++,安装很简单,一路回车就可以了。然后,到http://www.trolltech.com/download/下载***的安装包,对于Windows系统来说,可以下载已编译好的安装包,当前***版为qt-win-opensource-4.4.0-mingw.exe。在Qt安装过程中唯一需要注意的是,当安装程序要求选择mingw的路径时,直接选择Dev-Cpp的安装路径就行了。安装好上述两个软件后,***还要到http://download.csdn.net/source/219376下载Qt4 For Dev-Cpp Templates,下载后将其解压到Dev-Cpp的Templates文件夹下即可。这是用于在Dev-Cpp下开发Qt程序的模板资源。
***,把Qt安装目录中的\bin目录中的动态链接库拷贝到windows目录下,这样当运行编译好的Qt程序时,就再也不会碰到无法找到Qt的DLL 的问题了。
三、我们的***个Qt程序
迄今为止,我们已经搭建好了Qt的开发环境,接下来就可以编写我们的***个Qt程序。按照学习编程的老传统,一般编写的***个程序都是一个Hello程序,我们也不例外。
运行Dev-C++,在其“文件”菜单中选择“新建”菜单项,然后单击“工程”命令,出现如下图所示的对话框:
图1 新建Qt工程
选择其中的“Empty Project”,以便建立一个空项目,将项目名称定为“hello”,其他选择默认,如图1所思,然后单击“确定”按钮。在弹出的“Create new project”对话框中选择工程文件名称和保存路径,如图2所示。
图2 保存项目文件
上面已经新建了一个空的Qt项目,现在为它添加一个源代码文件。在Dev-C++的“文件”菜单中选择“新建”菜单项,然后单击“源代码”命令,在弹出的确认对话框中单击“Yes”按钮。在编辑区中录入如下代码,保存源代码文件时将其命名为hello.cpp。
- #include <QApplication>
- #include <QLabel>
- int main(int argc, char *argv[])
- {
- QApplication app(argc, argv);
- QLabel *label = new QLabel("Hello World!");
- label->show();
- return app.exec();
- }
现在,让我们来编译该程序。单击“运行”菜单中的“编译”菜单项,出现如下图所示对话框时,说明没有出现错误,编译成功。
图3 编译成功
单击“关闭”按钮。很好,现在运行我们***个Qt应用程序的时候到了,单击“运行”菜单中的“运行”菜单项。来,看看我们的“大作”吧!
图4 我们的hello程序
上面演示了在集成开发环境中开发Qt应用程序的整个过程,下面开始介绍我们的源代码。俗话说,万事开头难,所以我们在这里会尽可能细致地为读者讲解这些代码。
- #include <QApplication>
- #include <QLabel>
在这个程序中,我们总共用到了两个类QApplication和Qlabel,根据先声明后使用的原则,我们在上面两行将这两个类的定义包含到我们的代码中。对Qt来说,它的每一个类都有一个同名的头文件与之对应,这个类的定义就在这个头文件中。我们注意到,这两个头文件都是以大写字母开头的,实际上类对应的头文件都是这样。
- int main(int argc, char *argv[])
在这里,main()函数是程序的入口。在使用Qt的时候,main()一般只是执行一些初始化工作,接着就把控制转交给Qt库,然后Qt库通过事件来向程序报告用户的行为。
- QApplication app(argc, argv);
上面这行代码为QApplication创建了一个对象,实际上,在每一个使用Qt的应用程序中都必须有一个QApplication对象,该对象用来管理应用程序的各种资源。一般说来,在使用Qt的窗口部件被之前,要首先创建QApplication对象。因为Qt支持命令行参数,所以这里的QApplication带有argc和argv,用来接收入口函数从系统那里接收到的命令行变量,以便进一步处理。
- QLabel *label = new QLabel("Hello World!");
这一行创建了一个窗口部件QLabel,我们用它来显示一则消息“Hello World!”。按照Qt的术语,一个窗口部件就是用户界面中的一个可见的用户界面对象,它能够处理用户输入和绘制图形,它相当于Windows的术语中的一个控件或容器。我们可以改变窗口部件的全部观感、主要属性(比如颜色等)以及窗口部件的内容等。我们常见的按钮、菜单、滚动条和框架等都属于窗口部件。窗口部件可以包含其它的窗口部件,比如应用程序窗口通常就是一个窗口部件,而其中又包含了QMenuBar、QToolBars、QstatusBar以及其它的窗口部件。大多数应用程序使用一个QMainWindow或者QDialog作为自己的主窗口,但是这不是必须的的,实际上任何窗口部件都能当作程序的主窗口。就本例而言,窗口部件QLabel就是应用程序的主窗口或者说是主窗口部件。如果用户关闭了主窗口部件,应用程序就会退出。
- label->show();
默认时,窗口部件是不可见的,之所以这样,是为了让我们可以在显示之前对窗口部件进行必要的设定,以防止闪烁现象的发生。上面这一行代码的作用是使标签变为可见的。
- return app.exec();
上面这一行代码将应用程序的控制权交给Qt,交权后,应用程序便进入事件循环状态。这时的程序只是静静地等待用户的鼠标或键盘之类的动作。当用户发出动作时,就会生成相应的事件,如果这些事件正是该程序需要响应的那些事件,它便会执行一些函数来响应用户的动作。
迄今为止,我们已经编译运行了***个Qt应用程序,并且对该程序的源代码有了初步的了解,但是我们的这个程序非常简单,简单到显示一条消息后就只能通过标题栏上的“关闭”按钮来关闭。接下来我们将进一步学习如何通过窗口部件来跟用户互动。
#p#
四、跟用户互动
在第二个实例中,我们将为大家介绍如何响应用户的动作。该程序也很简单,它仅由一个按钮组成,当用户单击该按钮时,程序就会退出。这个应用程序运行画面如下所示:
图5 利用按钮跟用户进行交互
下面是该程序的源代码:
- #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();
- }
我们看到,这里的源代码跟上面的非常相似,只有两处不一样,一是主窗口部件是QPushButton,而非Qlabel;二是将用户操作(如这里的单击按钮)跟一段代码联系在一起。当用户执行某些操作,或状态发生变化时,Qt的窗口部件就会发出一些信号来指示这些事件的发生。举例来说,当用户单击按钮时,QPushButton就会发出一个clicked()信号。这时,跟这个事件相联系的代码就会就会自动执行。在QT中,对这样的代码有一个专门的称谓,叫做槽。对这里的例子来说,我们将按钮的clicked()信号连到QApplication的槽quit()上。所以,单击Quit按钮,或按下空格键时,该程序就会终止。
这里涉及到Qt的一个基本思想,那就是“信号和槽”。这一思想需要专门一篇文章来进行解释,我们这里只要知道,每个Qt对象,无论是直接还是间接继承QObject对象的对象,都能用信号发出信息,也能用槽来接收信息并作出反应。这里要注意的是,所有窗口部件都是Qt对象,因为它们继承自QWidget,而Qwidget又继承自QObject。
这里的connect()是QObject中的一个静态函数,它的作用是将信号和槽连接在一起。比如本例中,它把按钮的clicked()信号和QApplication的槽quit()连接起来了,所以当这个按钮被按下的时候,这个程序就退出了。
五、窗口部件的布局
读者可能已经发现,我们上面的两个例子中,都只是用了一个窗口部件,但是现实情况却是一个程序界面中有多个窗口部件,并且一些窗口部件通常还位于其他窗口部件之内。这时问题就来了:如何将一些窗口部件放进另一个窗口部件中?放进去以后又如何对它们进行布置呢?别急,这些事情Qt的设计者早就替我们考虑到了,下面就介绍Qt的自动布局支持。
除了解释如何使用布局来管理窗口部件在窗口中的几何形状之外,本示例程序还将为读者介绍如何使用信号和槽来实现两个窗口部件的同步。如下图所示:
图6 窗口部件布局与同步示例
我们可以在这个界面中输入一个1到100之间的数字。当然,我们可以用两种方法输入数字,既可以拖动滑块,也可以使用Spinbox按钮。但是,无论使用哪一种方式,只要一边表示的数字发生了变化,另一边也会随之改变,所以它们总能保持一致。该示例程序的源代码如下所示:
- #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 Number");
- QSpinBox *spinBox = new QSpinBox;
- QSlider *slider = new QSlider(Qt::Horizontal);
- spinBox->setRange(0, 100);
- slider->setRange(0, 100);
- QObject::connect(spinBox, SIGNAL(valueChanged(int)),
- slider, SLOT(setValue(int)));
- QObject::connect(slider, SIGNAL(valueChanged(int)),
- spinBox, SLOT(setValue(int)));
- spinBox->setValue(60);
- QHBoxLayout *layout = new QHBoxLayout;
- layout->addWidget(spinBox);
- layout->addWidget(slider);
- window->setLayout(layout);
- window->show();
- return app.exec();
- }
该应用程序的界面由三个窗口部件组成,分别是QSpinBox、QSlider和QWidget,其中QWidget是本程序的主窗口,然后在QWidget内再引用QSpinBox和QSlider,所以后两者是前者的子部件,或者说前者是后两者的父部件。QWidget本身没有父部件,因为它是一个***窗口。QWidget及其子类的构造函数使用参数QWidget *来规定其父部件。
下面我们对源代码进行解释:
- QWidget *window = new QWidget;
- window->setWindowTitle("Enter Your Number");
上面两行将QWidget设置为该程序的主窗口,其中setWindowTitle()用于规定显示在这个窗口标题栏中的文本内容。
- QSpinBox *spinBox = new QSpinBox;
- QSlider *slider = new QSlider(Qt::Horizontal);
上面两行创建了一个QSpinBox和一个QSlider,然后,
- spinBox->setRange(0, 100);
- slider->setRange(0, 100);
这两行设置了其有效范围,我们这里选择0至100之间的数字。
- QObject::connect(spinBox, SIGNAL(valueChanged(int)),
- slider, SLOT(setValue(int)));
- QObject::connect(slider, SIGNAL(valueChanged(int)),
- spinBox, SLOT(setValue(int)));
在上面的两个语句中,我们调用了QObject::connect()两次,实现了Spinbox按钮和滑块之间的同步,从而使得显示的结果将保持一致。当一个窗口部件中的值发生变化时,它就会发出valueChanged(int)信号,并用这个新值来调用另一个窗口部件的槽setValue(int),这样它们就能保持一致。
- spinBox->setValue(60);
上面这一行代码将Spinbox按钮的值设为60。这时,QSpinBox会发出valueChanged(int)信号,其中参数int为60,这个参数传递给QSlider的槽setValue(int),这个槽继而将滑块值设为60。 因为滑块自己的值变了,所以它会发出信号valueChanged(int)来触发Spinbox按钮的槽setValue(int)。不过由于Spinbox按钮的值早已是60,两者是一致的,所以它就不会继续发信号了,同步过程至此结束。
- QHBoxLayout *layout = new QHBoxLayout;
- layout->addWidget(spinBox);
- layout->addWidget(slider);
通过上面三行代码,我们新建了一个一个布局管理器,然后将Spinbox按钮和滑块这两个窗口部件交给这个布局管理器,让它来对这两个部件的大小和位置等作出安排。一个布局管理器是一个对象,用于设置窗口部件的位置和尺寸。Qt的布局管理器分为三大类:
QHBoxLayout将窗口小部件从左至右,或者从右到左水平放置窗口部件。
QVBoxLayout将窗口小部件自上而下垂直布置。
QGridLayout将窗口小部件布置在一个网格中。
- window->setLayout(layout);
我们在以上代码中调用QWidget::setLayout(),这会在窗口中安装一个布局管理器。这样一来,QSpinBox和QSlider又进一步成为安装布局管理器的窗口部件的子部件,所以当我们创建一个将来要放入布局管理器的窗口部件时,不用显式地指出其父部件。
如果我们要想显式地给QSpinBox和Qslider指定父窗口部件的话,可以在创建它们时将参数window传递给QSpinBox和QSlider的构造函数,以规定让window作为它们的父部件。
使用布局管理器有很大的优点,就像上面看到的那样,即使不对任何窗口部件的位置和尺寸进行任何显式地设置,***QSpinBox和QSlider还是很好地并排布置在了一起。这是因为QHBoxLayout会根据需要,自动地为其负责的窗口部件指定合适的位置和大小。更重要的是,该布局管理器将我们从在程序中硬编码窗口部件的屏幕位置的琐碎工作中解放了出来,它会替我们处理窗口平滑缩放等相应事项。
小结
本文简单介绍了信号和槽的连接以及布局的基本知识,并介绍了Qt的完全面向对象的构造和利用窗口小部件的方法。我们发现,利用Qt构造用户界面的方式简洁而又灵活,我们只需要创建窗口部件对象,然后根据需要为其设置属性即可。此外,我们还可以将生成的窗口部件添加到布局管理器中,这样,布局管理器就会自动地调整这些窗口部件的尺寸和位置。同时,我们还可以通过信号和槽机制方便的将用户界面的行为跟Qt的窗口部件联系在一起,这样就能实现程序跟用户的互动了。