Qt

qt的一篇入门文章(貌似大部分代码和讲解都来自C++ GUI Programming with QT4)

 

Qt学习之路(1):前言

Qt是一个著名的C++库——或许并不能说这只是一个GUI库,因为Qt十分庞大,并不仅仅是GUI。使用Qt,在一定程序上你获得的是一个“一站式”的服务:不再需要研究STL,不再需要C++的<string>,因为Qt有它自己的QString等等。或许这样说很偏激,但Qt确实是一个“伟大的C++库”。

我们所使用的Qt,确切地说也就是它的GUI编程部分。C++的GUI编程同Java不同:GUI并不是C++标准的一部分。所以,如果使用Java,那么你最好的选择就是AWT/Swing,或者也可以使SWT/JFace,但是,C++的GUI编程给了你更多的选择:wxWidget, gtk++以及Qt。这几个库我都有接触,但是接触都不是很多,只能靠一些资料和自己的一点粗浅的认识说一下它们之间的区别(PS: 更详尽的比较在前面的文章中有)。

首先说wxWidget,这是一个标准的C++库,和Qt一样庞大。它的语法看上去和MFC类似,有大量的宏。据说,一个MFC程序员可以很容易的转换到wxWidget上面来。wxWidget有一个很大的优点,就是它的界面都是原生风格的。这是其他的库所不能做到的。wxWidget的运行效率很高,据说在Windows平台上比起微软自家的MFC也不相上下。

gtk++其实是一个C库,不过由于C++和C之间的关系,这点并没有很大的关系。但是,gtk++是一个使用C语言很优雅的实现了面向对象程序设计的范例。不过,这也同样带来了一个问题——它的里面带有大量的类型转换的宏来模拟多态,并且它的函数名“又臭又长(不过这点我倒是觉得无所谓,因为它的函数名虽然很长,但是同样很清晰)”,使用下划线分割单词,看上去和Linux如出一辙。由于它是C语言实现,因此它的运行效率当然不在话下。gtk++并不是模拟的原生界面,而有它自己的风格,所以有时候就会和操作系统的界面显得格格不入。

再来看Qt,和wxWidget一样,它也是一个标准的C++库。但是它的语法很类似于Java的Swing,十分清晰,而且SIGNAL/SLOT机制使得程序看起来很明白——这也是我首先选择Qt的一个很重要的方面,因为我是学Java出身的 :) 。不过,所谓“成也萧何,败也萧何”,这种机制虽然很清楚,但是它所带来的后果是你需要使用Qt的qmake对程序进行预处理,才能够再使用make或者nmake进行编译。并且它的界面也不是原生风格的,尽管Qt使用style机制十分巧妙的模拟了本地界面。另外值得一提的是,Qt不仅仅运行在桌面环境中,Qt已经被Nokia收购,它现在已经会成为Symbian系列的主要界面技术——Qt是能够运行于嵌入式平台的。

以往人们对Qt的授权多有诟病。因为Qt的商业版本价格不菲,开源版本使用的是GPL协议。但是现在Qt的开源协议已经变成LGPL。这意味着,你可以将Qt作为一个库连接到一个闭源软件里面。可以说,现在的Qt协议的争议已经不存在了——因为wxWidgets或者gtk+同样使用的是类似的协议发布的。

在本系列文章中,我们将使用Qt4进行C++ GUI的开发。我是参照着《C++ GUI Programming with Qt4》一书进行学习的。其实,我也只是初学Qt4,在这里将这个学习笔记记下来,希望能够方便更多的朋友学习Qt4。我是一个Java程序员,感觉Qt4的一些命名规范以及约束同Java有异曲同工之妙,因而从Java迁移到Qt4似乎困难不大。不过,这也主要是因为Qt4良好的设计等等。

闲话少说,还是尽快开始下面的学习吧!

 

Qt学习之路(2):Hello, world!

任何编程技术的学习第一课基本上都会是Hello, world!,我也不想故意打破这个惯例——照理说,应该首先回顾一下Qt的历史,不过即使不说这些也并无大碍。

或许有人总想知道,Qt这个单词是什么意思。其实,这并不是一个缩写词,仅仅是因为它的发明者,TrollTech公司的CEO,Haarard Nord和Trolltech公司的总裁Eirik Chambe-Eng在联合发明Qt的时候并没有一个很好的名字。在这里,字母Q是Qt库中所有类的前缀——这仅仅是因为在Haarard的emacs的字体中,这个字母看起来特别的漂亮;而字母t则代表“toolkit”,这是在Xt( X toolkit )中得到的灵感。

顺便说句,Qt原始的公司就是上面提到的Trolltech,貌似有一个中文名字是奇趣科技——不过现在已经被Nokia收购了。因此,一些比较旧的文章里面会提到Trolltech这个名字。

好了,闲话少说,先看看Qt的开发吧!事先说明一下,我是一个比较懒的人,不喜欢配置很多的东西,而Qt已经提供了一个轻量级的IDE,并且它的网站上也有for Eclipse 和 VS 的开发插件,不过在这里我并不想用这些大块头 :)

Qt有两套协议——商业版本和开源的LGPL版本。不同的是前者要收费,而后者免费,当然,后者还要遵循LGPL协议的规定,这是题外话。

Qt的网址是 https://qt.nokia.com/downloads ,不过我打开这个站点总是很慢,不知道为什么。你可以找到大大的 LGPL/Free 和 Commercial,好了,我选的是LGPL版本的,下载包蛮大,但是下载并不会很慢。下载完成后安装就可以了,其它不用管了。这样,整个Qt的开发环境就装好了——如果你需要的话,也可以把qmake所在的目录添加进环境变量,不过我就不做了。

安装完成后会有个Qt Creator的东西,这就是官方提供的一个轻量级IDE,不过它的功能还是蛮强大的。运行这个就会发现,其实Qt不仅仅是Linux KDE桌面的底层实现库。而且是这个IDE的实现 :) 这个IDE就是用Qt完成的。

Qt Creator左面从上到下依次是Welcome(欢迎页面,就是一开始出现的那个);Edit(我们的代码编辑窗口);Debug(调试窗口);Projects(工程窗口);Help(帮助,这个帮助完全整合的Qt的官方文档,相当有用);Output(输出窗口)。

下面我们来试试我们的 Hello, world! 吧!

在Edit窗口空白处点右键,有 New project... 这里我们选第三项,Qt Gui Application。

 
然后点击OK,来到下一步,输入工程名字和保存的位置。
 
 
点击Next,来到选择库的界面。这里我们系统默认为我们选择了Qt core 和 GUI,还记得我们建的是Gui Application吗?嗯,就是这里啦,它会自动为我们加上gui这个库。现在应该就能看出,Qt是多么庞大的一个库,它不仅仅有Gui,而且有Network,OpenGL,XML之类。不过,现在在这里我们不作修改,直接Next。
 
 
下一个界面需要我们定义文件名,我们不修改默认的名字,只是为了清除起见,把generate form的那个勾去掉即可。
 
 
Next之后终于到了Finish了——漫长的一系列啊!检查无误后Finish就好啦!
 
 
之后可以看到,IDE自动生成了四个文件,一个.pro文件,两个.cpp和一个.h。这里说明一下,.pro就是工程文件(project),它是qmake自动生成的用于生产makefile的配置文件。这里我们先不去管它。main.cpp里面就是一个main函数,其他两个文件就是先前我们曾经指定的文件名的文件。
 
 
现在,我们把main.cpp中的代码修改一下:
 
#include <QtGui/QApplication>
#include <QLabel>

int main( int argc, char *argv[])
{
        QApplication a(argc, argv);
        QLabel *label = new QLabel( "Hello, world!");
        label->show();
         return a.exec();
}
 
修改完成后保存。点击左下角的绿色三角键,Run。一个小小的窗口出现了——
 
 
好了!我们的第一个Qt程序已经完成了。
 
PS:截了很多图,说得详细些,以后可就没这么详细的步骤啦,嘿嘿…相信很多朋友应该一下子就能看明白这个IDE应该怎么使用了的,无需我多费口舌。呵呵。
 
下一篇中,将会对这个Hello, world!做一番逐行解释!
下面来逐行解释一下前面的那个Hello, world!程序,尽管很简单,但却可以对Qt程序的结构有一个清楚的认识。现在再把代码贴过来:
 
#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();
}
 
第1行和第2行就是需要引入的头文件。和普通的C++程序没有什么两样,如果要使用某个组件,就必须要引入相应的头文件,这类似于Java的import机制。值得说明的是,Qt中头文件和类名是一致的。也就是说,如果你要使用某个类的话,它的类名就是它的头文件名。
 
第3行是空行 :)
 
第4行是main函数函数头。这与普通的C++程序没有什么两样,学过C++的都明白。因此你可以看到,实际上,Qt完全通过普通的main函数进入,这不同于wxWidgets,因为wxWidgets的Hello, world需要你继承它的一个wxApp类,并覆盖它的wxApp::OnInit方法,系统会自动将OnInit编译成入口函数。不过在Qt中,就不需要这些了。
 
第5行,噢噢,大括号…
 
第6行,创建一个QApplication对象。这个对象用于管理应用程序级别的资源。QApplication的构造函数要求两个参数,分别来自main的那两个参数,因此,Qt在一定程度上是支持命令行参数的。
 
第7行,创建一个QLabel对象,并且能够显示Hello, world!字符串。和其他库的Label控件一样,这是用来显示文本的。在Qt中,这被称为一个widget(翻译出来是小东西,不过这个翻译并不好…),它等同于Windows技术里面的控件(controls)和容器(containers)。也就是说,widget可以放置其他的widget,就像Swing的组件。大多数Qt程序使用QMainWindow或者QDialog作为顶级组件,但Qt并不强制要求这点。在这个例子中,顶级组件就是一个QLabel。
 
第8行,使这个label可见。组件创建出来之后通常是不可见的,要求我们手动的使它们可见。这样,在创建出组建之后我们就可以对它们进行各种定制,以避免出现之后在屏幕上面会有闪烁。
 
第9行,将应用程序的控制权移交给Qt。这时,程序的事件循环就开始了,也就是说,这时可以相应你发出的各种事件了。这类似于gtk+最后的一行gtk_main()。
 
第10行,大括号……程序结束了。
 
注意,我们并没有使用delete去删除创建的QLabel,因为在程序结束后操作系统会回收这个空间——这只是因为这个QLabel占用的内存比较小,但有时候这么做会引起麻烦的,特别是在大程序中,因此必须小心。
 
好了,程序解释完了。按照正常的流程,下面应该编译。前面也提过,Qt的编译不能使用普通的make,而必须先使用qmake进行预编译。所以,第一步应该是在工程目录下使用
 
qmake -project
 
命令创建.pro文件(比如说是叫helloworld.pro)。然后再在.pro文件目录下使用
 
qmake helloworld.pro  (make)
 
或者
 
qmake -tp vc helloworld.pro  (nmake)
 
生成makefile,然后才能调用make或者是nmake进行编译。不过因为我们使用的是IDE,所以这些步骤就不需要我们手动完成了。
 
值得说明一点的是,这个qmake能够生成标准的makefile文件,因此完全可以利用qmake自动生成makefile——这是题外话。
 
好了,下面修改一下源代码,把QLabel的创建一句改成
 
QLabel *label = new QLabel( "<h2><font color='red'>Hello</font>, world!<h2>");
 
运行一下:
 
 
同Swing的JLabel一样,Qt也是支持HTML解析的。
 
好了,这个Hello, world就说到这里!明确一下Qt的程序结构,在一个Qt源代码中,一下两条语句是必不可少的:
QApplication app(argc, argv);
//...
return app.exec();
Qt学习之路(4):初探信号槽
看过了简单的Hello, world! 之后,下面来看看Qt最引以为豪的信号槽机制!
 
所谓信号槽,简单来说,就像是插销一样:一个插头和一个插座。怎么说呢?当某种事件发生之后,比如,点击了一下鼠标,或者按了某个按键,这时,这个组件就会发出一个信号。就像是广播一样,如果有了事件,它就漫天发声。这时,如果有一个槽,正好对应上这个信号,那么,这个槽的函数就会执行,也就是回调。就像广播发出了,如果你感兴趣,那么你就会对这个广播有反应。干巴巴的解释很无力,还是看代码:
 
#include <QtGui/QApplication>
#include <QtGui/QPushButton>

int main( int argc, char *argv[])
{
        QApplication a(argc, argv);
        QPushButton *button = new QPushButton( "Quit");
        QObject::connect(button, SIGNAL(clicked()), &a, SLOT(quit()));
        button->show();
         return a.exec();
}
 
这是在Qt Creator上面新建的文件,因为前面已经详细的说明怎么新建工程,所以这里就不再赘述了。这个程序很简单,只有一个按钮,点击之后程序退出。(顺便说一句,Qt里面的button被叫做QPushButton,真搞不明白为什么一个简单的button非得加上push呢?呵呵)
 
主要是看这一句:
 
QObject::connect(button, SIGNAL(clicked()), &a, SLOT(quit()));
 
QObject是所有类的根。Qt使用这个QObject实现了一个单根继承的C++。它里面有一个connect静态函数,用于连接信号槽。
 
当一个按钮被点击时,它会发出一个clicked信号,意思是,向周围的组件们声明:我被点击啦!当然,其它很多组件都懒得理他。如果对它感兴趣,就告诉QObject说,你帮我盯着点,只要button发出clicked信号,你就告诉我——想了想之后,说,算了,你也别告诉我了,直接去执行我的某某某函数吧!就这样,一个信号槽就形成了。具体来说呢,这个例子就是QApplication的实例a说,如果button发出了clicked信号,你就去执行我的quit函数。所以,当我们点击button的时候,a的quit函数被调用,程序退出了。所以,在这里,clicked()就是一个信号,而quit()就是槽,形象地说就是把这个信号插进这个槽里面去。
 
Qt使用信号槽机制完成了事件监听操作。这类似与Swing里面的listener机制,只是要比这个listener简单得多。以后我们会看到,这种信号槽的定义也异常的简单。值得注意的是,这个信号槽机制仅仅是使用的QObject的connect函数,其他并没有什么耦合——也就是说,完全可以利用这种机制实现你自己的信号监听!不过,这就需要使用qmake预处理一下了!
 
细心的你或许发现,在Qt Creator里面,SIGNAL和SLOT竟然变颜色了!没错,Qt确实把它们当成了关键字!实际上,Qt正是利用它们扩展了C++语言,因此才需要使用qmake进行预处理,比便使普通的C++编译器能够顺利编译。另外,这里的signal和Unix系统里面的signal没有任何的关系!哦哦,有一点关系,那就是名字是一样的!
 
信号槽机制是Qt关键部分之一,以后我们还会再仔细的探讨这个问题的。
Qt学习之路(5):组件布局
同Swing类似,Qt也提供了几种组件定位的技术。其中就包括绝对定位和布局定位。
 
顾名思义,绝对定位就是使用最原始的定位方法,给出这个组件的坐标和长宽值。这样,Qt就知道该把组件放在哪里,以及怎么设置组件的大小了。但是这样做的一个问题是,如果用户改变了窗口大小,比如点击了最大化或者拖动窗口边缘,这时,你就要自己编写相应的函数来响应这些变化,以避免那些组件还只是静静地呆在一个角落。或者,更简单的方法是直接禁止用户改变大小。
 
不过,Qt提供了另外的一种机制,就是布局,来解决这个问题。你只要把组件放入某一种布局之中,当需要调整大小或者位置的时候,Qt就知道该怎样进行调整。这类似于Swing的布局管理器,不过Qt的布局没有那么多,只有有限的几个。
 
来看一下下面的例子:
 
#include <QtGui/QApplication>
#include <QtGui/QWidget>
#include <QtGui/QSpinBox>
#include <QtGui/QSlider>
#include <QtGui/QHBoxLayout>

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(slider, SIGNAL(valueChanged( int)), spinBox, SLOT(setValue( int)));
        QObject::connect(spinBox, SIGNAL(valueChanged( int)), slider, SLOT(setValue( int)));
        spinBox->setValue(35);

        QHBoxLayout *layout = new QHBoxLayout;
        layout->addWidget(spinBox);
        layout->addWidget(slider);
        window->setLayout(layout);

        window->show();

         return app.exec();
}
 
这里使用了两个新的组件:QSpinBox和QSlider,以及一个新的顶级窗口QWidget。QSpinBox是一个有上下箭头的微调器,QSlider是一个滑动杆,只要运行一下就会明白到底是什么东西了。
 
代码并不是那么难懂,还是来简单的看一下。首先创建了一个QWidget的实例,调用setWindowTitle函数来设置窗口标题。然后创建了一个QSpinBox和QSlider,分别设置了它们值的范围,使用的是setRange函数。然后进行信号槽的链接。这点后面再详细说明。然后是一个QHBoxLayout,就是一个水平布局,按照从左到右的顺序进行添加,使用addWidget添加好组件后,调用QWidget的setLayout把QWidget的layout设置为我们定义的这个Layout,这样,程序就完成了!
 
编译运行一下,可以看到效果:
 
 
如果最大化的话:
 
 
虽然我并没有添加任何代码,但是那个layout就已经明白该怎样进行布局。
 
或许你发现,那两个信号槽的链接操作会不会产生无限递归?因为steValue就会引发valueChanged信号!答案是不会。这两句语句实现了,当spinBox发出valueChanged信号的时候,会回调slider的setValue,以更新slider的值;而slider发出valueChanged信号的时候,又会回调slider的setValue。但是,如果新的value和旧的value是一样的话,是不会发出这个信号的,因此避免了无限递归。
 
迷糊了吧?举个例子看。比如下面的spinBox->setValue(35)执行的时候,首先,spinBox会将自己的值设为35,这样,它的值与原来的不一样了(在没有setValue之前的时候,默认值是0),于是它发出了valueChanged信号。slider接收到这个信号,于是回调自己的setValue函数,将它的值也设置成35,它也发出了valueChanged信号。当然,此时spinBox又收到了,不过它发现,这个35和它本身的值是一样的,于是它就不发出信号,所以信号传递就停止了。
 
那么,你会问,它们是怎么知道值的呢?答案很简单,因为你的信号和槽都接受了一个int参数!新的值就是通过这个进行传递的。实际上,我们利用Qt的信号槽机制完成了一个数据绑定,使两个组件或者更多组件的状态能够同步变化。
 
Qt一共有三种主要的layout,分别是:
 
QHBoxLayout- 按照水平方向从左到右布局;
 
QVBoxLayout- 按照竖直方向从上到下布局;
 
QGridLayout- 在一个网格中进行布局,类似于HTML的table。
 
layout使用addWidget添加组件,使用addLayout可以添加子布局,因此,这就有了无穷无尽的组合方式。
 
我是在Windows上面进行编译的,如果你要是在其他平台上面,应用程序就会有不同的样子:
 
 
还记得前面曾经说过,Qt不是使用的原生组件,而是自己绘制模拟的本地组件的样子,不过看看这个截图,它模拟的不能说百分百一致,也可说是惟妙惟肖了… :)
Qt学习之路(6): API文档的使用
今天来说一下有关Qt API文档的使用。因为Qt有一个商业版本,因此它的文档十分健全,而且编写良好。对于开发者来说,查看文档时开发必修课之一——没有人能够记住那么多API的使用!
 
在Qt中查看文档是一件很简单的事情。如果你使用QtCreator,那么左侧的Help按钮就是文档查看入口。否则的话,你可以在Qt的安装目录下的bin里面的assistant.exe中看到Qt的文档。在早期版本中,Qt的文档曾以HTML格式发布,不过在2009.03版中我没有找到HTML格式的文档,可能Qt已经把它全部换成二进制格式的了吧?——当然,如果你全部安装了Qt的组件,是可以在开始菜单中找到assistant的!
 
assistant里面的文档有很多项:
 
 
其中,第一个是帮助的帮助:-);第二个是Qt Designer的帮助;第三个是Qt Linguist的帮助;第四个是QMake的帮助;最后一个是Qt的API文档,在QtCreator中默认打开的就是这部分。
 
不过,关于文档的内容这里实在不好阐述,因为整个文档太大了,我也并没有看过多少,很多时候都是随用随查,就好像是字典一样——谁也不会天天没事抱着本字典去看不是?还有就是这里的文档都是英文的,不过如果是做开发的话,了解一些英文还是很有帮助的,不是吗?
首先说明一点,在C++ GUI Programming with Qt4, 2nd中,这一章连同以后的若干章一起,完成了一个比较完整的程序——一个模仿Excel的电子表格。不过这个程序挺大的,而且书中也没有给出完整的源代码,只是分段分段的——我不喜欢这个样子,我想要看到我写出来的是什么东西,这是最主要的,而不是慢慢的过上几章的内容才能看到自己的作品。所以,我打算换一种方式,每章只给出简单的知识,但是每章都能够运行出东西来。好了,扯完了,下面开始!
 
以前说的主要是一些基础知识,现在我们来真正做一个东西——一个查找对话框。什么?什么叫查找对话框?唉唉,先看看我们的最终作品吧!
 
 
好了,首先新建一个工程,就叫FindDialog吧!嗯,当然还是Qt Gui Application,然后最后一步注意,Base Dialog选择QDialog,而不是默认的QMainWindow,因为我们要学习建立对话框嘛!名字随便起,不过我就叫finddialog啦!Ganarate form还是不要的。然后Finish就好了。
 
打开finddialog.h,开始编写头文件。
 
#ifndef FINDDIALOG_H
#define FINDDIALOG_H

#include <QtGui/QDialog>

class QCheckBox;
class QLabel;
class QLineEdit;
class QPushButton;

class FindDialog : public QDialog
{
        Q_OBJECT

public:
        FindDialog(QWidget *parent = 0);
        ~FindDialog();
signals:
         void findNext( const QString &str, Qt::CaseSensitivity cs);
         void findPrevious( const QString &str, Qt::CaseSensitivity cs);
private slots:
         void findClicked();
         void enableFindButton( const QString &text);
private:
        QLabel *label;
        QLineEdit *lineEdit;
        QCheckBox *caseCheckBox;
        QCheckBox *backwardCheckBox;
        QPushButton *findButton;
        QPushButton *closeButton;
};

#endif // FINDDIALOG_H
 
大家都是懂得C++的啊,所以什么#ifndef,#define和#endif的含义和用途就不再赘述了。
 
首先,声明四个用到的类。这里做的是前向声明,否则的话是编译不过的,因为编译器不知道这些类是否存在。简单来说,所谓前向声明就是告诉编译器,我要用这几个类,而且这几个类存在,你就不要担心它们存不存在的问题啦!
 
然后是我们的FindDialog,继承自QDialog。
 
下面是一个重要的东西:Q_OBJECT。这是一个宏。凡是定义信号槽的类都必须声明这个宏。至于为什么,我们以后再说。
 
然后是public的构造函数和析构函数声明。
 
然后是一个signal:,这是Qt的关键字——还记得前面说过的嘛?Qt扩展了C++语言,因此它有自己的关键字——这是对信号的定义,也就是说,FindDialog有两个public的信号,它可以在特定的时刻发出这两个信号,就这里来说,如果用户点击了Find按钮,并且选中了 Search backward,就会发出findPrevious(),否则发出findNext()
 
紧接着是private slots:的定义,和前面的signal一样,这是私有的槽的定义。也就是说,FindDialog具有两个槽,可以接收某些信号,不过这两个槽都是私有的。
 
为了slots的定义,我们需要访问FindDialog的组件,因此,我们把其中的组件定义为成员变量以便访问。正是因为需要定义这些组件,才需要对它们的类型进行前向声明。因为我们仅仅使用的是指针,并不涉及到这些类的函数,因此并不需要include它们的头文件——当然,你想直接引入头文件也可以,不过那样的话编译速度就会慢一些。
#include <QtGui>
#include "finddialog.h"

FindDialog::FindDialog(QWidget *parent)
        : QDialog(parent)
{
        label = new QLabel(tr( "Find &what:"));
        lineEdit = new QLineEdit;
        label->setBuddy(lineEdit);

        caseCheckBox = new QCheckBox(tr( "Match &case"));
        backwardCheckBox = new QCheckBox(tr( "Search &backford"));

        findButton = new QPushButton(tr( "&Find"));
        findButton->setDefault( true);
        findButton->setEnabled( false);

        closeButton = new QPushButton(tr( "Close"));

        connect(lineEdit, SIGNAL(textChanged( const QString&)), this, SLOT(enableFindButton( const QString&)));
        connect(findButton, SIGNAL(clicked()), this, SLOT(findClicked()));
        connect(closeButton, SIGNAL(clicked()), this, SLOT(close()));

        QHBoxLayout *topLeftLayout = new QHBoxLayout;
        topLeftLayout->addWidget(label);
        topLeftLayout->addWidget(lineEdit);

        QVBoxLayout *leftLayout = new QVBoxLayout;
        leftLayout->addLayout(topLeftLayout);
        leftLayout->addWidget(caseCheckBox);
        leftLayout->addWidget(backwardCheckBox);

        QVBoxLayout *rightLayout = new QVBoxLayout;
        rightLayout->addWidget(findButton);
        rightLayout->addWidget(closeButton);
        rightLayout->addStretch();

        QHBoxLayout *mainLayout = new QHBoxLayout;
        mainLayout->addLayout(leftLayout);
        mainLayout->addLayout(rightLayout);
        setLayout(mainLayout);

        setWindowTitle(tr( "Find"));
        setFixedHeight(sizeHint().height());
}

FindDialog::~FindDialog()
{

}

void FindDialog::findClicked()
{
        QString text = lineEdit->text();
        Qt::CaseSensitivity cs = caseCheckBox->isChecked() ? Qt::CaseInsensitive : Qt::CaseSensitive;
         if(backwardCheckBox->isChecked()) {
                emit findPrevious(text, cs);
        } else {
                emit findNext(text, cs);
        }
}

void FindDialog::enableFindButton( const QString &text)
{
        findButton->setEnabled(!text.isEmpty());
}
 
CPP文件要长一些哦——不过,它们的价钱也会更高,嘿嘿——嗯,来看代码,第一行include的是QtGui。Qt是分模块的,记得我们建工程的时候就会问你,使用哪些模块?QtCore?QtGui?QtXml?等等。这里,我们引入QtGui,它包括了QtCore和QtGui模块。不过,这并不是最好的做法,因为QtGui文件很大,包括了GUI的所有组件,但是很多组件我们根本是用不到的——就像Swing的import,你可以import到类,也可以使用*,不过都不会建议使用*,这里也是一样的。我们最好只引入需要的组件。不过,那样会把文件变长,现在就先用QtGui啦,只要记得正式开发时不能这么用就好啦!
 
构造函数有参数初始化列表,用来调用父类的构造函数,相当于Java里面的super()函数。这是C++的相关知识,不是Qt发明的,这里不再赘述。
 
然后新建一个QLabel。还记得前面的Hello, world!里面也使用过QLabel吗?那时候只是简单的传入一个字符串啊!这里怎么是一个函数tr()?函数tr()全名是QObject::tr(),被它处理的字符串可以使用工具提取出来翻译成其他语言,也就是做国际化使用。这以后还会仔细讲解,只要记住, Qt的最佳实践:如果你想让你的程序国际化的话,那么,所有用户可见的字符串都要使用QObject::tr()!但是,为什么我们没有写QObject::tr(),而仅仅是tr()呢?原来,tr()函数是定义在Object里面的,所有使用了Q_OBJECT宏的类都自动具有tr()函数。
 
字符串中的&代表快捷键。注意看下面的findButton的&Find,它会生成 Find字符串,当你按下Alt+F的时候,这个按钮就相当于被点击——这么说很难受,相信大家都明白什么意思。同样,前面label里面也有一个&,因此它的快捷键就是Alt+W。不过,这个label使用了setBuddy函数,它的意思是,当label获得焦点时,比如按下Alt+W,它的焦点会自动传给它的buddy,也就是lineEdit。看,这就是伙伴的含义(buddy英文就是伙伴的意思)。
 
后面几行就比较简单了:创建了两个QCheckBox,把默认的按钮设为findButton,把findButton设为不可用——也就是变成灰色的了。
 
再下面是三个connect语句,用来连接信号槽。可以看到,当lineEdit发出textChanged(const QString&)信号时,FindDialog的enableFindButton(const QString&)函数会被调用——这就是回调,是有系统自动调用,而不是你去调用——当findButton发出clicked()信号时,FindDialog的findClicked()函数会被调用;当closeButton发出clicked()信号时,FindDialog的close()函数会被调用。注意,connect()函数也是QObject的,因为我们继承了QObject,所以能够直接使用。
 
后面的很多行语句都是layout的使用,虽然很复杂,但是很清晰——编写layout布局最重要一点就是思路清楚,想清楚哪个套哪个,就会很好编写。这里我们的对话框实际上是这个样子的:
 
 
注意那个spacer是由rightLayout的addStretch()添加的,就像弹簧一样,把上面的组件“顶起来”。
 
最后的setWindowTitle()就是设置对话框的标题,而setFixedHeight()是设置成固定的高度,其参数值sizeHint()返回“最理想”的大小,这里我们使用的是height()函数去到“最理想”的高度。
 
好了,下面该编写槽了——虽然说是slot,但实际上它就是普通的函数,既可以和其他函数一样使用,又可以被系统回调。
 
先看findClicked()函数。首先取出lineEdit的输入值;然后判断caseCheckBox是不是选中,如果选中就返回Qt::CaseInsensitive,否则返回Qt::CaseSensitive,用于判断是不是大小写敏感的查找;最后,如果backwardCheckBox被选中,就emit(发出)信号findPrevious(),否则emit信号findNext。
 
enableFindButton()则根据lineEdit的内容是不是变化——这是我们的connect连接的——来设置findButton是不是可以使用,这个很简单,不再说了。
 
这样,FindDialog.cpp也就完成了。下面编写main.cpp——其实QtCreator已经替我们完成了——
 
#include <QApplication>

#include "finddialog.h"

int main( int argc, char *argv[])
{
        QApplication app(argc, argv);
        FindDialog *dialog = new FindDialog;
        dialog->show();
         return app.exec();
}
 
运行一下看看我们的成果吧!
 
虽然很简单,也没有什么实质性的功能,但是我们已经能够制作对话框了——Qt的组件成百上千,不可能全部介绍完,只能用到什么学什么,更重要的是,我们已经了解了其编写思路,否则的话,即便是你拿着全世界所有的砖瓦,没有设计图纸,你也不知道怎么把它们组合成高楼大厦啊!
信号槽机制是Qt编程的基础。通过信号槽,能够使Qt各组件在不知道对方的情形下能够相互通讯。这就将类之间的关系做了最大程度的解耦。
 
槽函数和普通的C++成员函数没有很大的区别。它们也可以使virtual的;可以被重写;可以使public、protected或者private的;可以由其它的C++函数调用;参数可以是任何类型的。如果要说区别,那就是,槽函数可以和一个信号相连接,当这个信号发生时,它可以被自动调用。
 
connect()语句的原型类似于:
 
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
 
这里,sender和receiver都是QObject类型的,singal和slot都是没有参数名称的函数签名。SINGAL()和SLOT()宏用于把参数转换成字符串。
 
深入的说,信号槽还有更多可能的用法,如下所示。
 
一个信号可以和多个槽相连:
 
connect(slider, SIGNAL(valueChanged( int)),
              spinBox, SLOT(setValue( int)));
connect(slider, SIGNAL(valueChanged( int)),
               this, SLOT(updateStatusBarIndicator( int)));
 
注意,如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。
 
多个信号可以连接到一个槽:
 
connect(lcd, SIGNAL(overflow()),
              this, SLOT(handleMathError()));
connect(calculator, SIGNAL(divisionByZero()),
              this, SLOT(handleMathError()));
 
这是说,只要任意一个信号发出,这个槽就会被调用。
 
一个信号可以连接到另外的一个信号:
 
connect(lineEdit, SIGNAL(textChanged( const QString &)),
              this, SIGNAL(updateRecord( const QString &)));
 
这是说,当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
 
槽可以被取消链接:
 
disconnect(lcd, SIGNAL(overflow()),
                  this, SLOT(handleMathError()));
 
这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
 
为了正确的连接信号槽,信号和槽的参数个数、类型以及出现的顺序都必须相同,例如:
 
connect(ftp, SIGNAL(rawCommandReply( int, const QString &)),
              this, SLOT(processReply( int, const QString &)));
 
这里有一种例外情况,如果信号的参数多于槽的参数,那么这个参数之后的那些参数都会被忽略掉,例如:
 
connect(ftp, SIGNAL(rawCommandReply( int, const QString &)),
             this, SLOT(checkErrorCode( int)));
 
这里,const QString &这个参数就会被槽忽略掉。
 
如果信号槽的参数不相容,或者是信号或槽有一个不存在,或者在信号槽的连接中出现了参数名字,在Debug模式下编译的时候,Qt都会很智能的给出警告。
 
在这之前,我们仅仅在widgets中使用到了信号槽,但是,注意到connect()函数其实是在QObject中实现的,并不局限于GUI,因此,只要我们继承QObject类,就可以使用信号槽机制了:
 
class Employee : public QObject
{
        Q_OBJECT
public:
        Employee() { mySalary = 0; }
         int salary() const { return mySalary; }

public slots:
         void setSalary( int newSalary);

signals:
         void salaryChanged( int newSalary);

private:
         int mySalary;
};
 
在使用时,我们给出下面的代码:
 
void Employee::setSalary( int newSalary)
{
         if (newSalary != mySalary) {
                mySalary = newSalary;
                emit salaryChanged(mySalary);
        }
}
 
这样,当setSalary()调用的时候,就会发出salaryChanged()信号。注意这里的if判断,这是避免递归的方式!还记得前面提到的循环连接吗?如果没有if,当出现了循环连接的时候就会产生无限递归。
Qt学习之路(10): Meta-Object系统
前面说过,Qt使用的是自己的预编译器,它提供了对C++的一种扩展。利用Qt的信号槽机制,就可以把彼此独立的模块相互连接起来,不需要实现知道模块的任何细节。
 
为了达到这个目的,Qt提出了一个Meta-Object系统。它提供了两个关键的作用:信号槽和内省。
 
面向对象程序设计里面会讲到Smalltalk语言有一个元类系统。所谓元类,就是这里所说的Meta-Class。如果写过HTML,会知道HTML标签里面也有一个<meta>,这是用于说明页面的某些属性的。同样,Qt的Meta-Object系统也是类似的作用。内省又称为反射,允许程序在运行时获得类的相关信息,也就是meta-information。什么是meta-information呢?举例来说,像这个类叫什么名字?它有什么属性?有什么方法?它的信号列表?它的槽列表?等等这些信息,就是这个类的meta-information,也就是“元信息”。这个机制还提供了对国际化的支持,是QSA(Qt Script for Application)的基础。
 
标准C++并没有Qt的meta-information所需要的动态meta-information。所以,Qt提供了一个独立的工具,moc,通过定义Q_OBJECT宏实现到标准C++函数的转变。moc使用纯C++实现的,因此可以再任何编译器中使用。

这种机制工作过程是:
 
首先,Q_OBJECT宏声明了一些QObject子类必须实现的内省的函数,如metaObject(),tr(),qt_metacall()等;
 
第二,Qt的moc工具实现Q_OBJECT宏声明的函数和所有信号;
 
第三,QObject成员函数connect()和disconnect()使用这些内省函数实现信号槽的连接。
以上这些过程是qmake,moc和QObject自动处理的,你不需要去考虑它们。如果实现好奇的话,可以通过查看QMetaObject的文档和moc的源代码来一睹芳容。

本文出自 “豆子空间” 博客,请务必保留此出处http://devbean.blog.51cto.com/448512/199472

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值